RePlugin之Host进程启动

常驻进程

RePlugin默认开启常驻进程,所谓常驻进程就是单独启动一个进程来运行插件服务,可以在gradle中配置关闭常驻进程,那么服务会运行在UI进程。

1
2
3
4
repluginHostConfig {
/** 是否使用常驻进程? */
persistentEnable false
}

启用常驻进程下插件服务的启动

UI进程启动

创建RePluginApplication,在attachBaseContext函数中开始初始化RePlugin框架。

初始化RePlugin

通过RePlugin.App.attachBaseContext函数初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void attachBaseContext(Application app, RePluginConfig config) {
...
// 标记当前进程是UI进程还是常驻进程
IPC.init(app);
...
// 通过反射获取gradle中repluginHostConfig中的配置信息
HostConfigHelper.init();
...
// 创建PmBase对象,初始化插件管理框架,并且Hook应用的ClassLoader
PMF.init(app);
// 挂载所有插件
PMF.callAttach();

sAttached = true;
}

创建PmBase对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
PmBase(Context context) {
mContext = context;
// 添加客户端进程对应的provider和service的authorities,用来启动对应的组件
// processIndex是子进程个数,不能超过2
if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI || PluginManager.isPluginProcess()) {
String suffix;
if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI) {
suffix = "N1";
} else {
suffix = "" + PluginManager.sPluginProcessIndex;
}
// 如果是UI进程则启动ProviderN1和ServiceN1
// 如果是插件进程则启动Provider0、Provider1和Service0、Service1
mContainerProviders.add(IPC.getPackageName() + CONTAINER_PROVIDER_PART + suffix);
mContainerServices.add(IPC.getPackageName() + CONTAINER_SERVICE_PART + suffix);
}

// 4.创建Binder服务PluginClient,每个客户端都拥有一个该服务,常驻进程持有所有客户端进程该服务的代理对象,
// 常驻进程通过mClient服务的代理调用客户端的PluginProcessPer服务
mClient = new PluginProcessPer(context, this, PluginManager.sPluginProcessIndex, mContainerActivities);

//
mLocal = new PluginCommImpl(context, this);

//
mInternal = new PluginLibraryInternalProxy(this);
}
创建PluginClient服务

每个客户端都有一个PluginClient服务,该服务包含PluginServiceServer子服务和一个坑管理容器,因此每个客户端进程都有一个自己的坑容器。

1
2
3
4
5
6
7
8
9
10
11
12
PluginProcessPer(Context context, PmBase pm, int process, HashSet<String> containers) {
mContext = context;
mPluginMgr = pm;
// 在服务中又创建了子服务 PluginServiceServer
mServiceMgr = new PluginServiceServer(context);

// 创建插件容器管理,坑的分配
mACM = new PluginContainers();
// UI进程和自定义子进程初始化给定启动模式的activity对应manifest中占的坑的名称
// 也就是设置坑位
mACM.init(process, containers);
}

activity坑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 这种是UI线程对应的坑
<activity
android:theme="@ref/0x01030010"
android:name="com.qihoo360.replugin.sample.host.loader.a.ActivityN1STTS0"
android:exported="false"
android:launchMode="2"
android:screenOrientation="1"
android:configChanges="0x4b0" />

// 这种事自定义进程对应的坑
<activity
android:theme="@ref/0x01030006"
android:name="com.qihoo360.replugin.sample.host.loader.a.Activity0_singleTask1"
android:exported="false"
android:process=":loader0"
android:taskAffinity=":loader0"
android:launchMode="2"
android:screenOrientation="1"
android:configChanges="0x4b0" />
初始化PmBase

