Android-Service
服务概括
服务是一种可在后台长期运行不需要界面的应用组件,它可以由其他组件启动,且不依赖于其他组件,此外服务可以通过绑定与其他组件进行交互,甚至执行进程间通信(IPC)。最常见的运用服务的app如音乐播放器可在后台播放音乐,不依赖于activity界面。
服务类型
1、前台:前台服务执行的是用户能注意的操作,需要显示通知。
2、后台:后台服务执行的是用户不会注意的操作,与前台服务相反。
3、绑定:绑定服务会提供客户端-服务器接口,组件与服务交互以及进程间通信都是通过绑定服务,注意:多个组件可同时绑定到一个服务,当所有绑定解绑后,服务才会被销毁。
服务的启动方式
启动service的方法有两种:startService和BindService,两种方法涉及的生命周期大致相同,具体如下图所示:
以下说明各个生命周期含义:
- onCreate:创建服务
- onBind:绑定服务
- onStart:(已过时)
- onStartCommand:启动服务,当调用startService时会调用该方法
- onUnbind:解绑服务
- onPause:暂停服务
- onDestroy:销毁服务
以上参考android官方文档地址:https://developer.android.google.cn/training/best-background
通常启动服务往往伴随着绑定服务,所以其中的生命周期复杂多样,往往启动跟绑定交叉使用,以下重点梳理下不同方式所执行的生命周期:
[图片上传失败...(image-2a58f5-1580129516189)]
以上是两种简单的启动停止Service方式,左图为startService->stopService,右图为bindService->unbindService。
往往启动跟绑定是交叉使用,如下图是交叉使用的生命周期图,一般启动服务所涉及的生命周期如图箭头所示。
android_service.png
根据上述流程图,有几点需要注意:
1、startService后执行bindService如果使用的是BIND_AUTO_CREATE参数,接下来使用stopService服务并不会停止或者解绑。
2、使用bindService(BIND_NOT_FOREGROUND)绑定服务后,服务并不会启动,只是单纯的绑定了服务。
3、使用bindService(BIND_AUTO_CREATE)绑定服务后,服务可以启动并绑定,如果调用stopService服务并不会解绑或销毁。
在调用bindService时需要传入flags参数,列举通常使用的几个参数:
1、BIND_AUTO_CREATE:只要绑定服务,就自动创建服务
2、BIND_NOT_FOREGROUND:系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行。
ServiceConnectionLeaked
该问题在使用service时常常出现,具体报错提示如下图
error.png
出现该问题的场景是:如果在activity finish前没有解绑service就会出现这个error。
解决方法是在activity 销毁前unbindService,建议在onPause/onDestroy时解绑服务,具体在哪个方法视场景而定。
这个error是怎么产生的呢?按字面理解应该是类似于内存泄露,activity销毁了但是ServiceConnection还没有解绑导致。
如下图报错提示:
serviceConnectionLeaked.png
根据报错提示,总结以下流程图
ServiceConnectionLeakedAnalyse.png
可以看出该报错是从ActivityThread.java中handleDestroyActivity导致,handleDestroyActivity方法是activity销毁的回调方法,那是什么情况导致这个error抛出呢?从removeContextRegistrations方法中看到当ArrayMap mServices中key为context的ArrayMap 不为空时就会抛出该错误,我们在bindService时,会将ServiceConnection用ArrayMap保存起来,如果activity销毁时没有解绑,就会进入该error。
开发过程中对于这个问题又出现了一种特殊情况,在startService->bindService(BIND_NOT_FOREGROUND)->stopService后,生命周期虽然在执行stopService后onUnbind->onDestroy,但是此时只要一销毁activity还是会报这个错误。如果是unbindService在ContextImpl类中会 执行mPackageInfo.forgetServiceDispatcher(),将ArrayMap中的ServiceConnection移除,但是如果是 stopService并不会操作ArrayMap,但是为什么stopService还是会出发onUnbind呢?
根据context.stopService进入到ActivityManagerService,ActivityServices,stopService最终执行bringDownServiceLocked,在该方法中会执行以下4个操作:
1、关闭所有客户端连接,通过IPC触发客户端的ServiceConnection.onServiceDisconnected
2、清理资源(ArrayList)
3、通过IPC触发服务的onUnbind生命周期方法
4、通过IPC触发服务的onDestory生命周期方法
所以这就可以说明为什么stopService会触发onUnbind生命周期了。
附上stopService时序图
stopService时序图.png
IntentService
service先研究到这里,通常后台服务常伴随着耗时操作,service通常在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行,除非你在其中创建新的子线程,所以android提供了一个新的类IntentService,它继承Service,根据其生命周期onCreate->onHandleIntent->onDestroy,onHandleIntent方法会在子线程中执行,并且是处理完自动停止,并且一次只能处理一个任务,由于IntentService继承Service,所以也支持绑定、解绑。如下是IntentService源码中onCreate所执行的代码,可以看出在onCreate中创建了一个工作线程HandlerThread,使用ServiceHandler来作为消息执行者,这里用到了HandlerThread+Handler组合带消息循环机制的异步任务处理机制。
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
备注补充知识点:
1、进程间通信(IPC)
2、activity管理-ActivityClientRecord
3、HandlerThread+Handler组合带消息循环机制的异步任务处理机制
4、整理生命周期时序图完成stopService(service)
5、ScrollView嵌套RecyclerView显示不完全。