oynix

于无声处听惊雷,于无色处见繁花

Android基础之Service

Service的使用。

1. 启动方式

Service有两种启动方式,startService和bindService。

2. 生命周期

这里应该放一张官网的图,但是加图有些麻烦,还是用文字代替吧。

Service在通过startService启动时:onCreate -> onStartCommand。一个Service系统只会创建一个实例,多次调用start方法时,onCreate不会重复调用,只在第一次创建时调用一次,而onStartCommand会被多次调用。停止时调用stopService,此时会调用onDestroy

Service在通过bindService启动时:onCreate -> onBind,onCreate在创建时调用,Service是用来通过服务的server,想要bind Service的是client。一个server可以同时给多个client提供服务。只有在client第一次调用bindService时,才会回调onBind方法。当client调用unbindService时,此时会回调Service的onUnbind方法,当此server的client数量为0时,会调用onDestroy进行销毁。

混合调用时,即创建一个Service实例后,既调用过startService,也调用过bindService,那么这时即便bind的client为0时,Service也不会停止,只有通过stopService才能停止该Service,可以理解为start的等级更高些。

3. onStartCommand(intent: Intent?, flags: Int, startId: Int): Int

这个方法有3个参数1个返回值,摘点官方文档的介绍

  • intent:启动时,启动组件传递过来的Intent,如Activity可利用Intent封装所需要的参数并传递给Service
  • flags: 表示启动请求时是否有额外数据,可选值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY,0代表没有,它们具体含义如下:
    • START_FLAG_REDELIVERY: 这个值代表了onStartCommand方法的返回值为START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf方法停止服务。其中START_REDELIVER_INTENT意味着当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),此时Intent时有值的。
    • START_FLAG_RETRY: 该flag代表当onStartCommand调用后一直没有返回值时,会尝试重新去调用onStartCommand()。
  • startId: 指明当前服务的唯一ID,与stopSelfResult (int startId)配合使用,stopSelfResult 可以更安全地根据ID停止服务。

onStartCommand的返回值int类型,它有三种可选值, START_STICKY,START_NOT_STICKY,START_REDELIVER_INTENT,它们具体含义如下:

  • START_STICKY: 当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。
  • START_NOT_STICKY: 当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
  • START_REDELIVER_INTENT: 当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。

首次startService时,flags的值是0,其他情况启动时,flags的值根据其返回值决定。

4. startService示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Sevice,作为四大组件之一,需要在AndroidManifest中声明
class SampleService : Service() {

val TAG = SampleService::class.java.canonicalName

override fun onCreate() {
super.onCreate()
Log.d(TAG, "on create invoked")
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "on start command invoked, flag:${flags}")
return super.onStartCommand(intent, flags, startId)
}

override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "on destroy invoked")
}
}

// 在Activity中启动
val intent = Intent(this, SampleService::class.java)
startService(intent)

// 在Activity中停止
var intent = Intent(this, SampleService::class.java)
stopService(intent)

5. bindService示例,同一进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// BindService
class BindService : Service() {

val TAG = BindService::class.java.canonicalName

private lateinit var runner: Thread

// 写一个Binder的子类,用来返回给client
inner class ServerBinder : Binder() {

// 在Binder中写一个获取server的方法,当client拿到Binder时,通过
// 这个方法就可以获取到server实例
fun getService() : BindService {
return this@BindService
}
}

override fun onCreate() {
super.onCreate()
Log.d(TAG, "on create invoked")
}

override fun onBind(intent: Intent?): IBinder? {
Log.d(TAG, "on bind invoked")
return ServerBinder()
}

fun getData() : Int = 4

override fun onUnbind(intent: Intent?): Boolean {
Log.d(TAG, "on unbind invoked")
return super.onUnbind(intent)
}

override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "on destroy invoked")
runner.interrupt()
}
}

// 在Activity中,创建一个ServiceConnection,用来响应连接到Service的回调
private val conn = object : ServiceConnection {

// 连接成功后回调,返回的Binder就是Service中onBinder的返回值
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.d(TAG, "on service connected")
val binder = service as BindService.ServerBinder
val server = binder.getService()
val data = server.getData()
}

// Android 系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。注意:当客户端取消绑定时,系统“绝对不会”调用该方法。
override fun onServiceDisconnected(name: ComponentName?) {
Log.d(TAG, "on service disconnected")
}
}

// 绑定
val intent = Intent(this, BindService::class.java)
bindService(intent, conn, BIND_AUTO_CREATE)

// 解除绑定
val intent = Intent(this, BindService::class.java)
unbindService(conn)

6. bindService示例,不同进程

想要实现client和server的双向通信,那么就需要双方都有发送/接收消息,即Handler+Messenger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// MessengerService,同样需要在AndroidManifest中声明,同时增加process=":process"属性来指定进程
class MessengerService : Service() {

val TAG = MessengerService::class.java.canonicalName

// Handler用来处理client发送过来的消息
private val handler = object : Handler(Looper.myLooper()!!) {

override fun handleMessage(msg: Message) {
Log.d(TAG, "service handle message:${msg.what}")
val reply = Message.obtain()
reply.what = 45456
msg.replyTo.send(reply)
}
}

// Messenger用来发送消息给client
private val messenger = Messenger(handler)

override fun onCreate() {
super.onCreate()
Log.d(TAG, "messenger service on create")
}

override fun onBind(intent: Intent?): IBinder? {
Log.d(TAG, "messenger service on bind")
return messenger.binder
}

override fun onUnbind(intent: Intent?): Boolean {
Log.d(TAG, "messenger service on unbind")
return super.onUnbind(intent)
}

override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "messenger service on destroy")
}
}