由于开启常驻进程模式,所以UI进程作为客户端,会走initForClient分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void init() {
...
if (HostConfigHelper.PERSISTENT_ENABLE) {
// (默认)“常驻进程”作为插件管理进程,则常驻进程作为Server,其余进程作为Client
if (IPC.isPersistentProcess()) {
// 初始化“Server”所做工作
initForServer();
} else {
// 连接到Server
initForClient();
}
} else {
// “UI进程”作为插件管理进程(唯一进程),则UI进程既可以作为Server也可以作为Client
if (IPC.isUIProcess()) {
// 1. 尝试初始化Server所做工作,
initForServer();

// 2. 注册该进程信息到“插件管理进程”中
// 注意:这里无需再做 initForClient,因为不需要再走一次Binder
PMF.sPluginMgr.attach();

} else {
// 其它进程?直接连接到Server即可
initForClient();
}
}

// 最新快照
PluginTable.initPlugins(mPlugins);

// 输出
if (LOG) {
for (Plugin p : mPlugins.values()) {
LogDebug.d(PLUGIN_TAG, "plugin: p=" + p.mInfo);
}
}
}

initForClient

客户端尝试连接服务端,在PluginProcessMain中拥有两个非常重要的变量,一个是sPluginHostLocal,一个是sPluginHostRemote,对于客户端进程来说,使用sPluginHostRemote,该变量是服务在客户端的代理。而对于服务端来说,使用sPluginHostLocal,该变量是真正的服务对象。

常驻进程拥有IPluginHost服务(PmBase.mHostSvc),客户端通过持有其服务代理(PluginProcessMain.sPluginHostRemote)与服务通信。
客户端拥有IPluginClient服务(PmBase.mClient),常驻进程通过持有期服务代理(ProcessClientRecord.client)与服务通信。

1
2
3
4
5
6
7
private final void initForClient() {
// 1. 先尝试连接
PluginProcessMain.connectToHostSvc();

// 2. 然后从常驻进程获取插件列表
refreshPluginsFromHostSvc();
}

连接服务端,创建常驻进程

AIDL : Stub就是Binder服务对象,在onTransact中接受客户端消息并处理,其自己也可以是客户端通过transact发送消息给自己,所以在同一进程中,客户端和服务端都是同一Binder对象。而Stub.Proxy就是客户端的服务代理类,通过内部的mRemote,也就是BinderProxy对象与Binder服务进行跨进程交互,因为BinderProxy是给客户端用的,所以其只有transact方法用来发消息,而不能接受消息。

客户端进程通过Stub.Proxy.mRemote.transact发送消息到服务端进程的Binder对象的onTransact方法中。

asInterface : 将服务Binder转换,如果是真的Binder对象,其queryLocalInterface返回的是自身,则asInterface返回的还是该对象,但是如果是BinderProxy对象,那么其queryLocalInterface返回的是空,则会对其进行包装再返回,包装成Stub.Proxy对象,该Proxy对象内部mRemote变量就是BinderProxy对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static final void connectToHostSvc() {
Context context = PMF.getApplicationContext();

// 通过provider启动常驻进程并拿到服务Binder
IBinder binder = PluginProviderStub.proxyFetchHostBinder(context);
...
sPluginHostRemote = IPluginHost.Stub.asInterface(binder);

try {
PluginManagerProxy.connectToServer(sPluginHostRemote);

// 将当前进程的"正在运行"列表和常驻做同步
// TODO 若常驻进程重启,则应在启动时发送广播,各存活着的进程调用该方法来同步
PluginManagerProxy.syncRunningPlugins();
} catch (RemoteException e) {
// 获取PluginManagerServer时出现问题,可能常驻进程突然挂掉等,当前进程自杀
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.p p.h l3a: " + e.getMessage(), e);
}
System.exit(1);
}

// 注册该进程信息到“插件管理进程”中
PMF.sPluginMgr.attach();
}

由于插件服务并没有注册在ServiceManager中,所以不能通过SM来获取,这里是通过ContentProvider来获取,并且该provider会运行在新的进程中,该进程就作为常驻进程。

此处启动的是ProcessPitProviderPersist,运行的进程名是GuardService。

1
2
3
4
5
<provider
android:name="com.qihoo360.replugin.component.process.ProcessPitProviderPersist"
android:exported="false"
android:process=":GuardService"
android:authorities="com.qihoo360.replugin.sample.host.loader.p.main" />

在query中如果该provider服务不存在,会先创建该provider以及其所在的进程,该进程创建会重新创建application,然后走一遍上面的初始化流程。

