在前两篇分析的基础上,这篇我们来看看Replugin是如何支持Service
组件的。
本篇会包含以下内容:
-
Service
启动流程 -
PluginServiceServer
子系统的设计 - Replugin自定义进程实现原理
- Replugin自定义进程启动流程
注意,本文中多处用
PSS
表示PluginServiceServer
。
Service 启动流程
在上一篇有提到插件中的Activity
以及Applicatioin
中的Context
对象被替换成了PluginContext
,并且在Android中启动Service
职能通过Context
对象的startService
和bindService
函数,因此Replugin在PluginContext
中重写了这两个函数,以调用自己设计的启动流程。
本篇我们以startService
函数为例来讲解,bindService
就不赘述了,大家有兴趣的话可以参考本篇的内容,跟一跟代码。
PluginContext.startService
中有一点值得注意的是mContextInjector
,它为用户提供了能够自定义Service
启动前后的附加操作的途径。
public ComponentName startService(Intent service) {
if (mContextInjector != null) {
mContextInjector.startServiceBefore(service);
}
try {
return PluginServiceClient.startService(this, service, true); //Replugin的启动逻辑
} catch (PluginClientHelper.ShouldCallSystem e) {
return super.startService(service); // 若打开插件出错,则直接走系统逻辑
} finally {
if (mContextInjector != null) {
mContextInjector.startServiceAfter(service);
}
}
}
PluginServiceClient.startService
首先获取ComponentName
,接着找到运行Service
的进程,然后找到PSS
的对象,它是一个IPluginServiceServer
的一个实例化对象,并用它执行启动Service
的动作。
public static ComponentName startService(Context context, Intent intent, boolean throwOnFail) {
ComponentName cn = getServiceComponentFromIntent(context, intent); //获取 ComponentName
int process = getProcessByComponentName(cn); // 获取Service运行的进程
......
intent.setComponent(cn);
IPluginServiceServer pss = sServerFetcher.fetchByProcess(process);
......
try {
return pss.startService(intent, sClientMessenger); // 开启服务
} catch (Throwable e) {
}
return null;
}
PluginServiceClient.getProcessByComponentName
最终会调用 PluginClientHelper.getProcessInt
,你可以看到Service
可以在运行在UI进程,Persistent进程以及自定义进程中。
public static Integer getProcessInt(String processName) {
if (!TextUtils.isEmpty(processName)) {
// 插件若想将组件定义成在"常驻进程"中运行,则可以在android:process中定义:
// 1. ":GuardService"。这样无论宿主的常驻进程名是什么,都会定向到"常驻进程"
String pntl = processName.toLowerCase();
String ppdntl = RePluginConstants.PERSISTENT_NAME_DEFAULT.toLowerCase();
if (pntl.contains(ppdntl)) {
return IPluginManager.PROCESS_PERSIST;
}
// 2. 和宿主常驻进程名相同,这样也会定向到"常驻进程",但若移植到其它宿主上则会出现问题
String ppntl = IPC.getPersistentProcessName().toLowerCase();
if (TextUtils.equals(pntl, ppntl)) {
return IPluginManager.PROCESS_PERSIST;
}
// 3. 自定义进程
processName = PluginProcessHost.processTail(processName.toLowerCase());
if (PROCESS_INT_MAP.containsKey(processName)) {
return PROCESS_INT_MAP.get(processName);
}
}
return IPluginManager.PROCESS_UI;
}
PluginServiceServerFetcher.fetchByProcess
用于获取PSS
对象,如果是运行在Persistent进程,用IPluginHost
来获取PSS
,如果是其他进程,则使用IPluginClient
来获取。
稍后会详细讲解
IPluginHost
和IPluginClient
,他们的用法有一点tricky。
public IPluginServiceServer fetchByProcess(int process) {
......
try {
if (process == IPluginManager.PROCESS_PERSIST) {
IPluginHost ph = PluginProcessMain.getPluginHost();
pss = ph.fetchServiceServer();
} else {
PluginBinderInfo pbi = new PluginBinderInfo(PluginBinderInfo.NONE_REQUEST);
IPluginClient pc = MP.startPluginProcess(null, process, pbi);
pss = pc.fetchServiceServer();
}
......
} catch (Throwable e) {
}
......
return pss;
}
最后一个大的步骤就是通过Binder
通信机制远程调用PSS
服务启动Service
。 PluginServiceServer.startServiceLocked
先通过retrieveServiceLocked
获取或者创建一个ServiceRecord
对象,然后用installServiceIfNeededLocked
开始运行Service
的生命周期函数
ComponentName startServiceLocked(Intent intent, Messenger client) {
intent = cloneIntentLocked(intent);
ComponentName cn = intent.getComponent();
final ServiceRecord sr = retrieveServiceLocked(intent);
......
if (!installServiceIfNeededLocked(sr)) {
return null;
}
try {
final Intent finalIntent = intent;
ThreadUtils.syncToMainThread(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return sr.service.onStartCommand(finalIntent, 0, 0);
}
}, 6000);
} catch (Throwable e) {
return null;
}
sr.startRequested = true;
mServicesByName.put(cn, sr); // 加入到列表中,统一管理
return cn;
}
PluginServiceServer.installServiceIfNeededLocked
调用installServiceLocked
函数,加载Service
类,通过反射创建Service
对象,并利用attachBaseContextLocked
函数反射调用Service.attachBaseContext
,注意这里会将插件的全局PluginContext
赋值给Service
,然后还调用了Service.onCreate
函数,最后一步将开启Service
坑位,启动Service
坑位只是为了让进程被回收的可能性降低(请参考android系统资源回收的优先级)。
private boolean installServiceLocked(ServiceRecord sr) {
Context plgc = Factory.queryPluginContext(sr.plugin);
ClassLoader cl = plgc.getClassLoader();
......
Service s;
try {
s = (Service) cl.loadClass(sr.serviceInfo.name).newInstance();
} catch (Throwable e) {
return false;
}
try {
attachBaseContextLocked(s, plgc); // 复写Context
} catch (Throwable e) {
return false;
}
s.onCreate();
sr.service = s;
startPitService(); // 开启“坑位”服务
return true;
}
Replugin只是简单的将
Service
作为普通的类在运行,手动的调用Service
的生命周期函数。当然本质上原生系统在启动Service
的的时候,在ActivityThread
类中也是这样做的,只不过原生系统用ActivityManagerService
来管理Service
,Replugin用PSS
来管理,它们都为每一个Service
产生了一个ServiceRecord
对象。在我目前对Replugin的了解程度来看,我觉得这里的处理稍微的复杂了一点,为什么一定要采取这么反直觉的方式来启动
Service
,而不是采用跟启动Activity
同样的方式呢?
PluginServiceServer
子系统的设计
在上一个节中提到的IPluginHost
,IPluginClient
和PluginManagerService
是什么关系?先来看一个并不那么标准的UML图,图中我故意简化了一下,将一些中间类省去了,所以如果你在跟代码,请知晓!
-
Replugin启动过程中,在UI进程或者自定义进程创建了一个
PluginProcessPer
对象,继承自IPluginClient
并持有一个PluginServiceServer
对象,这个PluginProcessPer
对象会在应用启动时被注册到Persistent
进程中的PluginProcessMain
中(用一个Map
类型的ALL
成员变量缓存)。当需要使用PluginServiceServer
的时候,会通过PluginServiceServerFetcher
去Persistent
进程中查找并返回之前注册的IPluginClient
对象,也就是PluginProcessPer
的实例,然后用它所持有的PluginServiceServer
对象来完成启动Service
的工作,也就是前面讲到的流程。这里注册
PluginProcessPer
的动作是通过PmBase.attach
函数完成的,也就是第一篇中讲到的UI进程启动的过程注册的。final void attach() { try { mDefaultPluginName = PluginProcessMain.getPluginHost().attachPluginProcess(IPC.getCurrentProcessName(), PluginManager.sPluginProcessIndex, mClient, mDefaultPluginName); } catch (Throwable e) { } }
PluginServiceServerFetcher
查询PSS
对象的代码这里再贴出来看看。public IPluginServiceServer fetchByProcess(int process) { ...... try { if (process == IPluginManager.PROCESS_PERSIST) { IPluginHost ph = PluginProcessMain.getPluginHost(); pss = ph.fetchServiceServer(); } else { PluginBinderInfo pbi = new PluginBinderInfo(PluginBinderInfo.NONE_REQUEST); IPluginClient pc = MP.startPluginProcess(null, process, pbi); pss = pc.fetchServiceServer(); } ...... } catch (Throwable e) { } ...... return pss; }
-
如果
Service
本身是要运行在Persistent
进程中,那么就直接使用IPluginHost
对象来启动Service
,实质上它是PmHostSvc
的实例,PmHostSvc
是IPluginHost
的实现类,并且它只存在于Persistent
进程中。PmHostSvc
的创建在PmBase.init
函数内完成,正是在Persistent进程启动并初始化是完成的。void init() { if (IPC.isPersistentProcess()) { mHostSvc = new PmHostSvc(mContext, this); PluginProcessMain.installHost(mHostSvc); ...... } else { ...... }
从上面的分析可以知道,在每一个进程里面都有一个PluginServiceServer
在运行,并且负责各自进程内部的Service
启动工作。PSS
相关的其他代码逻辑并不复杂,大家有兴趣可以去看一看代码。
Replugin自定义进程实现原理
既然提到了进程,也该讲一讲进程相关的东西了,拿到这个框架大家肯定会自然而然的想到如何启动新的进程。在Repugin中默认会启动两个进程,就是前面提到过的UI进程和Persistent进程。那如果我要启动其他的进程来运行四大组件呢?
Replugin也提供了对进程的支持, 它允许用户启动最多三个自定义进程,它们的进程名将是固定的p0
,p1
和p2
。自定义进程都是由UI进程启动起来的,UI进程或者自定义进程想要与其它进程联系,比如向其他进程发送广播,在其它进程中国启动Activity或者Service等,都要先通过常驻进程。
在
PluginProcessHost
中又对自定义进程的的定义:
/**
* 自定义插件的数量,暂时只支持3个自定义进程
*/
public static final int PROCESS_COUNT = 3;
/**
* 自定义进程,int 标识,从 -100 开始,每次加 1;
* 目前只支持 3 个进程,即到 -98
*/
public static final int PROCESS_INIT = -100;
/**
* 插件中,进程名称后缀,
* 进程名称类似 xxx.xx:p0, xxx.xx:p1, xxx.xx:p2
*/
public static final String PROCESS_PLUGIN_SUFFIX = "p";
大家知道要启动一个新的进程需要在AndroidManifest
文件中給组件加上属性anroid:process=
,但这并不需要你给出的属性值是p0,p1或者p2,你根据自己的意愿给出进程名,然后将你所给定的进程名与Replugin提供的三个进程名建立映射关系。这点是通过MetaData
来实现的,在插件的AndroidManifest
文件中你需要这样写(作为Appication
标签的字标签):
<meta-data
android:name="process_map" //用来在Application.mMetaData中找到映射关系
android:value="[{'from':'com.qihoo360.replugin.sample.demo1:bg','to':'$p1'}]"
/>
在上一篇分析中我们分析过插件Dex文件的加载过程Loader.loadDex
,在这个函数中有一个步骤叫做adjustPluginProcess
,在这个函数中,Replugin会从PackageInfo
中的ApplicationInfo.mMetaData
找到自定义进程名与Replugin提供的进程名的映射关系(以meta-data标签中的android:name
作为key)。
private void adjustPluginProcess(ApplicationInfo appInfo) throws Exception {
......
Bundle bdl = appInfo.metaData;
if (bdl == null || TextUtils.isEmpty(bdl.getString("process_map"))) {
return;
}
HashMap<String, String> processMap = new HashMap<>();
try {
String processMapStr = bdl.getString("process_map");
JSONArray ja = new JSONArray(processMapStr);
for (int i = 0; i < ja.length(); i++) {
JSONObject jo = (JSONObject) ja.get(i);
if (jo != null) {
String to = jo.getString("to").toLowerCase();
if (to.equals("$ui")) {
to = IPC.getPackageName();
} else {
if (to.contains("$" + PluginProcessHost.PROCESS_PLUGIN_SUFFIX)) {
to = PluginProcessHost.PROCESS_ADJUST_MAP.get(to);
}
}
processMap.put(jo.getString("from"), to);
}
}
} catch (JSONException e) {
e.printStackTrace();
}
if (!processMap.isEmpty()) {
doAdjust(processMap, mComponents.getActivityMap());
doAdjust(processMap, mComponents.getServiceMap());
doAdjust(processMap, mComponents.getReceiverMap());
doAdjust(processMap, mComponents.getProviderMap());
}
}
最后通过doAdjust
函数用Replugin内置的进程名替换掉用户在插件中自定义的进程名,并保存在ComponentList
中的ComponentIinfo
中。
private void doAdjust(HashMap<String, String> processMap, HashMap<String, ? extends ComponentInfo> infos) throws Exception {
for (HashMap.Entry<String, ? extends ComponentInfo> entry : infos.entrySet()) {
ComponentInfo info = entry.getValue();
if (info != null) {
String targetProcess = processMap.get(info.processName);
if (!TextUtils.isEmpty(targetProcess)) {
info.processName = targetProcess;
}
}
}
}
一切就绪以后,当然就是要启动进程啦!
Replugin自定义进程启动流程
启动流程我们从MP.startPluginProcess
开始说起,它会通过AIDL接口IPluginHost.startPluginProcess
调用Persistent
进程中的PmHostSvc.startPluginProcess
函数,最后会调用PmBase.startPluginProcessLocked
函数。
- 第一步先尝试从缓存中查找
- 缓存中未找到,先分配一个进程坑位(这个分支需要优化)
- 启动进程
- 获取
IPluginClient
对象,用于在这个进程中启动组件
final IPluginClient startPluginProcessLocked(String plugin, int process, PluginBinderInfo info) {
......
PluginProcessMain.schedulePluginProcessLoop(PluginProcessMain.CHECK_STAGE1_DELAY);
// 尝试从缓存中查找
IPluginClient client = PluginProcessMain.probePluginClient(plugin, process, info);
......
int index = IPluginManager.PROCESS_AUTO;
try {//分支A,分配进程坑位,这里的代码在2.1.7以后的版本会优化,大部分情况下 process == index
index = PluginProcessMain.allocProcess(plugin, process);
} catch (Throwable e) {
}
// 启动
boolean rc = PluginProviderStub.proxyStartPluginProcess(mContext, index);
......
// 再次获取
client = PluginProcessMain.probePluginClient(plugin, process, info);
......
return client;
}
PluginProviderStub.proxyStartPluginProcess
会使用第一篇分析中启动Persitent进程同样的方式来启动自定义进程: 尝试去访问一个ContentProvider
,如果它没有运行,则在新的进程中启动它,而这个进程刚好就是上一节中通过PackageInifo.mMetaData
匹配出来的进程(Replugin默认提供的三个自定义进程之一)。
static final boolean proxyStartPluginProcess(Context context, int index) {
ContentValues values = new ContentValues();
values.put(KEY_METHOD, METHOD_START_PROCESS);
values.put(KEY_COOKIE, PMF.sPluginMgr.mLocalCookie);
Uri uri = context.getContentResolver().insert(ProcessPitProviderBase.buildUri(index), values);
if (uri == null) {
return false;
}
return true;
}
这里之所以能准确找到PackageInifo.mMetaData
中匹配的进程,要归功于上面这段代码中的index
参数,来看看ProcessPitProviderBase.buildUri
的代码:
public static final String AUTHORITY_PREFIX = IPC.getPackageName() + ".loader.p.main";
public static final Uri buildUri(int index) {
String str = "";
if (index < 0) {
str += "N";
index *= -1;
}
str += index;
Uri uri = Uri.parse("content://" + AUTHORITY_PREFIX + str + "/main");
return uri;
}
这里的Uri最终会是这样的:content://com.qihoo360.replugin.sample.host.loader.p.mainN99/main
,中间的com.qihoo360.replugin.sample.host.loader.p.mainN99
是ContentProvider
的authority
(如果你不知道这个东西就需要去补课咯)。但是当你去AndroidManifest文件中搜索的时候,发现并没有与之匹配的ContentProvidder
被注册,这是怎么回事?想了半天没想通,反编译(使用apktool)官方Demo中的Host代码,打开AndroidManifest文件,惊喜地发现了以下这些内容(这正是我们要找的东西):
<provider
android:name="com.qihoo360.replugin.component.process.ProcessPitProviderP0"
android:authorities="com.qihoo360.replugin.sample.host.loader.p.mainN100"
android:exported="false"
android:process=":p0" />
<provider
android:name="com.qihoo360.replugin.component.process.ProcessPitProviderP1"
android:authorities="com.qihoo360.replugin.sample.host.loader.p.mainN99"
android:exported="false"
android:process=":p1" />
<provider
android:name="com.qihoo360.replugin.component.process.ProcessPitProviderP2"
android:authorities="com.qihoo360.replugin.sample.host.loader.p.mainN98"
android:exported="false"
android:process=":p2" />
查看repugin-host-plutin
源码,打开ComponentGenerator.groovy
你会看到以下这段代码,原来是通过插件写到AndroidManifest文件中的,为什么要这样做呢?因为这里的applicationID
字段是会根据你的项目的包名不同而变化的。
provider("${name}": "com.qihoo360.replugin.component.process.ProcessPitProviderP${p}",
"android:authorities": "${applicationID}.loader.p.mainN${100 - p}",
"${process}": ":p${p}",
"${exp}": "${expV}")
好了,到这里自定义进程的启动流程就讲解完毕了!
总结
这一篇内容中比较不好理解的是PluginServiceServer
子系统的设计,如果你有兴趣可以多看看代码,结合上面的讲解再体会体会!本篇的内容到此为止,下一篇Replugin 全面解析(5)我们来分析ContentProvider
和BroadcastReceiver
相关的内容!