在上一篇中我们介绍了Android Service的基础概念和如何创建及启动Service。在这一篇中我们来详细探索一下Service的两种启动方式:startService和bindService。
一、startService
当你的服务不需要与页面组件进行交互或只有提示性交互时,一般会使用startService
启动服务,让其独立于组件而运行,如在后台定位用户位置信息。
通过 startService()
方法启动服务,其生命周期如下图所示:
下面分别介绍其生命周期方法:
onCreate()
:服务第一次被创建时调用,主要用来处理一些服务所必须的初始化操作。注意,这里不要做任何耗时或繁重的操作。
onStartCommand()
:通过startService
启动服务,在服务创建后会即可调用该方法,也是服务处理任务的主要方法,其一般实现如下:
1 |
|
可以看到该方法需要返回一个 int
类型的值,该值用于描述服务被系统终止后,希望系统后续如何处理该服务。只能取以下三个常量中的一个:
- START_NO_STICKY:如果系统在
onStartCommand()
返回值后终止了服务,除非有待传递的挂起Intent,否则系统不会重现创建并启动服务。这是最安全的选项,可以避免服务及未完成任务状态的错乱,在合适的时机再次启动服务来处理待处理的任务。此模式适用于一些需要获取结果数据,但允许在内存紧张时停止服务且后续不需要再次恢复的任务,如从服务端请求数据。 - START_STICKY:如果系统在
onStartCommand()
返回值后终止了服务,则系统会重新创建并调用onStartCommand()
方法,但不会重新传递最后一个Intent,因此该模式下onStartCommond()
方法中的参数 intent 有可能为null,必须进行判空处理。此模式适用于不执行命令、但无限期运行的任务,如媒体播放器。 - START_REDELIVER_INTENT:如果系统在
onStartCommand()
返回值后终止了服务,则系统会重新创建服务,并将最后一个 Intent 传递给onStartCommand()
。所有挂起的 Intent 均会依次传递。此模式适用于主动执行,且应立即恢复的任务,如文件下载或上传。
onStartCommand()
接收三个参数:第一个 intent 就是通过startService传递进来的Intent,可以携带一些数据;第二个 flags 为标识此次请求服务的额外数据,该标识由系统处理并传递,通常请求下为0,当服务被系统终止后再次启动服务时,系统会根据 onStartCommand()
方法的返回值传递该标识, 取值为 START_FLAG_REDELIVERY 或 START_FLAG_RETRY,一般我们不会用到这个参数;第三个 startId 是一个标识本次请求的唯一Integer值,主要用于停止服务标识(stopSelfResult(int)
),如果调用stopSelfResult的 startId 和最后一个调用服务分配的startId不同,则不会停止服务。
停止服务有两种方式:一是在组件中调用 stopService()
停止服务;二是在 Service
内部调用 stopSelf()
停止服务;
注意:再次提醒下,Service 的所有生命周期方法默认都是运行在启动服务的进程中的主线程里的,如果有耗时或阻塞性操作,需要在 onStartCommand 方法中另起工作线程处理任务,也可以使用 IntentService 代替
onDestroy
:服务销毁时调用,一般在这里需要将service中的资源做释放处理。
通过 startService()
启动的服务可以通过 Toast 或 状态栏通知 来通知用户任务处理的状态,也可以通过广播的方式将执行结果传递给接受者。
二、bindService
如果你需要在服务启动后,与服务进行交互或IPC,应该以bindService()
来启动服务,并实现Service
的onBind()
方法,返回一个IBinder
实例来进行与服务的交互或IPC。以下为一个简单的onBind()
实现示例:
1 |
|
如上所示onBind()
方法需要返回一个IBinder
类型的参数,IBinder
是Android中进行IPC重要的类,很多地方都有其身影。这里我们只是简单地继承了IBinder
的子类Binder
,并提供了一个获取当前服务实例的方法。关于IBinder
在不同服用中推荐的不同实现方式我们放到后面再详细展开说明,这里先关注以绑定启动服务的方法或步骤。
实现了onBind()
方法后,我们就可以在组件中以绑定的方式来启动服务了。以绑定的方式启动服务比直接启动服务要稍微复杂些,除了需要封装Intent
来显式声明服务外,还必须提供ServiceConnection
实例,后者会监控与服务的连接,并能从中获取onBind()
返回的IBinder
实例,以便组件与服务进行交互或IPC。以下为bindService()
启动服务的示例:
1 |
|
以上即为绑定启动服务的示例,下面我们来一一分析下ServiceConnection
的实现和bindService()
的参数及绑定启动后Service
的生命周期。
ServiceConnection
是一个接口,当创建客户端和服务的连接时,Android系统会调用其 onServiceConnected(ComponentName name, IBinder service)
方法,并将服务在onBind()
方法返回的IBinder
作为其方法的第二个参数传递进来,客户端可以使用该参数与绑定的服务进行通信交互。当客户端和服务断开连接时,Android系统会调用其onServiceDisconnected(ComponentName name)
方法,通知客户端与服务的连接断开了。
bindService()
的第三个参数为绑定服务的选项标识,可能的取值为以下几种:
Context. BIND_AUTO_CREATE
:在绑定服务时自动创建服务,是绑定服务时常用的标识,其他的几个不常用;Context. BIND_NOT_FOREGROUND
:不允许服务的进程提升到前台;Context. BIND_ABOVE_CLIENT
:服务的优先级高于客户端,当OOM时会优先销毁application,然后才销毁服务;Context. BIND_ALLOW_OOM_MANAGEMENT
:允许进程持有的服务超出自身的内存管理边界,使服务得以运行;Context. BIND_WAIVE_PRIORITY
:不会超出目标服务进程的计划或内存管理优先级,允许服务像在后台运行的普通应用进程那样被后台LRU列表管理;
以单纯的bindService()
启动服务,组件可以通过unbindService()
来解除与服务的绑定关系,同一个服务允许多个客户端同时绑定服务,当所有与服务绑定的组件都解绑后,服务也会随之销毁。其生命周期如下所示:
同一个服务也允许以startService()
和bindService()
两种方式同时启动,这种情况下服务的声明周期跟两种启动方式都相关,只有所有与服务绑定的组件都解绑了且调用了停止服务,此时服务才会销毁。可以用下图来表示Service
可能的生命周期:
总结下绑定启动服务的步骤:
- 创建
ServiceConnection
实例,用来监控服务的连接情况。 - 创建声明服务的显示
Intent
,调用bindService()
方法来绑定启动服务。 - 在
ServiceConnection
实例的onServiceConnected()
回调方法中接收onBind()
返回的IBinder
实例,并调用该实例的公共方法获取Service
实例或Service
其他可供客户端调用的对象实例。 - 客户端就可以通过获取到的服务相关实例来与
Service
进行交互了。
三、IBinder的几种实现方式
绑定服务时onBind()
需要返回一个IBinder实例,其实现方式有以下三种方式:继承Binder类、使用Messenger创建IBinder和AIDL。这三种方式有不同的适用场景和实现难度,接下来一一介绍。
1. 继承Binder
如果你的服务只供自身应用使用,且无需进行跨进程工作,则优先推荐使用该方式,使用此方法让客户端访问Service
的公共方法或资源。这也是实现IBinder
最简单的方式,上面的示例中我们就是使用这种方式。
1 |
|
继承Binder的一般使用方式可以总结如下:
- 在你的服务中,创建可执行以下某种操作的
Binder
实现类:- 包含客户端可调用的公共方法。
- 返回当前的
Service
实例,该实例中包含客户端可调用的公共方法。 - 返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法。
- 从
onBind()
回调方法返回此Binder
实例。 - 在客户端中,从
onServiceConnected()
回调方法接收Binder
,并使用提供的方法调用绑定服务。
使用Messenger
如果你的服务需要进行跨进程通讯(IPC),且无需执行多线程处理,推荐使用Messenger
为你提供IBinder
实例。Messenger
比使用AIDL
更简单,其会将请求加入到一个队列中,然后让其按照队列顺序一次执行一个请求,不存在线程不安全的问题(多个进程中的不同线程调用同一个服务)。以下是使用Messenger
的示例和大致流程:
创建Service
类,内部实现处理消息的Handler
和以该Handler
实例为参数的Messenger
实例,在onBinder()
方法中返回Messenger
实例创建的IBinder
实例。
1 |
|
配置RemoteService
单独的进程,清单文件里配置如下:
1 |
|
在Activity
中绑定服务,并通过ServiceConnection
获取服务传递过来的IBinder
实例,并以此实例构建Messenger
,这样客户端就可以通过该Messenger
与服务进行通讯了,如下:
1 |
|
至此组件就可以通过Messenger
同服务进行通讯了,但上面的示例中只实现了由客户端主动同服务端通讯,而无法由服务端主动向客户端发起通讯。可以通过客户端在Message
设置replyTo
来实现。以下为完整的服务端和客户端双向通讯的示例代码:
1 |
|
总结下使用 Messenger
创建 IBinder
实例的大致流程如下:
- 在服务内创建处理消息的
Handler
,并以此作为参数构建Messenger
实例; - 在
onBind()
方法中返回由Messager
创建的IBinder
实例; - 在绑定服务的
ServiceConnection
的onConnected()
方法中接收服务的IBinder
实例,并以此为参数构建Messenger
实例,然后客户端就可以通过此Messenger
发送消息给服务端,并在服务端的Handler
里处理此消息。 - 如果想要服务端主动与客户端交互,需要在客户端中创建自己的
Handler
并以此为参数构建Messenger
,将此Messenger
作为使用服务端IBinder
构建的Messenger
发送Message
的replyTo
参数发送给服务端,服务端使用replyTo
的Messenger
与客户端进行主动交互。
AIDL
如果你的服务需要进行跨进程通讯,且需要多线程操作,可以使用 AIDL
来创建 IBinder
实例,你需要自己来保证多线程操作时的线程安全。AIDL
实现起来比较复杂,后续作为单独的章节来介绍,这里就不展开了。