query中返回的binder是常驻进程的PmBase.mHostSvc对象,通过Parcelable将Binder对象从常驻进程传递到UI进程,在序列化时通过Parcel.readStrongBinder来创建Binder对象,如果是同一进程创建的就是Binder对象,如果在不同进程,那么创建的就是BinderProxy对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static final IBinder proxyFetchHostBinder(Context context, String selection) {
//
Cursor cursor = null;
try {
Uri uri = ProcessPitProviderPersist.URI;
cursor = context.getContentResolver().query(uri, PROJECTION_MAIN, selection, null, null);
...
// 此处拿到的是BinderProxy对象
IBinder binder = BinderCursor.getBinder(cursor);
return binder;
} finally {
CloseableUtils.closeQuietly(cursor);
}
}

常驻进程初始化,initForServer

每个进程都会创建一个自己的application对象,所以会再次触发初始化流程,常驻进程创建后,会重复走一遍上面的流程,只是在走到第5步时,会执行initServer方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private final void initForServer() {
...
// 该对象就是服务Binder,返回给UI进程的就是该Binder
mHostSvc = new PmHostSvc(mContext, this);
// 将服务赋值给mPluginHostLocal,并获取PluginManagerServer
PluginProcessMain.installHost(mHostSvc);
PluginProcessMain.schedulePluginProcessLoop(PluginProcessMain.CHECK_STAGE1_DELAY);

// 搜索所有本地插件,读取assets/plugins-builtin.json文件
mAll = new Builder.PxAll();
Builder.builder(mContext, mAll);
// 构建插件对象,保存在PmBase.mPlugins中
refreshPluginMap(mAll.getPlugins());

try {
List<PluginInfo> l = PluginManagerProxy.load();
if (l != null) {
// 将"纯APK"插件信息并入总的插件信息表中,方便查询
// 这里有可能会覆盖之前在p-n中加入的信息。本来我们就想这么干,以"纯APK"插件为准
refreshPluginMap(l);
}
} catch (RemoteException e) {
...
}
}

创建服务时,会创建两个子服务PluginServiceServer和PluginManagerService。

1
2
3
4
5
6
PmHostSvc(Context context, PmBase packm) {
mContext = context;
mPluginMgr = packm;
mServiceMgr = new PluginServiceServer(context);
mManager = new PluginManagerServer(context);
}

此时UI进程等待在provider.query方法中等待,直到常驻进程的application初始化完毕,然后进程会创建provider对象,并执行provider.query方法返回数据后才开始执行。

常驻进程-初始化本地插件列表

此时常驻进程执行完initServer后,常驻进程会继续第5步执行PluginTable.initPlugins(mPlugins),这个mPlugins就是上面搜索的本地插件对象。会将所有插件都放到PluginTable.PLUGINS这个静态Map中,key为plugin.alias,方便全局获取。到此,PmBase的初始化基本就完了。

常驻进程-Hook应用的ClassLoader

回到第2步,在初始化PmBase之后,会Hook应用,将Application.mBase.mPackageInfo.mClassLoader改成RePluginClassLoader对象。并将当前线程的contextClassLoader也改了。
此时,PMF初始化完毕,会执行PMF的callAttach方法,该方法中会拿到应用原生的ClassLoader,并将其attach到所有的插件对象上。如果此时是插件子进程,那么就会安装默认的插件。

provider.query

常驻进程初始化完毕,执行ProcessPitProviderPersist.query方法,将PmBase.mHostSvc服务对象通过Parcelable返回给UI进程。

UI进程-获取服务端代理

回到第7步,此时UI进程通过provider.query去获取服务,由于在不同进程,此时返回的是BinderProxy对象,那么在asInterface中返回的就是IPluginHost.Stub.Proxy对象了,会将该对象赋值给PluginProcessMain.sPluginHostRemote。此时UI进程拿到了常驻进程的服务代理对象。

UI进程-客户端连接服务端

