引言
在做IPC架构时,bindService()是一个绕不过的话题。基于此,本文打算深入研究bindService()的完整过程,并且尝试回答以下几个问题:
- bindService()过程系统到底做了什么?
- 使用Activity Context和Application Context进行bindService()的效果是一样的吗?
回答以上问题的目的其实是为了回答以下这个终极问题:
进行IPC通信是否一定要采用bindService(),有没有可能采用其他的方案?
bindService()过程
Client端bindService()
以Context的bindService()为例,在Context中只是个抽象方法,真正的实现是在ContextImpl中,如下:
|
|
而bindServiceCommon()方法如下:
|
|
这个方法中有以下几点值得注意:
- mPackageInfo其实是LoadedAPK对象,那么它的getServiceDispatcher()方法做了什么事呢?
- 如果getActivityToken(), flags不为BIND_AUTO_CREATE,并且targetSdkVersion小于14的话,flags会添加BIND_WAIVE_PRIORITY标志,这个标志的含义是弃权,即不影响提供Service的进程的优先级
- 调用AMP.bindService()时,会传递getActivityToken()的值,而这个值对于bindService()的结果有什么样的影响呢?
后面将逐步回答以上3个问题。
首先看一下LoadedApk的getServiceDispatcher()方法:
|
|
显然,这个方法就是首先尝试从缓存中取出ServiceDispatcher对象,如果没有取到就新建一个ServiceDispatcher对象,而ServiceConnection就是此时保存到ServiceDispatcher对象中的。
不过需要注意的是最后返回的并不是ServiceDispatcher对象,而是其中的IServiceConnection对象,其中IServiceConnection是一个IPC接口, 而IServiceConnection对象的初始化就是在ServiceDispatcher的构造方法中。这个IServiceConnection的binder会在IPC时传递到AMS中。
IServiceConnection的aidl定义如下:
|
|
显然,它是个单向方法,并且ComponentName参数也是单向传递到server端的。
再回到bindServiceCommon()方法中,接下来就是调用AMP的bindService()方法,然后通过IPC调用到AMS的bindService()方法,而AMS中的bindService()方法分析见下一小节。
AMS中bindService()
该方法如下:
|
|
注意其中的mServices是ActiveServices对象,其bindServiceLocked()方法如下:
|
|
这个方法主要做了以下事情:
- 根据传递的IApplicationThread,通过AMS.getRecordForAppLocked()方法,从LRU缓存中获取ProcessRecord对象;这里可以保证一定能从缓存中取到,因为在App调用bindService()之前,所在的进程一定已经启动,只要启动了就会在AMS中留下进程记录。
- 前面提到的Activity的token在这里用上了,如果token不为null,就会从缓存中取出ActivityRecord, 类似地,如果是从Activity中调用bindService(),由于Activity已经启动,所以这里也一定可以取到ActivityRecord对象;
- 如果是系统uid,那么会从Intent中提取出clientIntent;
- 调用retrieveServiceLocked()方法来获取ServiceLookupResult这个查询结果,这个方法会先尝试从缓存中取出ServiceRecord对象,如果没有则新建ServiceRecord并且存入缓存中,最后返回的ServiceLookupResult是ServiceRecord的包装类;
- 调用unscheduleServiceRestartLocked(),即如果要绑定的Service在重启名单中,那么就将它从AMS的mHandler中移除重启的Callback;
- 之后调用ServiceRecord的retrieveAppBindingLocked()从缓存中获取或者新建AppBindRecord对象;
- 然后新建ConnectionRecord对象,这个对象包含的信息非常多,包括AppBindRecord对象,ActivityRecord对象,IServiceConnection代理,flags, clientLabel和clientIntent;
- 之后将ConnectionRecord对象放入缓存中,这个缓存的key是IServiceConnection的binder, value是ArrayList
,这说明一个IServiceConnection可能对应多个ConnectionRecord,其实这也很好理解,因为一个client进程只有一个用于处理服务连接状态的IServiceConnection,但是一个client进程中却可能有多个连接; - 之后,如果ServiceRecord中的ProcessRecord不为空,则调用updateServiceClientActivitiesLocked()方法,这个方法主要作用就是查找client进程中是否存在activity,如果存在则更新AMS中ProcessRecord的LRU记录;
- 如果flags中含有Context.BIND_AUTO_CREATE标志,那么调用bringUpServiceLocked()将启动Service,这个过程还可能伴随启动Service所在进程(如果进程没有启动的话); 在下一节将详细分析。
Service侧进程启动以及Service启动
ActiveServices中bringUpServiceLocked()方法如下:
|
|
首先尝试从AMS.getProcessRecordLocked()方法中获取ProcessRecord,如果获取到了,说明进程已经启动,就可以调用realStartServiceLocked()方法启动Service了:
|
|
这个方法也很长,主要做了以下事情:
- 调用AMS.updateLruProcessLocked()方法更新ProcessRecord的LRU缓存;
- 调用AMS.updateOomAdjLocked()方法更新Service所在进程的oom_adj值;
- 通过ProcessRecord中的IApplicationThread的代理,通过IPC调用scheduleCreateService()方法,进行Service的创建,ApplicationThread中的调用过程以前我们分析过很多,这里就不再赘述了;
- 如果发现不需要重新创建(比如已经创建了),那么就进行一些状态维护即可
之后调用requestServiceBindingsLocked()方法,这个方法非常重要,就是在这里,把Service的onBind()与要提供的服务关联起来了。该方法如下:
12345678910private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)throws TransactionTooLargeException {for (int i=r.bindings.size()-1; i>=0; i--) {IntentBindRecord ibr = r.bindings.valueAt(i);if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {break;}}}
显然,这里是遍历ServiceRecord中的IntentBindRecord,即所有的绑定记录,然后调用requestServiceBindingLocked()方法,只要有一个执行成功即中断。而requestServiceBindingLocked()方法如下:
|
|
这个方法的重点是调用ApplicationThread的代理,通过IPC调用scheduleBindService(), 注意这里会将ServiceRecord(继承自Binder)传递过去(后面要给它赋值),而ApplicationThread中的scheduleBindService()方法如下:
|
|
这个方法很简单,就是新建了一个包装类BindServiceData对象,然后通过H传递消息到主线程,之后调用handleBindService()方法:
|
|
这里的data.token就是IPC调用传递过来的ServiceRecord对象,由于在前面已经通过IPC调用启动了Service对象,所以这里可以通过data.token从缓存中取出Service对象,然后分两种情况:
- 如果不是重连,那么就调用Service的onBind()方法获取IBinder,而获取的这个binder会在pushlishService()时传递到AMS中,之后又会通过AMS到client进程中,这也就是client中获取到Service中onBind()所返回的binder的原因。之后就进行IPC调用,最终会调用到AMS.publishService()方法,这个方法在后面会进行详细分析,这里先放一下;
- 否则回调Service的onRebind()方法,这个方法经常被我们忽视,实际上也很重要。之后通过IPC调用到AMS的serviceDoneExecuting()方法。
至此,Service侧的调用就基本结束了,主要是AMS对其的两次IPC调用,分别是IApplicationThread的scheduleCreateService()和scheduleBindService(), 前者是为了新建Service, 而后者是获取Service的onBind()所返回的IBinder对象。
继续ActiveServices.bringUpServiceLocked()方法分析
再回到ActiveServices的bringUpServiceLocked()方法中,对于还未启动或者进程已经回收的进程的情况,需要先调用AMS.startProcessLocked()方法来启动进程,并且等到进程启动完成之后再进行绑定。
那它是如何实现这点的呢?
其实很简单,就是将ServiceRecord先放到mPendingServices中,代码片段如下:
|
|
这个mPendingServices顾名思义,就是暂时挂起的服务,那它等到什么时候会用到呢?或者说要挂起到什么时候才能被使用呢?
答案就在AMS的attachApplicationLocked()方法中。
在启动Service所在进程后,会通过IPC调用到AMS的attachApplication()—>attachApplicationLocked()方法,
而在AMS的attachApplicationLocked()方法中,有如下代码片段:
|
|
注释已经写得很清楚了,就是执行到这里时,查找那些本来应该运行在这个进程的Service(由于进程没启动而暂时挂起的那些), 然后调用到ActiveServices的attachApplicationLocked()方法:
|
|
显然,在这个方法里面,会调用realStarServiceLocked()方法完成Service的创建和绑定工作,由于前面已经分析过,这里就不再赘述了。
AMS中更新oom_adj值
由于在上一步可能进行启动Service所在进程的操作,所以在这里再次对s.app是否为null进行判断,然后同样也更新了ProcessRecord的LRU记录;并且,有一个很重要的操作是调用了AMS的updateOomAdjLocked()方法更新client进程的oom_adj值,该方法如下:
12345678910111213141516171819202122
final boolean updateOomAdjLocked(ProcessRecord app) { final ActivityRecord TOP_ACT = resumedAppLocked(); final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null; final boolean wasCached = app.cached; mAdjSeq++; // This is the desired cached adjusment we want to tell it to use. // If our app is currently cached, we know it, and that is it. Otherwise, // we don't know it yet, and it needs to now be cached we will then // need to do a complete oom adj. final int cachedAdj = app.curRawAdj >= ProcessList.CACHED_APP_MIN_ADJ ? app.curRawAdj : ProcessList.UNKNOWN_ADJ; boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false, SystemClock.uptimeMillis()); if (wasCached != app.cached || app.curRawAdj == ProcessList.UNKNOWN_ADJ) { // Changed to/from cached state, so apps after it in the LRU // list may also be changed. updateOomAdjLocked(); } return success; }
注释中已经写得很清楚了,就是如果app进程(即发起bindService()的那个进程)是缓存的,那么取app进程当前的oom_adj值和ProcessList.CACHED_APP_MIN_ADJ中的较大值,然后调用updateOomAdjLocked()方法更新app进程的oom_adj值即可。该方法如下:
|
|
可见这里主要就是调用了computeOomAdjLocked()方法对于app进程的oom_adj值进行了一次计算,然后调用applyOomAdjLocked()方法将计算结果运用上去。
其中computeOomAdjLocked()方法非常重要,现在市面上所谓的进程保活措施的依据有很多就是来自于这里。这个方法的分析请见下一小节。
另外,由于AMS的updateOomAdjLocked()方法非常复杂,以至于我觉得有必要专门用一小节来分析,详情请看下下小节。
AMS.computeOomAdjLocked()
该方法如下:
|
|
吐槽一下,这个方法有600多行,只能分成以下几个部分来分析了:
1)如果当前service所在进程的oom_adj值已经被计算过了,那就无需再次计算,直接使用当前的值即可;
2)如果app.thread==null,表明这个进程在缓存中,那么返回ProcessList.CACHED_APP_MAX_ADJ值即可,而这个值的优先级并不高,因为这个数值为15;
3)如果app.maxAdj值小于等于ProcessList.FOREGROUND_APP_ADJ(数值为0),表明最大的调整都不允许Service进程达到ProcessList.FOREGROUND_APP_ADJ,那么就不值得为这个客户端进程工作,所以此时要进行调整,以使app.maxAdj的值能够达到ProcessList.FOREGROUND_APP_ADJ值;
4)根据service进程的情况来为其设置oom_adj值,分别考虑前台app进程,运行instrumentation的进程,正在接收广播的进程,正在执行service callback的进程以及空进程这5种情况。
5)如果不是前台进程,并且service所在进程的Activity数量大于0,就需要依次检查所有activity以更新ProcessRecord和foregroundActivities的状态;
6)如果adj值比ProcessList.PERCEPTIBLE_APP_ADJ(值为2)还大,则要对结果进行调整,如果是ProcessRecord是前台service进程,或者forcingToForeground不为null,则会将adj值调整到ProcessList.PERCEPTIBLE_APP_ADJ值;
7)如果service所在进程对应的ProcessRecord正在执行重量级任务,如果adj>ProcessList.HEAVY_WEIGHT_APP_ADJ值,那么会调整到ProcessList.HEAVY_WEIGHT_APP_ADJ值,这显然是一种均衡策略,不让某个太繁重的任务影响到其他进程的流畅运行;
8)如果服务进程就是mHomeProcess,那么也会依据adj值进行调整;
9)如果服务进程是前一个进程,并且它有activity的话,那么会调整到ProcessList.PREVIOUS_APP_ADJ值;
10)下面根据ProcessRecord中所有ServiceRecord的状态来更新adj和ProcessRecord的值,分以下情况:
- 如果Service是显式启动的,并且进程还拥有activity,则将其adj值设置为ProcessList.SERVICE_ADJ;
- 之后遍历每个ServiceRecord中包含的ConnectionRecord,根据ConnectionRecord的值进行adj和ProcessRecord的状态更新,此时会涉及到client端的oom_adj值;
11)最后根据ProcessRecord中所有ContentProviderRecord的状态,逐个分析每个ContentProviderRecord的状态,根据其状态进行adj值和ProcessRecord状态的调整;
12)最后将计算的adj值存放到ProcessRecord中的curRawAdj值中。并且调用ProcessRecord的modifyRawOomAdj()方法来对计算得到的原始adj值进行可能的调整,最后将结果赋值结ProcessRecord的curAdj值中。
###AMS.updateOomAdjLocked()
updateOomAdjLocked()方法如下:
|
|
吐槽一下这个400多行的方法,还好这个方法的注释写得比较全面,主要就是做了以下事情:
- 首先基于app当前状态更新每个app进程的oom_adj值,在这里面也可以看到,对于孤立的进程(即无service在其中运行的进程),AMS会有很大的概率将其杀死(原话是agressively kill these),所以如果想保活的话,要尽可能避免这种情况的出现
- 然后决定后台进程的memory trimming level
- 最后会根据uid的改变来更新oom_adj值
client端的连接回调
再回到ActiveServices中的bindServiceLocked()方法中,在更新完Service所在进程的oom_adj值之后,会通过IServiceConnection进行IPC调用,对应的代码片段为:
|
|
显然是调用IServiceConnection的connected()方法,再回到ContextImpl中,IServiceConnection的真正实现在ServiceDispatcher中的InnerConnection中:
|
|
显然,通过IPC调用之后会调用ServiceDispatcher的connected()方法,该方法如下:
|
|
到这里就很好理解了,由于回调是在binder线程中,这里通过ActivityThread中的Handler将事件发布到主线程中,其中RunConnection如下:
|
|
而doConnected()方法如下:
|
|
可以发现,这个方法看似很长,实际上主要就是做了三件事:
- 处理之前的ServiceConnection
- 为IBinder添加DeathMonitor,一旦发现binderDied()回调,就进行连接记录的修改,以及调用ServiceConnection的onDisconnected()方法
- 调用我们创建的ServiceConnection的onServiceConnected()方法。
继续AMS中的bindServiceLocked()方法
再回到AMS的bindServiceLocked()方法,最后面的一个代码片段如下:
|
|
这段代码非常有意思,即有两种情形,一种是某个进程重连到远程Service时,会调用requestServiceBindingLocked()方法:
|
|
忽略其它细节,注意r.app.thread.scheduleBindService()这个调用,这其实是会IPC调用到Service所在进程的ApplicationThread的scheduleBindService()方法,之后通过Handler在主线程调用handleBindService()方法:
|
|
在这里可以看到分两种情况,如果不是重新连接,调用AMP的publishService()方法;否则调用AMP的serviceDoneExecuting()方法,这个方法其实是一个IPC调用,告诉AMS重新连接上了,然后AMS就会更新它的ServiceRecord和相应进程的oom_adj值。下一小节我们将分析AMS的publishService()方法。
AMS.publishService()
publishService()方法如下:
|
|
类似的,调用到ActiveServices的publishServiceLocked()方法:
|
|
这个方法主要做了两件事:
- 给IntentBindRecord赋值,其中的binder就是Service中onBind()所返回的binder;
- 这里会先判断b.received是否为false,如果为false说明发起绑定流程的进程还没收到绑定回调,从而此时会再次通过IPC调用到InnerConnection的connected()方法。
最后再来看一下serviceDoneExecutingLocked()方法,这个方法也很重要:
|
|
整个方法中,对我们来说最重要的就是mAm.updateOomAdjLocked(r.app);这个方法了,因为它涉及到更新Service所在进程的oom_adj值,而oom_adj值直接关系到内存回收。由于AMS.updateOomAdjLocked()方法我们在前面已经分析过了,这里就不再赘述了。
总结
通过前面的分析,可以知道bindService()其实主要做了以下事情:
- 在client侧,ServiceConnection对象会保存在ServiceDispatcher对象中,并且利用IServiceConnection进行IPC
- AMS会根据bindService中的Intent信息,会对Service侧进行两次IPC调用,分别是IApplicationThread的scheduleCreateService()和scheduleBindService(), 前者是为了新建Service, 而后者是获取Service的onBind()所返回的IBinder对象
- 之后AMS会根据client端和Service本身的的情况来调整Service所在进程的oom_adj值
- 最后通过IServiceConnection的proxy进行IPC,最终调用到client端的ServiceConnection的onServiceConnected()方法
用图片展示整个流程的关键节点如下: