引言
在RePlugin解析-startActivity流程分析一文中,详细分析了插件的加载,进程的启动以及Activity坑位的分配等内容,由于Activity不仅涉及到process和taskAffinity属性,还涉及到launchMode属性,所以startActivity流程无疑是最复杂的。然后,startService的流程和startActivity还是有些不一样的,所以考虑再三,觉得还是有必要写一篇文章专门分析一下startService的流程。
1.startService入口
普通App中的Activity调用startService()时,实际上调用的是ContextWrapper中的mBase的startService()方法,而mBase是ContextImpl对象,那么对于RePlugin中的PluginActivity,调用startService()时,还是调用ContextImpl的startService()方法吗?
回到PluginActivity的attachBaseContext()方法就知道了:
|
|
注意这里调用super.attachBaseContext()就会用newBase给ContextWrapper的mBase赋值。下面看一下newBase到底是什么吧。
RePluginInternal.createActivityContext()方法如下:
|
|
这里其实是通过反射调用Factory2的createActivityContext()方法,从RePluginInternal的initLocked()即可看出:
|
|
而Factory2.createActivityContext()方法如下:
|
|
sPLProxy是PluginLibraryInternalProxy对象,而PluginLibraryInternalProxy.createActivityContext()方法如下:
|
|
而Loader.createBaseContext()方法很简单:
|
|
显然,就是创建一个PluginContext,而这个PluginContext继承自ContextThemeWrapper, 只不过这PluginContext中的ClassLoader是PluginDexClassLoader, Resources对象是对应于插件资源的Resources对象。
所以,PluginActivity中attachBaseContext()中的newBase其实是PluginContext对象。从而在插件中startService()本质上是调用了PluginContext.startService():
|
|
其中ContextInject.startServiceBefore()和startServiceAfter()是起监听作用,这个先不管。那实际上就会走到PluginServiceClient.startService()方法,这个方法的分析在下一节。
2. 启动插件及Service
PluginServiceClient.startService()方法如下:
|
|
这个方法涉及到的东西比较多,主要有获取ComponentName, 进程id, 获取具有IPC能力的IPluginServiceServer代理,最后通过这个代理开启服务。
2.1 构造ComponentName
PluginServiceClient中的getServiceComponentFromIntent()方法如下:
|
|
显然,这里分两种情况,一种是Intent中的ComponentName不为空,那么调用PluginClientHelper.getComponentNameByContext()来获取ComponentName:
|
|
这个方法其实很简单,就是获取插件名称,然后重新创建一个ComponentName,其中ComponentName的pkg为插件名称,cls为之前的ComponentName中的cls,即真正要启动的Service的名称。
再回到PluginServiceClient中的getServiceComponentFromIntent()方法,另外一种情形是根据ComponentList来获取ServiceInfo和Service所在的插件名称,ComponentList.getServiceAndPluginByIntent()方法如下:
|
|
这个方法很简单,就是根据Intent中的action进行匹配,匹配成功的话就根据service名称从ComponentList中的mServices成员获取ServiceInfo对象。这个mServices成员是在解析插件时赋值的,包含了插件中所有的Service,关于ComponentList中成员的赋值,在之前的文章中讲过,就不再赘述了。
所以,总结起来,这一小节的内容其实就是由插件中原始的Intent来构造出含有目标Service名称,以及目标Service所在插件的插件名称的ComponentName,根据这些信息,后面就可以启动目标Service.
2.2 获取pid
PluginServiceClient中getProcessByComponentName()方法如下:
|
|
前面分析过,构造出来的ComponentName中的pkgName其实就是插件名称,所以cn.getPackageName()获取的是插件名称,而Factory.queryPluginComponentList()方法如下:
|
|
这里实际上是调用PluginCommImpl的queryPluginComponentList()方法:
|
|
这个方法很简单,就是先从缓存中取ComponentName,如果插件已经启动,那么就可以取到,否则,需要调用PmBase的loadPackageInfoPlugin()方法,而调用这个方法会启动插件:
|
|
而PmBase.loadPlugin()就涉及插件的安装与加载问题了,关于这个问题,可以看我之前的文章RePlugin解析之插件的安装与加载,这里就不再赘述了。
再回到PluginServiceClient的getProcessByComponentName()方法,获取到ComponentList后,可以从其中获取到ServiceInfo,然后根据ServiceInfo中的processName可以获取到pid,获取方法如下:
|
|
这个方法很简单,就分四种情况,前两种就不说了,第三种就是根据进程名和pid的映射来获取pid. 最后一种就是UI进程。
2.3 获取IPluginServiceServer的代理
再回到PluginServiceClient.startService()方法中,下一步就是调用PluginServiceServerFetch的fetchByProcess()方法来获取IPluginServiceServer代理,方法如下:
|
|
显然,首先是尝试从mServiceManagerByProcessMap这个缓存中取,如果缓存中取到了就直接返回;否则分两种情况,一种是如果process为IPluginManager.PROCESS_PERSIST,即为常驻进程,那么可以直接从IPluginHost中获取; 如果不是的话,就需要先创建PluginBinderInfo对象,然后调用MP.startPluginProcess()来获取IPluginClient的代理:
|
|
这里PluignProcessMain.getPluginHost()获取到了IPluginHost的代理,然后通过这个代理调用startPluignProcess()方法,最终通过IPC调用到PmHostSvc中的startPluginProcess()方法:
|
|
这里实际上是调用PmBase的startPluignProcessLocked()方法,这个方法在RePlugin解析-startActivity流程分析一文中分析过,这里不再赘述,简单地说就是通过启动相应进程中的ContentProvider的方法来启动进程并且获取可以控制对应插件进程的IPluginClient代理。
获取到IPluginClient之后,通过IPluginClient的fetchServiceServer()来获取IPluginServiceServer代理,这个IPluginServiceServer就是用来管理Service的,它的定义如下:
|
|
再回到PluginServiceClient.startService()方法中,之后就是调用IPluginServiceServer的代理,调用startService()来启动Service.
2.4 IPluginServiceServer.startService()
IPluginServiceServer.startService()会通过IPC最终调用到PluginServiceServer$Stub中的startService()方法:
|
|
而PluginServiceServer.startServiceLocked()方法如下:
|
|
首先获取ComponentName,然后调用retreveServiceLocked()方法获取ServiceRecord,方法如下:
|
|
显然,首先会从缓存中获取ServiceRecord,如果获取到则直接返回;否则从ComponentList获取ServiceInfo,然后新建ServiceRecord对象,之后将ComponentName, Intent.FilterComparison, ServiceInfo对象都放入ServiceRecord对象中。
再回到startServiceLocked()方法中,获取到ServiceRecord对象后,调用installServiceIfNeededLocked()方法:
|
|
这个方法很简单,就是在主线程中调用installServiceLocked(),并且最多等待6000ms, ThreadUtils这个封装做得不错。
而installServiceLocked()方法如下:
|
|
这个方法其实很简单,就是获取插件的PluginDexClassLoader之后,利用它加载出Service对象,然后调用attachBaseContextLocked()方法:
|
|
注释都说得很清楚了,就是通过反射调用Service的attachBaseContext()方法,并且通过反射,利用PluginContext替换掉Service中mApplication这个成员。
再回到installServiceLocked()方法中,在通过反射调用attachBaseContext()方法之后,调用Service.onCreate()方法,然后进行了一个很重要的操作:启动坑位Service.
2.5 启动坑位Service以提高进程优先级
回到installServiceLocked()方法中,首先通过getPitComponentName()来获取坑位Service的ComponentName:
|
|
可见,这里是调用根据PluginContext和pid来获取一个对应的坑位Service,而makeComponentName()方法如下:
|
|
可见,这里分三种情况,如果是UI进程,value为”PluginPitServiceUI”;如果是常驻进程,则为”PluginPitServiceGuard”;如果是其它进程,比如process的值为“-100”,则value为”PluginPitServiceP0”,那么像PluginPitServiceP0这些组件是什么时候生成并打入到Manifest文件中的,答案就在replugin-host-gradle的ComponentsGenerator中,感兴趣的同学可以看一下,其实很简单的,这里就展开来说了。
再回到installServiceLocked()方法中,假设返回的是含有”PluignPitServiceP0”的ComponentName,之后便调用startPitService()启动坑位Service:
|
|
这个方法太简单了,就是普通的startService(),没啥好说了。
2.6 startService()总结
到了这里可见:RePlugin中根本就不通过AMS来启动Service,而是直接利用PluginDexClassLoader就创建了Service对象,然后在主线程中调用Service的attachBaseContext(), onStartCommand()等方法,并且启动与这个Service在同一个进程中的坑位Service以防止进程被杀。
这里虽然也涉及坑位Service,但是却不存在先替换再取回的过程,而是直接启动坑位Service, 这也是startService()比startActivity()简单的原因。而startService()能这么做的根本原因是Service不需要直接与用户交互,没有复杂的生命周期,所以我们直接接管它的回调就能搞定。
3.思考题
Q:在插件中能否通过startService()启动其它插件的Service?
A:目前其实RePlugin技术上既不支持,思想上也不赞成这么做,因为它倡导的是各个插件相互独立,从而插件与插件之间是互相不可见的,所以一个插件不会知道另外一个插件中Service的名称或者其action.
下面说说为什么技术上不支持!
其实答案就藏在PluginServiceClient中的getServiceComponentFromIntent()中:
|
|
其实Factory.fetchPluginName()这个调用,利用当前的PluginContext来获取相应的PluginDexClassLoader对象,再根据这个ClassLoader对象来获得插件名称,也就是说除了从宿主中启动之外,它是默认要启动的Service就在当前发出请求的这个插件中!
而如果要从一个插件启动另一个插件的Service的话,这个假设就不成立了。