Service
Services overview | Android Developers (google.cn)
一个应用程序组件,没有界面,即使切换到其他程序还可以长期在后台运行。一个组件可以和一个服务绑定后交互,甚至可以进程间通信。服务可以在后台处理网络通信,播放音乐,文件读写或者与content provider交互。
服务运行在当前进程的主线程中,除非指定,否则服务不会创建自己的线程也不会运行在独立的进程中,因此服务中执行任何阻塞操作需要在单独的线程中执行,避免阻塞主线程导致ANR。
考虑使用WorkManager
来代替Service
的功能
分类
前端服务:显示在通知栏上的服务,用户可以明确知道当前有这个服务在运行,例如音乐播放时,通知栏显示
后端服务:后台服务,用户不会感知到在执行,例如下载文件
绑定服务:当一个应用组件通过bindService()
绑定到这个服务,服务给组件提供C/S模式的交互,也可以进程间通信。绑定服务只在一个组件与他绑定后才会运行,当多个组件和一个服务绑定,只有当所有的组件都解绑后,服务才会销毁。
服务作为一个组件需要在manifest文件中声明,也可声明为私有,这样别的应用程序不能使用。可以在声明中增加android:description
属性提供一个服务的说明,用户可以看到这个服务的作用。
安全考虑使用一个显式的Intent来启动服务,不要给服务声明intent filter。
生命周期
由于用户可能看不到服务的运行状态,所以服务的生命周期管理十分重要,避免没有被销毁。
启动服务:一个组件通过调用startService()运行起来,通过参数Intent将信息传递给服务,服务自己调用stopSelf()或其他组件调用stopService()。启动这个服务的组件即使销毁了,服务还是运行状态。另一个组件可以停止其他组件启动的服务。一个服务可以启动多次,如果服务已经是运行状态,那么startService()执行后会调用onStartCommand(),而不再调用onCreate()
绑定服务:其他组件通过调用bindService()运行起来,客户端通过IBinder接口与服务交互。客户端通过调用unbindService()结束连接。服务不需要自己结束。
对于一个启动服务,其他组件还可以bind到这个服务上,此时调用stopService()或stopSelf()并不会结束服务,直到所有绑定的客户端unbind。例如通过启动服务开始播放音乐,其他组件可以通过绑定到这个服务获取当前播放的歌曲信息。
停止一个服务,当一个服务有多个并行启动的请求时,多个请求都会执行onStartCommand(),如果有一个触发停止,可能会导致新启动服务被停止掉,因此可以在stopSelf(int)中传入对应请求onStartCommand()的startId,在stopSelf()中判断如果id不是当前最新的id,就不能停止。
系统在内存很少时会结束后台运行的服务,如果服务与用户当前交互的界面绑定,不太会被销毁;如果一个服务声明为前端服务,几乎不会被自动销毁;系统销毁一个服务后,当资源满足后,还会把服务运行起来,此时会执行onStartCommand()接口。根据onStartCommand()的返回值START_NOT_STICKY
/START_STICKY
/START_REDELIVER_INTENT
,系统会决定重启服务时传入的Intent的方式。
基本接口
onStartCommand() 组件调用startService()启动服务时会回调这个接口,只要有调用这个接口,就需要手动调用stopService()来释放
onBind() 组件通过调用bindService()与服务绑定会回调这个接口,这个接口需要返回一个IBinder接口,用来实现客户端与服务的交互。如果不希望被绑定,返回null。
onCreate() 只会在服务初始化调用一次,如果服务已经运行,不会被回调。例如绑定一个已经启动服务,不会回调这个接口。可以在这里创建线程
onDestroy() 系统销毁服务回调,可以用来释放创建的资源例如线程。
举例
1 | public class HelloService extends Service { |
前端服务
前端服务用于当用户不需要与应用直接交互,但是又需要知道应用当前的运行状态的场景。前端服务会固定显示通知栏通知,直到服务结束。例如音乐播放器切换到后台后,波形音乐信息可以用前端服务在状态栏显示,一个跑步应用可以实时显示跑步距离。
配置
API level 28 anroid 9 必须声明FOREGROUND_SERVICE
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" ...> |
前端服务周期
启动一个服务
1
2
3Context context = getApplicationContext();
Intent intent = new Intent(...); // Build the intent for the service
context.startForegroundService(intent);在服务的
onStartCommand
接口中调用startForeground
让服务在前端运行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification =
new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build();
// Notification ID cannot be 0.
startForeground(ONGOING_NOTIFICATION_ID, notification);移除前端服务 使用
stopForeground
传入boolean变量决定是否同时删除通知栏显示,这个方法执行后,服务还是运行状态。也可以停止服务来结束服务运行,通知栏会自动删除。
声明前端服务类型
声明前端服务的类型,可以让前端服务访问位置,摄像头和麦克风信息
配置文件中需要增加配置
1
2
3
4
5<manifest>
...
<service ...
android:foregroundServiceType="location|camera|microphone" />
</manifest>启动服务时指明需要哪些权限
1
2
3Notification notification = ...;
Service.startForeground(notification,
FOREGROUND_SERVICE_TYPE_LOCATION | FOREGROUND_SERVICE_TYPE_CAMERA);当应用在后台运行时,前端服务使用的这些权限会有限制,此时不能访问麦克风和摄像头,只有当用户授权了
ACCESS_BACKGROUND_LOCATION
权限后,才能访问位置信息。当然还有一些特殊情况可以去掉这种限制。
通知栏
以下几种前端服务会立即显示到通知栏:
- The service is associated with a notification that includes action buttons.
- The service has a
foregroundServiceType
ofmediaPlayback
,mediaProjection
, orphoneCall
. - The service provides a use case related to phone calls, navigation, or media playback, as defined in the notification’s category attribute.
- The service has opted out of the behavior change by passing
FOREGROUND_SERVICE_IMMEDIATE
intosetForegroundServiceBehavior()
when setting up the notification.
绑定服务
绑定服务是一种客户端-服务端模式的服务,当一个组件例如activity绑定了一个服务,activity作为客户端可以向服务发送请求。同时不同进程间可以使用绑定服务实现IPC。
可以同时实现 onBind()
和onStartCommand()
两个接口,这样一个服务可以正常启动后,再被别的组件绑定。例如用户从一个音乐播放器程序的activity启动了服务进行音乐播放,在用户把音乐程序切换后台后,再切换回来,这个activity可以绑定之前服务,对音乐进行控制。
服务端
当有一个客户端绑定服务后,系统会回调服务的onBind() 接口,这个接口返回一个IBinder
对象供客户端访问服务的公共接口。当有多个客户端绑定服务时,只有第一个绑定时会回调onBind
,后面的绑定都复用缓存的同一个IBinder
接口对象。
如果服务端在onUnBind()
中返回true
,那么下次有客户端再绑定服务时,会回调服务的onRebind
接口。
IBinder接口对象
有三种方式提供IBinder
接口实现:
提供
Binder
的子类如果服务只是给应用内部使用,且不需要进程间通信,返回一个继承Binder类的对象来提供服务的公共接口最合适。
使用
Messenger
如果服务需要在不同进程间通信,由于不同进程间不能获取对方接口信息,所以不能直接调用
Binder
对象的方法。这时需要使用Messenger
,通过消息的方式给服务发送请求。服务中定义一个Handler
来处理客户端请求的Message
。Messenger
内部会把所有的客户端请求Message
放在一个线程的队列中通知给服务,这样服务中不需要考虑多线程问题。使用AIDL
Android Interface Definition Language (AIDL) 可以将对象进行序列化后用于进程间的通信。
Messenger
本质上也是使用了AIDL,只是把所有的请求放在一个队列中执行。当服务需要同时处理多个客户端的请求时,可以使用AIDL的方式,此时需要服务端自己处理多线程。
客户端
客户端通过调用 bindService()
来绑定一个服务,绑定过程是异步的,bindService()会立即返回,客户端需要实现 ServiceConnection 用来监控与服务的连接状态。
bindService(new Intent(Binding.this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE)
其中的mConnection
在绑定成功后收到onServiceConnected
回调,里面可以获得服务的onBind
接口返回的IBinder
对象。
客户端通过调用 unbindService()
与服务解绑,当客户端被销毁时,同时也会触发解绑,但是建议不需要服务的时候客户端主动解绑,释放服务资源。
注意事项
bind
和unbind
要成对出现。如果客户端只是在用户可见的时候与服务有交互,在onStart
中绑定,onStop
中解绑定- 如果activity切换到后台后还有交互,在
onCreate
中绑定,onDestory
中解绑定。这种方式activity在整个生命周期中都使用服务,如果服务在另一个进程中运行,这样会增加服务进程的权重,系统更可能杀死这个进程。 - 对象的引用计数会跨进程累计
- 连接发生异常时,会抛出 DeadObjectException
实现Binder类的步骤
- 服务类中创建一个Binder类的实例,这个类提供:
- 客户端可以调用的公共方法
- 返回当前的Service类的实例,客户端可以通过这个实例访问服务的公共方法
- 返回服务中定义的其他类的实例,客户端可以访问这些类的公共方法
- 服务的
onBind()
方法返回定义的Binder类的实例 - 客户端在
onServiceConnected()
中获取Binder类对象,并调用其提供的接口。
服务端举例
1 | public class LocalService extends Service { |
客户端举例
1 | public class BindingActivity extends Activity { |
实现Messenger的步骤
- 服务实现 Handler 用来处理客户端发来的请求
- 服务使用 Handler 创建一个
Messenger
对象,Messager
对象中有这个Handler
的一个引用 Messenger
创建一个IBinder
用来在onBind
中返回给客户端- 客户端使用
IBinder
对象获得Messenger
对象,客户端使用Messenger
对象给服务发送Message
对象 - 服务在 Handler 的
handleMessage()
中处理客户端发来的Message
- 客户端中也可以像服务端一样创建一个
Messenger
对象,在发送消息时,把自己的Messenger
对象作为Message
的replyTo
参数,这样服务收到消息后,可以使用客户端的Messenger
对象给客户端回消息。
客户端举例
1 | public class MessengerServiceActivities { |
服务端举例
1 | //BEGIN_INCLUDE(service) |
AIDL
一般不会用到