在第7步中,先连接服务端,通过PluginManagerProxy.connectToServer,获取服务端中的子服务PluginManagerServer服务(PmHostSvc.mManager.mStub)。保存在PluginManagerProxy.sRemote中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
PluginManagerProxy.connectToServer(sPluginHostRemote);

public static void connectToServer(IPluginHost host) throws RemoteException {
if (sRemote != null) {
if (LogDebug.LOG) {
LogDebug.e(TAG, "connectToServer: Already connected! host=" + sRemote);
}
return;
}
// 该sRemote对象是IPluginManagerServer.Stub.Proxy对象
sRemote = host.fetchManagerServer();
}


public IPluginManagerServer fetchManagerServer() throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();

IPluginManagerServer _result;
try {
_data.writeInterfaceToken("com.qihoo360.loader2.IPluginHost");
this.mRemote.transact(29, _data, _reply, 0);
_reply.readException();
// readStrongBinder会将Binder服务序列化成BinderProxy,然后asInterface后,
返回IPluginManagerServer.Stub.Proxy对象。
_result = com.qihoo360.replugin.packages.IPluginManagerServer.Stub.asInterface(_reply.readStrongBinder());
} finally {
_reply.recycle();
_data.recycle();
}

return _result;
}

UI进程-同步插件列表

然后在第7步中,会同步当前进程加载插件列表到常驻进程服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void syncRunningPlugins() throws RemoteException {
if (sRunningSynced) {
// 已经同步过,无需再次同步,直到常驻进程挂掉
return;
}
if (!sRunningList.hasRunning()) {
// 没有正在运行的插件,无需同步。若该进程被刚刚加载时会出现此情况
return;
}
// 不判断sRemote在不在,因为本应该在sRemote获取后就马上调用
sRemote.syncRunningPlugins(sRunningList);
sRunningSynced = true;
}

private void syncRunningPluginsLocked(PluginRunningList list) {
// 复制一份List,这样无论是否为跨进程,都不会因客户端对List的修改而产生影响
PluginRunningList newList = new PluginRunningList(list);
// 所谓同步,就是将该进程的已加载插件列表维护到一个map中,key为进程名。
// mProcess2PluginsMap中维护了所有进程已加载插件列表。
mProcess2PluginsMap.put(list.mProcessName, newList);
...
}

设置UI进程Binder的死亡代理

第7步的最后,会将当前进程的信息注册到常驻进程中,并将PluginProcessPer(IPluginClient)服务传到到常驻进程中,在常驻进程中添加对该Binder的死亡代理,当客户端死亡时,通知到常驻进程中进行一系列处理。服务进程也会通过该Binder调用客户进程的服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 客户端发起attach,将当前进程的IPluginClient服务传过去
final void attach() {
//
try {
// 此处的getPluginHost拿到的就是从provider处拿到的服务,也就是sPluginHostRemote。
mDefaultPluginName = PluginProcessMain.getPluginHost().attachPluginProcess(IPC.getCurrentProcessName(), PluginManager.sPluginProcessIndex, mClient, mDefaultPluginName);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "c.n.a: " + e.getMessage(), e);
}
}
}

// 服务端接受,创建ProcessClientRecord对象未作客户端Binder的死亡代理,此处binder是BinderProxy对象,
client是IPluginClient.Stub.Proxy对象,pms是服务端的PluginManagerService子服务对象。
static final String attachProcess(int pid, String process, int index, IBinder binder, IPluginClient client, String def, PluginManagerServer pms) {
synchronized (PROCESSES) {
// 查询插件进程的默认插件,如果是UI进程,则返回'ui'
String plugin = attachProcessLocked(pid, process, index, binder, client, def);
// 构建死亡代理对象
ProcessClientRecord pr = new ProcessClientRecord(pms);
pr.name = process;
pr.plugin = plugin;
pr.pid = pid;
pr.index = index;
pr.binder = binder;
pr.client = client;
ALL.put(process, pr);
try {
// 注册死亡代理,当客户端的binder死亡时,会触发该代理的binderDied方法,然后会进行一些数据的销毁。
pr.binder.linkToDeath(pr, 0);
} catch (Throwable e) {
...
}
return plugin;
}
}