// Activity中,同上,Handler用来处理server发送的消息,Messenger用来发消息给server
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
Log.d(TAG, "client handle message:${msg.what}")
}
}

private val messenger = Messenger(handler)

private val ipcConn = object : ServiceConnection {

override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val server = Messenger(service)
val msg = Message.obtain()
msg.what = 12123
msg.replyTo = messenger
server.send(msg)
}

override fun onServiceDisconnected(name: ComponentName?) {
}
}

// 绑定Service
val intent = Intent(this, MessengerService::class.java)
bindService(intent, ipcConn, BIND_AUTO_CREATE)

// 解除绑定
val intent = Intent(this, MessengerService::class.java)
unbindService(ipcConn)

7. ForegroundService前台服务

通俗理解,后台任务多是用来做一些不需要用户看到的任务,比如静默下载,而前台任务则是做些需要给用户展示一些提示的任务,比如下载文件的进度条、播放音乐的进度条,等。摘一段官方的说明:

前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知,状态栏位于“正在进行”标题下方,这意味着除非服务停止或从前台删除,否则不能清除通知。例如将从服务播放音乐的音乐播放器设置为在前台运行,这是因为用户明确意识到其操作。 状态栏中的通知可能表示正在播放的歌曲,并允许用户启动 Activity 来与音乐播放器进行交互。如果需要设置服务运行于前台, 我们该如何才能实现呢?Android官方给我们提供了两个方法,分别是startForeground()和stopForeground()。

可以通过onStartzCommand方法传递命令,来回切换控制服务是否置于前台,这里只演示了如何切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// ForegroundService
class ForegroundService : Service() {

val TAG = ForegroundService::class.java.canonicalName

override fun onCreate() {
super.onCreate()
Log.d(TAG, "on service create")
// 8.0出了NotificationChannel,不设置就会报错:bad notification
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// make channel
val channelId = "default"
val channel = NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_DEFAULT)
val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (nm.getNotificationChannel(channelId) == null) {
nm.createNotificationChannel(channel)
}
NotificationCompat.Builder(this, channelId)
} else {
NotificationCompat.Builder(this)
}
// make notification
builder.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))
.setOngoing(true)
.setAutoCancel(false)
.setShowWhen(true)
.setContentTitle("Foreground Service")
val notification = builder.build()
startForeground(1, notification)
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "on service start command")
return super.onStartCommand(intent, flags, startId)
}

override fun onBind(intent: Intent?): IBinder? {
return null
}

override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "on service destroy")
stopForeground(true)
}
}

// 启动Service,启动有两种放,第一种就和正常启动无异,startService,这样启动的就是的正常的Service,只有在Service中调用startForeground后方为前台服务;
// 第二种方式是API26,也就是8.0 Oreo新加的:startForegroundService,它和startService的区别就是,启动之后5秒内若是没有调用startFroeground,则crash
val intent = Intent(this, ForegroundService::class.java)
startForegroundService(intent) // 必须在Service中调用startForeground
startService(intent)

// 停止Service,同正常停止
val intent = Intent(this, ForegroundService::class.java)
stopService(intent)

8. IntentService

和Service相比,特点是自带一个工作线程,可以用来做一些阻塞线程的任务,阻塞任务需要写在onHandleIntent里。现在已经显示弃用了,推荐使用androidx里的WorkManager

1
2
3
4
5
6
7
// 构造方法里面传入的是子线程的名字
class DIntentService : IntentService("download") {

override fun onHandleIntent(intent: Intent?) {
// do something
}
}

9. 显示启动和隐式启动

以上通过指定Service类的启动方式,均为显示启动。隐式启动指定Intent的action为全类名即可,此时需要service在Manifest中的exported属性为true。同一应用中两种方式均可,不同的应用只能用隐式。

1
2
val intent = Intent(ForegroundService::class.java.canonicalName)
startService(intent)

5.0之前隐式启动只会发送一个警告,5.0之后官方出于安全考虑禁止了隐式启动,直接报错:Service Intent must be explicit,意思就是指定的Service必须精确

1
2
3
4
5
6
7
8
9
10
private validateServiceIntent(Intent service) {
if (service.getComponent() == null && service.getPackage() == null) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
IllegalArgumentException ex = new IllegalArgumentException("Service Intent must be explicit: " + service);
throw ex;
} else {
Log.w(TAG, "Implicit intents with startService are not safe: " + service + " " + Debug.getCallers(2, 3));
}
}
}

解决方式,很简单,给Intent指定component,或者package,或者两者都指定,便可通过检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 指定package
val intent = Intent(ForegroundService::class.java.canonicalName)
intent.setPackage(getPackageName())
startService(intent)

// 指定component
fun getExplicitServiceIntent(context: Context, intent: Intent) : Intent? {
val pm = context.packageManager
val resolveInfo = pm.queryIntentServices(intent, 0)
if (resolveInfo == null || resolveInfo.size != 1) {
return null
}
val serviceInfo = resolveInfo[0]
val packageName = serviceInfo.serviceInfo.packageName
val className = serviceInfo.serviceInfo.name
val component = ComponentName(packageName, className)
val explicitIntent = Intent(intent);
explicitIntent.component = component
return explicitIntent;
}
------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2022/03/0907ab9bd01c/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道