startService的生命周期以及实现方式
可以通过一个简单的音乐播放器来测试startService的生命周期
- MainActivity中放三个按钮,分别代表播放,暂停,停止
将这三个按钮分别绑定监听器,播放按钮监听器中开启服务并播放歌曲,停止按钮停止服务并停止播放,暂停按钮暂停播放。
- 播放事件中使用startService(Intent intent)方法开启服务,停止事件中会调用服务中stopSelf(int startId)结束服务。
- 自定义MyService,继承自Service,然后分别重写
onCreate(),onStartCommand(),onDestory(),onBind()
方法,然后在方法中进行相关操作,并记录日志。其中onStartCommand()
在服务被调用时会被执行,故在此方法进行操作。 - 最后在AndroidManifest.xml中注册该Service。label标签给该service取个名字便于区分
<service
android:name=".MyService"
android:label="@string/service_test_worktwo">
</service>
开始测试
- 点击播放音乐,观察日志,发现进入了
onCreate()
和onStartCommand()
状态,开始播放音乐,服务被开启,点击多次时,只调用onStartCommand()
方法,onCreate()方法只有第一次会被调用。
- 点击暂停播放,然后再点播放音乐,观察日志,发现只从
onStartCommand()
进入,说明只有第一次打开应用时才走onCreate()
- 点击停止播放,观察日志,发现从
onStartCommand()
进入,然后调用onDestroy()
方法,服务被关闭
总结:第一次调用startService()
方法时,才会走onCreate()
方法,每次调用startService()
方法时,都会走onStartCommand()
方法。每次回调onStartCommand()
方法时,startId
都会递增,这个是用于标记每次用户发起的服务。当离开应用时,服务还在后台运行。只有显式的调用stopself()
或者stopService()
方法才会结束服务,结束服务时会调用onDestroy()
方法结束服务。
bindService的生命周期以及三种实现方式
通过继承Binder类来实现bindService
MainActivity
中放三个按钮,分别代表绑定服务,解除绑定,绑定服务后客户端使用服务中的方法。MainActivity
中提供ServiceConnection
用于连接服务,绑定服务中使用bindService()
方法进行绑定。解除绑定中使用unbindService()
方法进行解除绑定。
- 自定义
MyBindService
,继承自Service
,然后分别重写onCreate(),onDestory(),onBind(),onUnBind()
方法,然后在方法中进行相关操作,并记录日志。MyBindService
中提供一个内部类,并提供一个getService()
方法用于返回这个Service
类。 - 注册
测试
- 点击开启服务按钮,观察日志,发现经过了
onCreate()
和onBind()
状态,Activity
和服务被绑定。此时继续点击开启服务按钮,发现无日志,说明Activity
被绑定且未销毁前,只会onBind()(绑定)一次。
- 点击测试按钮,获得了随机值,符合预期,说明服务以及绑定成功。
- 点击取消绑定按钮,观察日志,发现经过了
onUnBind()
和onDestroy()
状态,服务解绑后被销毁,由于只有一个Activity绑定了服务,所以解绑后服务被销毁,符合预期。
- 退出应用时,发现经过了
onUnBind()
和onDestroy()
状态,说明服务会随着Activity的销毁而销毁。
通过使用Messenger类来实现bindService
使用了一个小demo测试了一下,这种方法的生命周期与第一种相同。记录一下使用方式。
- 服务实现一个 Handler,由其接收来自客户端的每个调用的回调
- Handler 用于创建 Messenger 对象(对 Handler 的引用)
- Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端
- 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用后者将 Message 对象发送给服务
- 服务在其 Handler 中(具体地讲,是在 handleMessage() 方法中)接收每个 Message。
通过AIDL来实现bindService
- Messenger的底层就是使用AIDL实现其底层结构的,都能够实现IPC,但是使用Messenger不需要考虑多线程问题,使用AIDL可以同时让服务处理多个请求,但是此时必须要考虑线程安全问题。
通过一个简单的图书存储demo测试AIDL,分为客户端和服务端
-
服务端:
- 创建
aidl
文件夹
其中Java
类为自定义的Book
实体类,需实现Parcelable
接口,这样才能被AIDL
支持
Book.aidl
中需要声明Book
类
IBookManager.aidl
文件相当于一个接口,提供图书管理的系统的两个简单操作
然后直接将aidl包整个拷贝到客户端main下即可 - 创建远程服务类
RemoteService
类,继承自Service
,重写onBind()
方法,这个方法返回一个Binder
类用于客户端使用。在这个类中各个生命周期中记录日志,方便记录生命周期的变化。
- 创建
-
客户端:
- 创建四个按钮,方便记录生命周期和测试。分别添加绑定事件。
- MainActivity中在点击事件中进行绑定和解绑,并进行添加书籍和查看书籍操作(远程服务调用)。
- 创建四个按钮,方便记录生命周期和测试。分别添加绑定事件。
测试
- 先打开服务端,然后打开客户端
- 点击绑定服务,观察服务端日志,发现经过了
onCreate()
和onBind()
状态,绑定服务成功。
- 点击解绑,观察服务端日志,发现经过了
onUnbind()
和onDestroy()
状态,绑定解绑成功,由于只有一个应用绑定该服务,故解绑后被销毁。
- 测试添加学生和查看所有学生,观察日志均成功,远程服务调用成功。
- 退出应用,进入下图状态,说明服务的生命周期与
Activity
绑定了
异常记录
-
创建好
aidl
文件后进行编译,报以下错误
原因:下图是创建后自动创建的接口和方法,
入参类型不是AIDL支持的,需要对入参加上定向tag
:in或out或inout
。加了后会编译正常。
修改后: -
运行时找不到Book类
项目结构如下图:在RemoteService
中引用Book
类时编译报错,显示找不到这个类。
解决办法:在build.gradle
中添加以下参数,将aidl
添加为Java
文件夹。
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/aidl']
resources.srcDirs = ['src/main/java', 'src/main/aidl']
aidl.srcDirs = ['src/main/aidl']
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
}
}
- 测试的时候,点击绑定服务时,应用Crash,观察日志报如下错误
Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit
原先是通过action直接绑定。
谷歌后找到问题和解决方案:
原因:非法意图行为,Android 5.0要用显式intent去访问Service
1. intent
改成显式访问,但是是两个app应用,找不到另外一个类,故这种方法不行。
2. intent
添加setPackage
方法,将隐式调用改成显示调用。
4. 测试的时候,没有先绑定服务,直接远程调用服务查看所有书籍时,应用Crash,观察日志报以下错误
java.lang.NullPointerException: Attempt to invoke interface method 'java.util.List com.example.worktwoaidlserver.IBookManager.getBooks()' on a null object reference
错误原因:服务还未绑定,所有客户端的服务为空,直接调用导致空指针异常。
解决办法 : 使用一个boolean
变量isBound
,每次调用服务前先判断是否已经绑定。未绑定不能调用服务。
总结
-
startService
和bindService
生命周期上,只有显式调用stopSelf()
或者其他应用调用stopService()
时才会被销毁,否则一直在后台运行。而bindService
与绑定的应用生命周期相同,退出应用服务就会被解绑,当没有应用绑定到服务时,服务会被销毁(未使用startService()
方法)。 -
startService
和bindService
使用上,startService
通过onStartCommand()
方法直接在里面进行各种操作。bindService
通过重写onBind()
方法,返回一个IBinder
接口实例给客户端,然后通过这个实例得到相应的Service
,只有有了这个Service
才能进行各种操作。 -
bindService
三种实现方式的区别:
- 扩展
Binder
类:服务供自己的应用使用时,并且与客户端在相同的进程中工作,这个情况也是最常见的情况。 - 使用
Messenger
类:需要让接口跨不同的进程时使用,这也是IPC
最简单的使用方式,因为Messenger
会在单一线程中创建多个请求的队列,就无需考虑线程安全问题。 - 使用
AIDL
:AIDL
是Messenger
的底层数据结构,也是用来执行IPC
操作,一般只有需要服务同时处理多个请求时才会使用,必须要采用线程安全式设计。