UI进程-获取服务端的插件列表

第7步连接服务端完毕之后,回到第6步,从服务端获取插件列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void refreshPluginsFromHostSvc() {
List<PluginInfo> plugins = null;
try {
// listPlugins返回的是PluginTable.PLUGINS列表的当前快照(克隆一份新的列表),
而不是PLUGINS对象。而PLUGINS对象就是在第9步中初始化的插件列表
plugins = PluginProcessMain.getPluginHost().listPlugins();
} catch (Throwable e) {
...
}

// 判断是否有需要更新的插件
// FIXME 执行此操作前,判断下当前插件的运行进程,具体可以限制仅允许该插件运行在一个进程且为自身进程中
List<PluginInfo> updatedPlugins = null;
// 获取插件json信息中是否有upinfo字段
if (isNeedToUpdate(plugins)) {
...
try {
// 调用PluginManagerService的load方法去更新
updatedPlugins = PluginManagerProxy.updateAllPlugins();
} catch (RemoteException e) {
e.printStackTrace();
}
}
// 构建插件对象,保存在客户端的PmBase.mPlugins中
if (updatedPlugins != null) {
refreshPluginMap(updatedPlugins);
} else {
refreshPluginMap(plugins);
}
}

UI进程-维护本地插件列表

此时UI进程执行完initClient方法,会继续第5步执行PluginTable.initPlugins(mPlugins),这个mPlugins就是上面从服务端拿到的插件对象。会将所有插件都放到PluginTable.PLUGINS这个静态Map中,key为plugin.alias,方便全局获取。到此,PmBase的初始化基本就完了。(此处其实就是从服务进程的PluginTable.PLUGINS中拷贝一份数据放到客户进程中的PluginTable.PLUGINS中)

UI进程-Hook应用的ClassLoader

接下来和常驻进程一样,在初始化PmBase之后,会Hook,将Application.mBase.mPackageInfo.mClassLoader改成RePluginClassLoader对象。并将当前线程的contextClassLoader也改了。此时,PMF初始化完毕,会执行PMF的callAttach方法,该方法中会拿到应用原生的ClassLoader,并将其attach到所有的插件对象上。如果此时是插件子进程,那么就会安装默认的插件。

UI进程和常驻进程都已经初始化完毕。

gradle配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class RepluginConfig {

/** 自定义进程的数量(除 UI 和 Persistent 进程) */
def countProcess = 3

/** 是否使用常驻进程? */
def persistentEnable = true

/** 常驻进程名称(也就是上面说的 Persistent 进程,开发者可自定义)*/
def persistentName = ':GuardService'

/** 背景不透明的坑的数量 */
def countNotTranslucentStandard = 6
def countNotTranslucentSingleTop = 2
def countNotTranslucentSingleTask = 3
def countNotTranslucentSingleInstance = 2

/** 背景透明的坑的数量 */
def countTranslucentStandard = 2
def countTranslucentSingleTop = 2
def countTranslucentSingleTask = 2
def countTranslucentSingleInstance = 3

/** 宿主中声明的 TaskAffinity 的组数 */
def countTask = 2

/**
* 是否使用 AppCompat 库
* com.android.support:appcompat-v7:25.2.0
*/
def useAppCompat = false

/** HOST 向下兼容的插件版本 */
def compatibleVersion = 10

/** HOST 插件版本 */
def currentVersion = 12

/** plugins-builtin.json 文件名自定义,默认是 "plugins-builtin.json" */
def builtInJsonFileName = "plugins-builtin.json"

/** 是否自动管理 plugins-builtin.json 文件,默认自动管理 */
def autoManageBuiltInJsonFile = true

/** assert目录下放置插件文件的目录自定义,默认是 assert 的 "plugins" */
def pluginDir = "plugins"

/** 插件文件的后缀自定义,默认是".jar" 暂时支持 jar 格式*/
def pluginFilePostfix = ".jar"

/** 当发现插件目录下面有不合法的插件 jar (有可能是特殊定制 jar)时是否停止构建,默认是 true */
def enablePluginFileIllegalStopBuild = true
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×