RePlugin解析(-):startActivity流程分析

1.编译时代码替换

所有plugin中的Activity子类都会在编译时由gradle插件进行替换,即将继承自Activity或Activity子类(如AppCompatActivity)的类改为继承PluginActivity和PluginAppCompatActivity等,这样做的原因在于:RePlugin既不想Hook AMP,Instrumentation等,又想达到动态加载未在Manifest中声明的组件的目的,那么就只能在编译期做更多的事情,即通过gradle插件来修改plugin中的代码(特别是组件的代码),比如这里所有本来继承自Activity或AppCompatActivity改为继承PluginActivity和PluginAppCompatActivity,而PluginActivity虽然也是继承自Activity,但是其内部的方法已经重写了,代码如下(在replugin-plugin-library中):

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
* 插件内的BaseActivity,建议插件内所有的Activity都要继承此类
* 此类通过override方式来完成插件框架的相关工作
*
* @author RePlugin Team
*/
public abstract class PluginActivity extends Activity {
@Override
protected void attachBaseContext(Context newBase) {
newBase = RePluginInternal.createActivityContext(this, newBase);
super.attachBaseContext(newBase);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
//
RePluginInternal.handleActivityCreateBefore(this, savedInstanceState);
super.onCreate(savedInstanceState);
//
RePluginInternal.handleActivityCreate(this, savedInstanceState);
}
@Override
protected void onDestroy() {
//
RePluginInternal.handleActivityDestroy(this);
super.onDestroy();
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
//
RePluginInternal.handleRestoreInstanceState(this, savedInstanceState);
try {
super.onRestoreInstanceState(savedInstanceState);
} catch (Throwable e) {
// Added by Jiongxuan Zhang
// Crash Hash: B1F67129BC6A67C882AF2BBE62202BF0
// java.lang.IllegalArgumentException: Wrong state class异常
// 原因:恢复现场时,Activity坑位找错了。通常是用于占坑的Activity的层级过深导致
// 举例:假如我们只有一个坑位可用,A和B分别是清理和通讯录的两个Activity
// 如果进程重启,系统原本恢复B,却走到了A,从而出现此问题
// 解决:将其Catch住,这样系统在找ViewState时不会出错。
// 后遗症:
// 1、可能无法恢复系统级View的保存的状态;
// 2、如果自己代码处理不当,可能会出现异常。故自己代码一定要用SecExtraUtils来获取Bundle数据
if (LogRelease.LOGR) {
LogRelease.e("PluginActivity", "o r i s: p=" + getPackageCodePath() + "; " + e.getMessage(), e);
}
}
}
@Override
public void startActivity(Intent intent) {
//
if (RePluginInternal.startActivity(this, intent)) {
// 这个地方不需要回调startActivityAfter,因为Factory2最终还是会回调回来,最终还是要走super.startActivity()
return;
}
super.startActivity(intent);
}
@Override
public void startActivityForResult(Intent intent, int requestCode) {
//
if (RePluginInternal.startActivityForResult(this, intent, requestCode)) {
// 这个地方不需要回调startActivityAfter,因为Factory2最终还是会回调回来,最终还是要走super.startActivityForResult()
return;
}
super.startActivityForResult(intent, requestCode);
}
}

显然,这样做的结果是之前插件中Activity代码中的startActivity和startActivityForResult()方法都修改为RePluginInternal.startActivity()和RePluginInternal.startActivityForResult()方法,而RePluginInternal其实只是做了封装,最终通过反射调用到replugin-host-library的startActivity()中:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/*
* Copyright (C) 2005-2017 Qihoo 360 Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed To in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.qihoo360.i;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.qihoo360.loader2.PluginLibraryInternalProxy;
import org.json.JSONArray;
/**
* plugin-library中,通过“反射”调用的内部逻辑(如PluginActivity类的调用等)均在此处 <p>
* 注意:务必要Keep住此类,否则插件调用将失败
*
* @author RePlugin Team
*/
public final class Factory2 {
/**
* @hide 内部框架使用
*/
public static PluginLibraryInternalProxy sPLProxy;
/**
* @hide 内部方法,插件框架使用
* 插件的Activity创建成功后通过此方法获取其base context
* @param activity
* @param newBase
* @return 为Activity构造一个base Context
*/
public static final Context createActivityContext(Activity activity, Context newBase) {
return sPLProxy.createActivityContext(activity, newBase);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onCreate调用前调用此方法
* @param activity
* @param savedInstanceState
*/
public static final void handleActivityCreateBefore(Activity activity, Bundle savedInstanceState) {
sPLProxy.handleActivityCreateBefore(activity, savedInstanceState);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onCreate调用后调用此方法
* @param activity
* @param savedInstanceState
*/
public static final void handleActivityCreate(Activity activity, Bundle savedInstanceState) {
sPLProxy.handleActivityCreate(activity, savedInstanceState);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onDestroy调用后调用此方法
* @param activity
*/
public static final void handleActivityDestroy(Activity activity) {
sPLProxy.handleActivityDestroy(activity);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Activity的onRestoreInstanceState调用后调用此方法
* @param activity
* @param savedInstanceState
*/
public static final void handleRestoreInstanceState(Activity activity, Bundle savedInstanceState) {
sPLProxy.handleRestoreInstanceState(activity, savedInstanceState);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Service的onCreate调用后调用此方法
* @param service
*/
public static final void handleServiceCreate(Service service) {
sPLProxy.handleServiceCreate(service);
}
/**
* @hide 内部方法,插件框架使用
* 插件的Service的onDestroy调用后调用此方法
* @param service
*/
public static final void handleServiceDestroy(Service service) {
sPLProxy.handleServiceDestroy(service);
}
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity
* 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制
* @param context Context上下文
* @param intent
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public static final boolean startActivity(Context context, Intent intent) {
return sPLProxy.startActivity(context, intent);
}
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity
* 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制
* @param activity Activity上下文
* @param intent
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public static final boolean startActivity(Activity activity, Intent intent) {
return sPLProxy.startActivity(activity, intent);
}
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity,如果插件不存在会触发下载界面
* @param context 应用上下文或者Activity上下文
* @param intent
* @param plugin 插件名
* @param activity 待启动的activity类名
* @param process 是否在指定进程中启动
* @param download 下载
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public static final boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
return sPLProxy.startActivity(context, intent, plugin, activity, process, download);
}
/**
* 通过 forResult 方式启动一个插件的 Activity
*
* @param activity 源 Activity
* @param intent 要打开 Activity 的 Intent,其中 ComponentName 的 Key 必须为插件名
* @param requestCode 请求码
* @param options 附加的数据
* @see #startActivityForResult(Activity, Intent, int, Bundle)
*/
public static final boolean startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) {
return sPLProxy.startActivityForResult(activity, intent, requestCode, options);
}
/**
* @hide 内部方法,插件框架使用
* 返回所有插件的json串,格式见plugins-builtin.json文件
* @param name 插件名,传null或者空串表示获取全部
* @return
*/
public static final JSONArray fetchPlugins(String name) {
return sPLProxy.fetchPlugins(name);
}
/**
* @hide 内部方法,插件框架使用
* 登记动态映射的类
* @param className 壳类名
* @param plugin 目标插件名
* @param type 目标类的类型: activity, service, provider
* @param target 目标类名
* @return
*/
public static final boolean registerDynamicClass(String className, String plugin, String type, String target) {
return sPLProxy.registerDynamicClass(className, plugin, type, target);
}
/**
* @hide 内部方法,插件框架使用
* 登记动态映射的类
* @param className 壳类名
* @param plugin 目标插件名
* @param target 目标类名
* @return
*/
public static final boolean registerDynamicClass(String className, String plugin, String target, Class defClass) {
return sPLProxy.registerDynamicClass(className, plugin, target, defClass);
}
/**
* @hide 内部方法,插件框架使用
* 查询动态映射的类
* @param className 壳类名
* @param plugin 目标插件名
* @return
*/
public static final boolean isDynamicClass(String plugin, String className) {
return sPLProxy.isDynamicClass(plugin, className);
}
public static void unregisterDynamicClass(String source) {
sPLProxy.unregisterDynamicClass(source);
}
/**
* @hide 内部方法,插件框架调用
* 根据动态注册的类,反查此类对应的插件名称
*
* @param className 动态类名称
* @return 插件名称
*/
public static final String getPluginByDynamicClass(String className) {
return sPLProxy.getPluginByDynamicClass(className);
}
}

而Factory2其实仍然还是起一个封装的作用,实际调用的是PluginLibraryInternalProxy中的startActivity()方法:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity
* 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制
* @param context Context上下文
* @param intent
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public boolean startActivity(Context context, Intent intent) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: intent=" + intent);
}
// 兼容模式,直接使用标准方式启动
if (intent.getBooleanExtra(IPluginManager.KEY_COMPATIBLE, false)) {
PmBase.cleanIntentPluginParams(intent);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: COMPATIBLE is true, direct start");
}
return false;
}
// 获取Activity的名字,有两种途径:
// 1. 从Intent里取。通常是明确知道要打开的插件的Activity时会用
// 2. 从Intent的ComponentName中获取
String name = intent.getStringExtra(IPluginManager.KEY_ACTIVITY);
if (TextUtils.isEmpty(name)) {
ComponentName cn = intent.getComponent();
if (cn != null) {
name = cn.getClassName();
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: custom context=" + context);
}
}
}
// 已经是标准坑了(例如N1ST1这样的),则无需再过“坑位分配”逻辑,直接使用标准方式启动
if (mPluginMgr.isActivity(name)) {
PmBase.cleanIntentPluginParams(intent);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: context is container, direct start");
}
return false;
}
// 获取插件名,有三种途径:
// 1. 从Intent里取。通常是明确知道要打开的插件时会用
// 2. 根据当前Activity的坑位名来“反查”其插件名。通常是插件内开启自己的Activity时用到
// 3. 通过获得Context的类加载器来判断其插件名
String plugin = intent.getStringExtra(IPluginManager.KEY_PLUGIN);
/* 检查是否是动态注册的类 */
// 如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。
// 原因:宿主的某些动态注册的类不能运行在坑位中(如'桌面'插件的入口Activity)
ComponentName componentName = intent.getComponent();
if (componentName != null) {
if (LogDebug.LOG) {
LogDebug.d("loadClass", "isHookingClass(" + plugin + "," + componentName.getClassName() + ") = "
+ isDynamicClass(plugin, componentName.getClassName()));
}
if (isDynamicClass(plugin, componentName.getClassName())) {
intent.putExtra(IPluginManager.KEY_COMPATIBLE, true);
intent.setComponent(new ComponentName(IPC.getPackageName(), componentName.getClassName()));
context.startActivity(intent);
return false;
}
}
if (TextUtils.isEmpty(plugin)) {
// 看下Context是否为Activity,如是则直接从坑位中获取插件名(最准确)
if (context instanceof Activity) {
plugin = fetchPluginByPitActivity((Activity) context);
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: custom plugin is empty, query plugin=" + plugin);
}
}
// 没拿到插件名?再从 ClassLoader 获取插件名称(兜底)
if (TextUtils.isEmpty(plugin)) {
plugin = RePlugin.fetchPluginNameByClassLoader(context.getClassLoader());
}
// 仍然拿不到插件名?(例如从宿主中调用),则打开的Activity可能是宿主的。直接使用标准方式启动
if (TextUtils.isEmpty(plugin)) {
PmBase.cleanIntentPluginParams(intent);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: plugin and context is empty, direct start");
}
return false;
}
// 获取进程值,看目标Activity要打开哪个进程
int process = intent.getIntExtra(IPluginManager.KEY_PROCESS, Integer.MIN_VALUE);
PmBase.cleanIntentPluginParams(intent);
// 调用“特殊版”的startActivity,不让自动填写ComponentName,防止外界再用时出错
return Factory.startActivityWithNoInjectCN(context, intent, plugin, name, process);
}

在分析这个方法之前,我们先分析一下除了Activity.startActivity()之外的另外一种startActivity()的调用,即Context.startActivity()这个调用,那我们将这个调用也替换掉呢?

其实很简单,不需要改动这部分代码,只需要在插件初始化时将其Context替换为特制的Context,即PluginContext:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public void startActivity(Intent intent) {
// HINT 只有插件Application才会走这里
// 而Activity.startActivity系统最终会走startActivityForResult,不会走这儿
// 这里会被调用两次:
// 第一次:获取各种信息,最终确认坑位,并走startActivity,再次回到这里
// 第二次:判断要打开的是“坑位Activity”,则返回False,直接走super,后面的事情你们都懂的
// 当然,如果在获取坑位信息时遇到任何情况(例如要打开的是宿主的Activity),则直接返回false,走super
if (!Factory2.startActivity(this, intent)) {
if (mContextInjector != null) {
mContextInjector.startActivityBefore(intent);
}
super.startActivity(intent);
if (mContextInjector != null) {
mContextInjector.startActivityAfter(intent);
}
}
}

可以看到,PluginContext中的startActivity()方法其实也是调用 Factory2.startActivity(),到这里我们就把插件中所有startActivity()的调用都统一到Factory2.startActivity()的调用了,而至于插件中上下文的替换,是另外一个很大的话题,后面会专门写一篇文章来阐述。

2.PluginLibraryInternalProxy

2.1 首次进入插件时的startActivity()调用

此时涉及到坑位的分配,情况比较复杂。

从外部进入一个插件,通过调用RePlugin.startActivity(Context,Intent)实现,这个调用流程如下:

下面就逐步来分析。

2.1.1 RePlugin.startActivity(Context,Intent)

在看这个方法之前,先看一下这个Intent是如何来的,它跟一般的startActivity中的Intent不一样,它是特殊构造的,如下是一个调用示例:

1
RePlugin.startActivity(MainActivity.this,RePlugin.createIntent("demo1","com.qihoo360.replugin.sample.demo1.MainActivity"));

而RePlugin.createIntent()方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 创建一个用来定向到插件组件的Intent <p>
* <p>
* 推荐用法: <p>
* <code>
* Intent in = RePlugin.createIntent("clean", "com.qihoo360.mobilesafe.clean.CleanActivity");
* </code> <p>
* 当然,也可以用标准的Android创建方法: <p>
* <code>
* Intent in = new Intent(); <p>
* in.setComponent(new ComponentName("clean", "com.qihoo360.mobilesafe.clean.CleanActivity"));
* </code>
*
* @param pluginName 插件名
* @param cls 目标全名
* @return 可以被RePlugin识别的Intent
* @since 1.0.0
*/
public static Intent createIntent(String pluginName, String cls) {
Intent in = new Intent();
in.setComponent(createComponentName(pluginName, cls));
return in;
}

显然,这个方法的作用是创建一个定向到插件中组件的Intent,所以”demo1”是插件名称,而”com.qihoo360.replugin.sample.demo1.MainActivity”是组件名。

下面看RePlugin.startActivity(Context,Intent)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 开启一个插件的Activity <p>
* 其中Intent的ComponentName的Key应为插件名(而不是包名),可使用createIntent方法来创建Intent对象
*
* @param context Context对象
* @param intent 要打开Activity的Intent,其中ComponentName的Key必须为插件名
* @return 插件Activity是否被成功打开?
* FIXME 是否需要Exception来做?
* @see #createIntent(String, String)
* @since 1.0.0
*/
public static boolean startActivity(Context context, Intent intent) {
// TODO 先用旧的开启Activity方案,以后再优化
ComponentName cn = intent.getComponent();
if (cn == null) {
// TODO 需要支持Action方案
return false;
}
String plugin = cn.getPackageName();
String cls = cn.getClassName();
return Factory.startActivityWithNoInjectCN(context, intent, plugin, cls, IPluginManager.PROCESS_AUTO);
}

其实注释已经写得很完整了,就是开启一个插件的Activity,而plugin和cls刚刚在前面已经赋值,分别为”demo1”和”com.qihoo360.replugin.sample.demo1.MainActivity”. 其中IPluginManager.PROCESS_AUTO表示自动分配插件进程。

2.1.2 Factory.startActivityWithNoInjectCN(Context, Intent, String, String, int)

该方法代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 内部接口,仅为Factory2.startActivity(context, intent) 和 RePlugin.startActivity方法而使用
*
* @param context 应用上下文或者Activity上下文
* @param intent Intent对象
* @param plugin 插件名
* @param activity 待启动的activity类名
* @param process 是否在指定进程中启动
* @return 插件机制层,是否成功,例如没有插件存在、没有合适的Activity坑
* Added by Jiongxuan Zhang
*/
public static final boolean startActivityWithNoInjectCN(Context context, Intent intent, String plugin, String activity, int process) {
boolean result = sPluginManager.startActivity(context, intent, plugin, activity, process);
RePlugin.getConfig().getEventCallbacks().onStartActivityCompleted(plugin, activity, result);
return result;
}

显然,该方法仅仅是起一个转发以及回调的作用。没什么好分析的,PluginCommImpl.startActivity(Context, Intent, String, String, int)类似,下面就直接进入PluginLibraryInternalProxy.startActivity()的分析。

####2.1.3 PluginLibraryInternalProxy.startActivity(Context, Intent, String, String, int, boolean)

该方法如下:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// FIXME 建议去掉plugin和activity参数,直接用intent代替
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity,如果插件不存在会触发下载界面
* @param context 应用上下文或者Activity上下文
* @param intent
* @param plugin 插件名
* @param activity 待启动的activity类名
* @param process 是否在指定进程中启动
* @param download 下载
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start activity: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process + " download=" + download);
}
// 是否启动下载
// 若插件不可用(不存在或版本不匹配),则直接弹出“下载插件”对话框
// 因为已经打开UpdateActivity,故在这里返回True,告诉外界已经打开,无需处理
if (download) {
if (PluginTable.getPluginInfo(plugin) == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "plugin=" + plugin + " not found, start download ...");
}
// 如果用户在下载即将完成时突然点按“取消”,则有可能出现插件已下载成功,但没有及时加载进来的情况
// 因此我们会判断这种情况,如果是,则重新加载一次即可,反之则提示用户下载
// 原因:“取消”会触发Task.release方法,最终调用mDownloadTask.destroy,导致“下载服务”的Receiver被注销,即使文件下载了也没有回调回来
// NOTE isNeedToDownload方法会调用pluginDownloaded再次尝试加载
if (isNeedToDownload(context, plugin)) {
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}
}
}
/* 检查是否是动态注册的类 */
// 如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。
// 原因:宿主的某些动态注册的类不能运行在坑位中(如'桌面'插件的入口Activity)
if (LOG) {
LogDebug.d("loadClass", "isHookingClass(" + plugin + " , " + activity + ") = "
+ Factory2.isDynamicClass(plugin, activity));
}
if (Factory2.isDynamicClass(plugin, activity)) {
intent.putExtra(IPluginManager.KEY_COMPATIBLE, true);
intent.setComponent(new ComponentName(IPC.getPackageName(), activity));
context.startActivity(intent);
return true;
}
// 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)
// Added by Jiongxuan Zhang
if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PluginLibraryInternalProxy.startActivity(): Plugin Disabled. pn=" + plugin);
}
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}
// 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”
// Added by Jiongxuan Zhang
if (!RePlugin.isPluginDexExtracted(plugin)) {
PluginDesc pd = PluginDesc.get(plugin);
if (pd != null && pd.isLarge()) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PM.startActivity(): Large Plugin! p=" + plugin);
}
return RePlugin.getConfig().getCallbacks().onLoadLargePluginForActivity(context, plugin, intent, process);
}
}
// WARNING:千万不要修改intent内容,尤其不要修改其ComponentName
// 因为一旦分配坑位有误(或压根不是插件Activity),则外界还需要原封不动的startActivity到系统中
// 可防止出现“本来要打开宿主,结果被改成插件”,进而无法打开宿主Activity的问题
// 缓存打开前的Intent对象,里面将包括Action等内容
Intent from = new Intent(intent);
// 帮助填写打开前的Intent的ComponentName信息(如有。没有的情况如直接通过Action打开等)
if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {
from.setComponent(new ComponentName(plugin, activity));
}
ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
if (cn == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "plugin cn not found: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process);
}
return false;
}
// 将Intent指向到“坑位”。这样:
// from:插件原Intent
// to:坑位Intent
intent.setComponent(cn);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start activity: real intent=" + intent);
}
context.startActivity(intent);
// 通知外界,已准备好要打开Activity了
// 其中:from为要打开的插件的Intent,to为坑位Intent
RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);
return true;
}

这个方法是最长的一个,主要分如下几个部分来分析:

1)如果插件还未下载,则触发下载回调,让用户决定是否下载;

2)检查这个activity类是否为动态类,这里这个类并不是,所以进入下一个判断;

3)如果插件状态出现问题,则也弹出插件不存在的回调,让用户决定下一步如何做;

4)调用RePlugin.isPluginDexExtracted()来判断当前插件是否已经释放了Dex,Native库等,如果没有,则要提示用户正在加载中;

5)下面调用PluginCommImpl.loadPluginActivity(Intent, String, String, int)的方法,这个方法里面涉及到坑位的分配,这是整个RePlugin中非常核心的一部分,内容也非常多,所以单独在下个小节进行分析。

3.Activity坑位的分配–PluginCommImpl分析

PluginCommImpl.loadPluginActivity(Intent, String, String, int)方法如下:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* 加载插件Activity,在startActivity之前调用
* @param intent
* @param plugin 插件名
* @param target 目标Service名,如果传null,则取获取到的第一个
* @param process 是否在指定进程中启动
* @return
*/
public ComponentName loadPluginActivity(Intent intent, String plugin, String activity, int process) {
ActivityInfo ai = null;
String container = null;
PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.ACTIVITY_REQUEST);
try {
// 获取 ActivityInfo(可能是其它插件的 Activity,所以这里使用 pair 将 pluginName 也返回)
ai = getActivityInfo(plugin, activity, intent);
if (ai == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: bindActivity: activity not found");
}
return null;
}
// 存储此 Activity 在插件 Manifest 中声明主题到 Intent
intent.putExtra(INTENT_KEY_THEME_ID, ai.theme);
if (LOG) {
LogDebug.d("theme", String.format("intent.putExtra(%s, %s);", ai.name, ai.theme));
}
// 根据 activity 的 processName,选择进程 ID 标识
if (ai.processName != null) {
process = PluginClientHelper.getProcessInt(ai.processName);
}
// 容器选择(启动目标进程)
IPluginClient client = MP.startPluginProcess(plugin, process, info);
if (client == null) {
return null;
}
// 远程分配坑位
container = client.allocActivityContainer(plugin, process, ai.name, intent);
if (LOG) {
LogDebug.i(PLUGIN_TAG, "alloc success: container=" + container + " plugin=" + plugin + " activity=" + activity);
}
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "l.p.a spp|aac: " + e.getMessage(), e);
}
}
// 分配失败
if (TextUtils.isEmpty(container)) {
return null;
}
PmBase.cleanIntentPluginParams(intent);
PluginIntent ii = new PluginIntent(intent);
ii.setPlugin(plugin);
ii.setActivity(ai.name);
ii.setProcess(IPluginManager.PROCESS_AUTO);
ii.setContainer(container);
ii.setCounter(0);
return new ComponentName(IPC.getPackageName(), container);
}

这个方法主要分为如下几个部分。

3.1 Activity信息的获取

PluginCommImpl.getActivityInfo(String,String,Intent)

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
/**
* 根据条件,查找 ActivityInfo 对象
*
* @param plugin 插件名称
* @param activity Activity 名称
* @param intent 调用者传递过来的 Intent
* @return 插件中 Activity 的 ActivityInfo
*/
public ActivityInfo getActivityInfo(String plugin, String activity, Intent intent) {
// 获取插件对象
Plugin p = mPluginMgr.loadAppPlugin(plugin);
if (p == null) {
...
return null;
}
ActivityInfo ai = null;
// activity 不为空时,从插件声明的 Activity 集合中查找
if (!TextUtils.isEmpty(activity)) {
ai = p.mLoader.mComponents.getActivity(activity);
} else {
// activity 为空时,根据 Intent 匹配
ai = IntentMatcherHelper.getActivityInfo(mContext, plugin, intent);
}
return ai;
}

这个方法的逻辑很简单,就是先加载插件,然后从插件的组件信息中获取ActivityInfo对象。可见,重点在于PmBase.loadAppPlugin(String)的调用, 从这个方法开始,涉及到插件的加载和安装的问题,请看RePlugin解析之插件的安装与加载

在插件安装完成并加载到内存之后,这里分两种情况:

1)activity信息不为空,调用ComponentList.getActivity()方法来获取ActivityInfo,如果看了请看RePlugin解析之插件的安装与加载这篇文章的话,就会知道ComponentList中含有插件中所有四大组件的信息,所以要从ComponentList中获取;

2)activity信息为空,那自然就要根据intent-filter信息来进行匹配了。其中IntentMatchHelper.getActivityInfo()方法如下:

1
2
3
4
5
6
7
8
public static ActivityInfo getActivityInfo(Context context, String plugin, Intent intent) {
if (plugin == null) {
return null;
}
String activity = doMatchIntent(context, intent, ManifestParser.INS.getActivityFilterMap(plugin));
return Factory.queryActivityInfo(plugin, activity);
}

其中doMatchIntent()方法如下:

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
public static String doMatchIntent(Context context, Intent intent, Map<String, List<IntentFilter>> filtersMap) {
if (filtersMap == null) {
return null;
}
final String action = intent.getAction();
final String type = intent.resolveTypeIfNeeded(context.getContentResolver());
final Uri data = intent.getData();
final String scheme = intent.getScheme();
final Set<String> categories = intent.getCategories();
for (Map.Entry<String, List<IntentFilter>> entry : filtersMap.entrySet()) {
String pluginName = entry.getKey();
List<IntentFilter> filters = entry.getValue();
if (filters == null) {
continue;
}
for (IntentFilter filter : filters) {
int match = filter.match(action, type, scheme, data, categories, "ComponentList");
if (match >= 0) {
...
return pluginName;
} else {
if (LOG) {
String reason;
switch (match) {
case IntentFilter.NO_MATCH_ACTION:
reason = "action";
break;
case IntentFilter.NO_MATCH_CATEGORY:
reason = "category";
break;
case IntentFilter.NO_MATCH_DATA:
reason = "data";
break;
case IntentFilter.NO_MATCH_TYPE:
reason = "type";
break;
default:
reason = "unknown reason";
break;
}
LogDebug.d(TAG, " Filter did not match: " + reason);
}
}
}
}
return "";
}

注意传递到doMatchIntent中的第有一个参数为ManifestParser.INS.getActivityFilterMap(),而在RePlugin解析之插件的安装与加载一文中已经分析过,ManifestParser这个类就是解析插件的Manifest文件,并且会收集所有组件的intent-filter信息,其中getActivityFilterMap()返回的就是对应插件中所有Activity的intent-filter信息。

之后首先解析出Intent的action, type, data, scheme, categories, 然后调用IntentFilter.match()方法进行匹配,如果匹配成功则返回组件名称。

这里值得吐槽的有两点:

  • 命名极不规范,pluginName其实应该命名为pluginComponentName,即插件中组件的名称
  • 匹配效率太低,目前这样是要逐一遍历插件中所有对应组件(比如如果是Activity的话就要遍历所有Activity的intent-filter)的intent-filter信息,如果组件多的话效率会有点低

3.2 分配pid

再回到PluginCommImpl.loadPluginActivity()方法中,在获取到ActivityInfo之后,主要有如下语句:

1
2
3
4
5
6
7
// 存储此 Activity 在插件 Manifest 中声明主题到 Intent
intent.putExtra(INTENT_KEY_THEME_ID, ai.theme);
...
// 根据 activity 的 processName,选择进程 ID 标识,processName类似"com.qihoo360.replugin.sample.demo1"
if (ai.processName != null) {
process = PluginClientHelper.getProcessInt(ai.processName); //process一般为-1,-1代表PROCESS_UI,即UI进程
}

第一句是将ActivityInfo中的theme放入到Intent中,第二句是调用PluginClientHelper.getProcessInt()获取进程id。这里有两个地方需要解决,首先是ActivityInfo的processName是什么时候赋值的呢?

其实就是在ComponentList的构造方法中:

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
54
public ComponentList(PackageInfo pi, String path, PluginInfo pli) {
if (pi.activities != null) {
for (ActivityInfo ai : pi.activities) {
...
ai.applicationInfo.sourceDir = path;
// todo extract to function
if (ai.processName == null) {
ai.processName = ai.applicationInfo.processName;
}
if (ai.processName == null) {
ai.processName = ai.packageName;
}
mActivities.put(ai.name, ai);
}
}
if (pi.providers != null) {
for (ProviderInfo ppi : pi.providers) {
...
if (ppi.processName == null) {
ppi.processName = ppi.applicationInfo.processName;
}
if (ppi.processName == null) {
ppi.processName = ppi.packageName;
}
mProvidersByName.put(ppi.name, ppi);
mProvidersByAuthority.put(ppi.authority, ppi);
}
}
if (pi.services != null) {
for (ServiceInfo si : pi.services) {
...
if (si.processName == null) {
si.processName = si.applicationInfo.processName;
}
if (si.processName == null) {
si.processName = si.packageName;
}
mServices.put(si.name, si);
}
}
if (pi.receivers != null) {
for (ActivityInfo ri : pi.receivers) {
...
if (ri.processName == null) {
ri.processName = ri.applicationInfo.processName;
}
if (ri.processName == null) {
ri.processName = ri.packageName;
}
mReceivers.put(ri.name, ri);
}
}
...
}

显然,对于在Manifest中没有process属性的组件,将其processName全部设置为插件的包名,比如“com.qihoo360.replugin.sample.demo1”这样的,然后看一下PluginClientHelper.getProcessInt()方法:

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
/**
* 根据进程名称获取进程ID
*
* @param processName 进程名称
*/
public static Integer getProcessInt(String processName) {
if (!TextUtils.isEmpty(processName)) {
// 插件若想将组件定义成在"常驻进程"中运行,则可以在android:process中定义:
// 1. (推荐)":GuardService"。这样无论宿主的常驻进程名是什么,都会定向到"常驻进程"
String pntl = processName.toLowerCase();
String ppdntl = HostConfigHelper.PERSISTENT_NAME.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. 用户自定义进程时,从 Map 中取数据
// (根据冒号之后的名称来判断是否是自定义进程)
processName = PluginProcessHost.processTail(processName.toLowerCase());
if (PROCESS_INT_MAP.containsKey(processName)) {
return PROCESS_INT_MAP.get(processName);
}
}
return IPluginManager.PROCESS_UI;
}

代码中的注释都写得很清楚了,对于进程名含有”:guardservice”或者和宿主常驻进程名(如”me.ele.repluginhostsample:guardservice”)相同的,分配的pid就是IPluginManager.PROCESS_PERSIST(值为-2),对于用户自定义的进程,则从PROCESS_INT_MAP中分配,比如对于demo1这个插件,有如下映射关系:

1
2
3
4
5
<meta-data
android:name="process_map"
android:value="[
{'from':'com.qihoo360.replugin.sample.demo1:bg','to':'$p0'}
]" />

那么由于PROCESS_INT_MAP中含有”:p0”这样的key,那么给它分配的pid就是PROCESS_INIT+0,即-100+0.

需要注意的是,这里的pid并不是真正组件进程的pid !

至于这个分配的pid有什么用,在下一小节会分析。

3.3 插件中组件进程启动

在获取到pid之后,调用MP.startPluginProcess()来启动插件进程,其实这里确切地说是启动插件中组件的进程,因为插件在之前启动时运行在宿主的UI进程中,这里其实是对于那些有自定义进程需求的组件服务的。MP.startPluginProcess()方法如下:

1
2
3
public static final IPluginClient startPluginProcess(String plugin, int process, PluginBinderInfo info) throws RemoteException {
return PluginProcessMain.getPluginHost().startPluginProcess(plugin, process, info);
}

这里startPluginProcess()是一个IPC调用,那么重点就在于PluginProcessMain.getPluginHost()这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static final IPluginHost getPluginHost() {
if (sPluginHostLocal != null) {
return sPluginHostLocal;
}
// 可能是第一次,或者常驻进程退出了
if (sPluginHostRemote == null) {
if (LogDebug.LOG) {
if (IPC.isPersistentProcess()) {
LogDebug.e(PLUGIN_TAG, "插件框架未正常初始化");
throw new RuntimeException("插件框架未正常初始化");
}
}
// 再次唤起常驻进程
connectToHostSvc();
}
return sPluginHostRemote;
}

为了理解这个方法,我们需要先看一下sPluginHostLocal和sPluginHostRemote是怎么来的。

3.3.1 sPluginHostLocal是什么

sPluginHostLocal的赋值就只有一个地方,那就是PluginProcessMain.installHost()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
static final void installHost(IPluginHost host) {
sPluginHostLocal = host;
// 连接到插件化管理器的服务端
// Added by Jiongxuan Zhang
try {
PluginManagerProxy.connectToServer(sPluginHostLocal);
} catch (RemoteException e) {
// 基本不太可能到这里,直接打出日志
if (LOGR) {
e.printStackTrace();
}
}
}

而这个方法的调用是在PmBase.initForServer()中,而initForServer()是在插件的常驻进程(对于HostConfigHelper.PERSISTENT_ENABLE为true,即允许常驻进程作为插件管理时),或者UI进程(此时插件的管理就在UI进程中)。所以总结起来就是initForServer()是在插件的管理进程中被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private final void initForServer() {
...
mHostSvc = new PmHostSvc(mContext, this);
PluginProcessMain.installHost(mHostSvc);
PluginProcessMain.schedulePluginProcessLoop(PluginProcessMain.CHECK_STAGE1_DELAY);
// 兼容即将废弃的p-n方案 by Jiongxuan Zhang
mAll = new Builder.PxAll();
Builder.builder(mContext, mAll);
refreshPluginMap(mAll.getPlugins());
// [Newest!] 使用全新的RePlugin APK方案
// Added by Jiongxuan Zhang
try {
List<PluginInfo> l = PluginManagerProxy.load();
if (l != null) {
// 将"纯APK"插件信息并入总的插件信息表中,方便查询
// 这里有可能会覆盖之前在p-n中加入的信息。本来我们就想这么干,以"纯APK"插件为准
refreshPluginMap(l);
}
} catch (RemoteException e) {
...
}
}

可以看到其实就是创建了一个PmHostSvc对象然后传递给PluginProcessMain.installHost()方法,那么PmHostSvc到底是什么呢?

看看它继承自什么就知道了。

1
2
3
class PmHostSvc extends IPluginHost.Stub {
...
}

显然,PmHostSvc其实是实现了IPluginHost接口IPC调用的Server端的功能。

IPluginHost是一个非常重要的IPC接口,下面列一下它的方法:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
interface IPluginHost {
void installBinder(String name, in IBinder binder);
IBinder fetchBinder(String name);
long fetchPersistentCookie();
IPluginClient startPluginProcess(String plugin, int process, inout PluginBinderInfo info);
String attachPluginProcess(String process, int index, in IBinder binder, String def);
List<PluginInfo> listPlugins();
void regActivity(int index, String plugin, String container, String activity);
void unregActivity(int index, String plugin, String container, String activity);
void regService(int index, String plugin, String service);
void unregService(int index, String plugin, String service);
void regPluginBinder(in PluginBinderInfo info, IBinder binder);
void unregPluginBinder(in PluginBinderInfo info, IBinder binder);
/**
* 注册某插件下所有静态声明的的 receiver 到常驻进程
*/
void regReceiver(String plugin, in Map receiverFilterMap);
void unregReceiver();
/**
* 插件收到广播
*
* @param plugin 插件名称
* @param receiver Receiver 名称
* @param Intent 广播的 Intent 数据
*/
void onReceive(String plugin, String receiver, in Intent intent);
int sumBinders(int index);
void updatePluginInfo(in PluginInfo info);
PluginInfo pluginDownloaded(String path);
boolean pluginUninstalled(in PluginInfo info);
boolean pluginExtracted(String path);
oneway void sendIntent2Process(String target, in Intent intent);
oneway void sendIntent2Plugin(String target, in Intent intent);
void sendIntent2ProcessSync(String target, in Intent intent);
void sendIntent2PluginSync(String target, in Intent intent);
boolean isProcessAlive(String name);
IBinder queryPluginBinder(String plugin, String binder);
/**
* 根据 Inent 查询所有插件中的与之匹配的 Receivers
*/
List queryPluginsReceiverList(in Intent intent);
/**
* 获取“全新Service管理方案”在Server端的服务
* Added by Jiongxuan Zhang
*/
IPluginServiceServer fetchServiceServer();
/**
* 获取 IPluginManagerServer(纯APK方案使用)的插件服务
* Added by Jiongxuan Zhang
*/
IPluginManagerServer fetchManagerServer();
/**
* 根据 taskAffinity,判断应该取第几组 TaskAffinity
* 由于 taskAffinity 是跨进程的属性,所以这里要将 taskAffinityGroup 的数据保存在常驻进程中
* Added by hujunjie
*/
int getTaskAffinityGroupIndex(String taskAffinity);
/**
* 通过进程名来获取PID
*/
int getPidByProcessName(String processName);
/**
* 通过PID来获取进程名
*/
String getProcessNameByPid(int pid);
/**
* dump详细的运行时信息
*/
String dump();
}

可见,这些方法都是与插件管理有关的。

需要注意的是:只有在插件管理进程中的PluginProcessMain对象(PluginProcessMain只是封装了一些跨进程操作,它会在常驻进程和其他进程中被用到)中sPluginHostLocal才不会为null, 对于在其他进程中的PluginProcessMain对象,其installHost()方法并不会被调用,从而sPluginHostLocal为null. 此时,就需要sPluginHostRemote出场了!

3.3.2 sPluginHostRemote的作用,以及ContentProvider的巧妙运用

其实sPluginHostRemote是IPluginHost接口的代理,它是在connectToHostSvc()中被赋值的:

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
54
55
56
57
58
59
/**
* 非常驻进程调用,获取常驻进程的 IPluginHost
*/
static final void connectToHostSvc() {
Context context = PMF.getApplicationContext();
//
IBinder binder = PluginProviderStub.proxyFetchHostBinder(context);
...
if (binder == null) {
// 无法连接到常驻进程,当前进程自杀
...
System.exit(1);
}
//
try {
binder.linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
...
// 检测到常驻进程退出,插件进程自杀
if (PluginManager.isPluginProcess()) {
...
System.exit(0);
}
sPluginHostRemote = null;
// 断开和插件化管理器服务端的连接,因为已经失效
PluginManagerProxy.disconnect();
}
}, 0);
} catch (RemoteException e) {
// 无法连接到常驻进程,当前进程自杀
...
System.exit(1);
}
//
sPluginHostRemote = IPluginHost.Stub.asInterface(binder);
...
// 连接到插件化管理器的服务端
// Added by Jiongxuan Zhang
try {
PluginManagerProxy.connectToServer(sPluginHostRemote);
// 将当前进程的"正在运行"列表和常驻做同步
// TODO 若常驻进程重启,则应在启动时发送广播,各存活着的进程调用该方法来同步
PluginManagerProxy.syncRunningPlugins();
} catch (RemoteException e) {
// 获取PluginManagerServer时出现问题,可能常驻进程突然挂掉等,当前进程自杀
...
System.exit(1);
}
// 注册该进程信息到“插件管理进程”中
PMF.sPluginMgr.attach();
}

这个方法做了很多事情,主要分为如下几点:

  • 首先是PluginProviderStub.proxyFetchHostBinder()的作用。从后面IPluginHost.Stub.asInterface(binder)这个语句可知这里获取到的是与插件管理进程中IPluginHost的server通信的IBinder对象, 那么为什么它能同步获取到IBinder呢?一般不是都要通过bindService()这样的异步操作来获取吗?

​ 这里其实RePlugin采用了ContentProvider的一个特性,那就是ContentResolver().query()时返回Cursor,将IBinder包裹在Cursor中即可。见PluginProviderStub.proxyFetchHostBinder()方法:

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
/**
* 此处uri对应的是packagename+".loader.p.main",如"me.ele.repluginhostsample.loader.p.main",对应的是com.qihoo360.replugin.component.process.ProcessPitProviderPersist
* @param context
* @param selection
* @return
*/
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);
if (cursor == null) {
...
return null;
}
while (cursor.moveToNext()) {
//
}
IBinder binder = BinderCursor.getBinder(cursor);
...
return binder;
} finally {
CloseableUtils.closeQuietly(cursor);
}
}

由于uri为ProcessPitProviderPersist.URI, 所以这个query()方法最终调用到ProcessPitProviderPersist中的query(),该ContentProvider子类如下:

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
public class ProcessPitProviderPersist extends ContentProvider {
private static final String TAG = "ProcessPitProviderPersist";
private static final String AUTHORITY_PREFIX = IPC.getPackageName() + ".loader.p.main";
public static final Uri URI = Uri.parse("content://" + AUTHORITY_PREFIX + "/main");
public static boolean sInvoked;
@Override
public boolean onCreate() {
return true;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
sInvoked = true;
return PluginProviderStub.stubMain(uri, projection, selection, selectionArgs, sortOrder);
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
}

可以看到,对于query()回调,会调用PluginProviderStub.stubMain():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static final Cursor stubMain(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
...
if (SELECTION_MAIN_BINDER.equals(selection)) {
return BinderCursor.queryBinder(PMF.sPluginMgr.getHostBinder());
}
if (SELECTION_MAIN_PREF.equals(selection)) {
// 需要枷锁否?
initPref();
return BinderCursor.queryBinder(sPrefImpl);
}
return null;
}

此处我们的selection是SELECTION_MAIN_BINDER,所以返回的是BinderCursor.queryBinder(PMF.sPluginMgr.getHostBinder()), 该方法如下:

1
2
3
4
public static final Cursor queryBinder(IBinder binder) {
...
return new BinderCursor(PluginInfo.QUERY_COLUMNS, binder);
}

可见就是将IBinder传递进去,然后使用BinderCursor包裹,那这里的关键是PMF.sPluginMrg.getHostBinder()怎么就拿到了IPluginHost的server端的IBinder?

其实答案很简单! 因为ProcessPitProviderPersist这个ContentProvider跟插件管理进程在同一个进程中,它是被gradle插件打入到Manifest文件中的,最终效果如下:

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

而PMF.sPluginMrg.getHostBinder()会调用到PmBase.getHostBinder(), 而其中的mHostSvc的创建在前面已经提到过了,这里不再赘述了。

拿到IBinder之后,就可以调用IPluginHost.Stub.asInterface()拿到IPluginHost的代理了,之后调用PluginManagerProxy.connectToServer()其实就是通过这个代理对象拿到IPluginManagerServer的代理。我们看一下PmHostSvc中fetchManagerServer()的实现:

1
2
3
public IPluginManagerServer fetchManagerServer() throws RemoteException {
return mManager.getService();
}

只是调用PluginManagerServer.getService()方法:

1
2
3
public IPluginManagerServer getService() {
return mStub;
}

而Stub对象mStub其实是实现了继承自IPluginManagerServer.Stub,代码如下:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
private class Stub extends IPluginManagerServer.Stub {
@Override
public PluginInfo install(String path) throws RemoteException {
synchronized (LOCKER) {
return PluginManagerServer.this.installLocked(path);
}
}
@Override
public List<PluginInfo> load() throws RemoteException {
synchronized (LOCKER) {
return PluginManagerServer.this.loadLocked();
}
}
@Override
public List<PluginInfo> updateAll() throws RemoteException {
synchronized (LOCKER) {
return PluginManagerServer.this.updateAllLocked();
}
}
@Override
public void updateUsed(String pluginName, boolean used) throws RemoteException {
synchronized (LOCKER) {
PluginManagerServer.this.updateUsedLocked(pluginName, used);
}
}
@Override
public boolean uninstall(PluginInfo info) throws RemoteException {
synchronized (LOCKER) {
return PluginManagerServer.this.uninstallLocked(info);
}
}
@Override
public PluginRunningList getRunningPlugins() throws RemoteException {
synchronized (LOCKER) {
return PluginManagerServer.this.getRunningPluginsLocked();
}
}
@Override
public boolean isPluginRunning(String pluginName, String process) throws RemoteException {
synchronized (LOCKER) {
return PluginManagerServer.this.isPluginRunningLocked(pluginName, process);
}
}
@Override
public void syncRunningPlugins(PluginRunningList list) throws RemoteException {
synchronized (LOCKER) {
PluginManagerServer.this.syncRunningPluginsLocked(list);
}
}
@Override
public void addToRunningPlugins(String processName, int pid, String pluginName) throws RemoteException {
synchronized (LOCKER) {
PluginManagerServer.this.addToRunningPluginsLocked(processName, pid, pluginName);
}
}
@Override
public String[] getRunningProcessesByPlugin(String pluginName) throws RemoteException {
synchronized (LOCKER) {
return PluginManagerServer.this.getRunningProcessesByPluginLocked(pluginName);
}
}
}

需要注意的是IPluginManagerServer与IPluginHost的区别:IPluginHost其实更多的是为了起一个通信支撑的作用,有了它之后才能使插件,宿主进行通信,但它本身几乎没有涉及到插件业务,而IPluginManagerServer则是真正地实现插件管理,利用它来控制插件的安装、卸载、获取等,它们的相同之处则在于server端都运行在常驻进程中。

其实从IPluginManagerServer的aidl接口定义也可见一斑:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
interface IPluginManagerServer {
/**
* 安装一个插件
* <p>
* 注意:若为旧插件(p-n开头),则应使用IPluginHost的pluginDownloaded方法
*
* @return 安装的插件的PluginInfo对象
*/
PluginInfo install(String path);
/**
* 卸载一个插件
* <p>
* 注意:只针对“纯APK”插件方案
*
* @param info 插件信息
* @return 是否成功卸载插件?
*/
boolean uninstall(in PluginInfo info);
/**
* 加载插件列表,方便之后使用
* <p>
* TODO 这里只返回"新版插件",供PmBase使用。将来会合并
*
* @return PluginInfo的列表
*/
List<PluginInfo> load();
/**
* 更新所有插件列表
*
* @return PluginInfo的列表
*/
List<PluginInfo> updateAll();
/**
* 设置isUsed状态,并通知所有进程更新
*
* @param pluginName 插件名
* @param used 是否已经使用
*/
void updateUsed(String pluginName, boolean used);
/**
* 获取正在运行的插件列表
*
* @return 正在运行的插件名列表
*/
PluginRunningList getRunningPlugins();
/**
* 插件是否正在运行?
*
* @param pluginName 插件名
* @param process 指定进程名,如为Null则表示查所有
* @return 是否在运行?
*/
boolean isPluginRunning(String pluginName, String process);
/**
* 当进程启动时,同步正在运行的插件状态到Server端
*
* @param list 正在运行的插件名列表
*/
void syncRunningPlugins(in PluginRunningList list);
/**
* 当进程启动时,同步正在运行的插件状态到Server端
*
* @param processName 进程名
* @param pluginName 正在运行的插件名
*/
void addToRunningPlugins(String processName, int pid, String pluginName);
/**
* 获取正在运行此插件的进程名列表
*
* @param pluginName 要查询的插件名
* @return 正在运行此插件的进程名列表。一定不会为Null
*/
String[] getRunningProcessesByPlugin(String pluginName);
}

可以看到IPluginManagerServer的功能非常强大,包括安装、卸载插件,加载插件列表,更新插件列表,获取正在运行的插件列表,判断插件是否在运行,同步插件状态,获取正在运行此插件的进程名列表等。

3.3.3 进程分配

再回到MP.startPluginProcess()方法,可知会通过IPluginHost的代理经过IPC最终调用到PmHostSvc中startPluginProcess()方法:

1
2
3
4
5
6
@Override
public IPluginClient startPluginProcess(String plugin, int process, PluginBinderInfo info) throws RemoteException {
synchronized (this) {
return mPluginMgr.startPluginProcessLocked(plugin, process, info);
}
}

其中mPluginMgr是PmBase对象,PmBase.startPluginProcessLocked()方法如下:

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
54
55
56
final IPluginClient startPluginProcessLocked(String plugin, int process, PluginBinderInfo info) {
...
// 强制使用UI进程
if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) {
if (info.request == PluginBinderInfo.ACTIVITY_REQUEST) {
if (process == IPluginManager.PROCESS_AUTO) {
process = IPluginManager.PROCESS_UI;
}
}
if (info.request == PluginBinderInfo.BINDER_REQUEST) {
if (process == IPluginManager.PROCESS_AUTO) {
process = IPluginManager.PROCESS_UI;
}
}
}
PluginProcessMain.schedulePluginProcessLoop(PluginProcessMain.CHECK_STAGE1_DELAY);
// 获取
IPluginClient client = PluginProcessMain.probePluginClient(plugin, process, info);
if (client != null) {
...
return client;
}
// 分配
int index = IPluginManager.PROCESS_AUTO;
try {
index = PluginProcessMain.allocProcess(plugin, process);
...
} catch (Throwable e) {
...
}
// 分配的坑位不属于UI、和自定义进程,就返回。
if (!(index == IPluginManager.PROCESS_UI
|| PluginProcessHost.isCustomPluginProcess(index)
|| PluginManager.isPluginProcess(index))) {
return null;
}
// 启动 //KP 注意:在这里才真正地启动插件进程
boolean rc = PluginProviderStub.proxyStartPluginProcess(mContext, index);
...
if (!rc) {
return null;
}
// 再次获取
client = PluginProcessMain.probePluginClient(plugin, process, info);
if (client == null) {
...
return null;
}
...
return client;
}

这个方法比较长,主要分为以下几个部分:

  • 首先是PluginBinderInfo对象是在PluginCommImpl中的loadPluginActivity()中创建的,并且只赋予了request的值为ACTIVITY_REQUEST, pid和index为默认值-1
  • 由于Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS为true,而且我们这里info.request==PluginBinderInfo.ACTIVITY_REQUEST,不过我们这里process==IPluginManager.PROCESS_UI;如果是process==IPluginManager.PROCESS_AUTO,即自动分配进程的话,那么process也会被赋值IPluginManager.PROCESS_UI;
  • 之后调用PluginProcessMain.schedulePluginProcessLoop()是为了循环检查各插件进程,如果发现插件中没有组件在运行了,就收回这个进程。负责检查的task如下:

    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
    private static final void doPluginProcessLoop() {
    if (Constant.SIMPLE_QUIT_CONTROLLER) {
    ...
    synchronized (PROCESSES) {
    for (ProcessRecord r : PROCESSES) {
    if (r.state != STATE_RUNNING) {
    continue;
    }
    if (r.activities > 0) {
    continue;
    }
    if (r.services > 0) {
    continue;
    }
    if (r.binders > 0) {
    continue;
    }
    ...
    android.os.Process.killProcess(r.pid);
    waitKilled(r.pid);
    r.setStoped();
    //
    schedulePluginProcessLoop(CHECK_STAGE3_DELAY);
    return;
    }
    }
    }
    }

显然,这里就是遍历PROCESS,如果发现它不再运行,而且运行的组件和binder都为0了,就回收掉这个进程。需要注意的是这里的ProcessRecord是自定义的,不是android系统的那个ProcessRecord.

  • 之后调用PluginProcessMain.probePluginClient()方法来获取IPluginClient对象,该方法如下:

    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
    static final IPluginClient probePluginClient(String plugin, int process, PluginBinderInfo info) {
    synchronized (PROCESSES) {
    for (ProcessClientRecord r : ALL.values()) {
    if (process == IPluginManager.PROCESS_UI) {
    if (!TextUtils.equals(r.plugin, Constant.PLUGIN_NAME_UI)) {
    continue;
    }
    /* 是否是用户自定义进程 */
    } else if (PluginProcessHost.isCustomPluginProcess(process)) {
    if (!TextUtils.equals(r.plugin, getProcessStringByIndex(process))) {
    continue;
    }
    } else {
    if (!TextUtils.equals(r.plugin, plugin)) {
    continue;
    }
    }
    if (!isBinderAlive(r)) {
    return null;
    }
    if (!r.binder.pingBinder()) {
    return null;
    }
    info.pid = r.pid;
    info.index = r.index;
    return r.client;
    }
    }
    return null;
    }

这里就是分几种情况:

  • 如果process==IPluginManager.PROCESS_UI,那么遇到ProcessClientRecord.plugin与Constants.PLUGIN_NAME_UI相同的情况,就继续查找;
  • 如果是用户自定义进程,并且ProcessClient.plugin和getProcessStringByIndex()不相同时,就继续查找
  • 如果ProcessClientRecord.plugin与plugin相等,那么继续查找
  • 如果发现相应该ProcessRecord的binder都没有活着了,就返回null
  • 如果发现binder都ping不通,也返回null
  • 走到最后的,说明这个插件进程还活着,所以给PluginBinderInfo对象赋值,并且返回ProcessRecord中的IPluginClient对象.

而IPluginClient的作用是什么呢?

看下它的aidl定义:

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
interface IPluginClient {
// 参数 plugin, process 可能有冗余,目前临时使用,后续可能优化
String allocActivityContainer(String plugin, int process, String target, in Intent intent);
// 参数 plugin 用来处理多插件单进程情况
IBinder queryBinder(String plugin, String binder);
void releaseBinder();
oneway void sendIntent(in Intent intent);
void sendIntentSync(in Intent intent);
int sumActivities();
IPluginServiceServer fetchServiceServer();
/**
* 插件收到广播
*
* @param plugin 插件名称
* @param receiver Receiver 名称
* @param Intent 广播的 Intent 数据
*/
void onReceive(String plugin, String receiver, in Intent intent);
/**
* dump通过插件化框架启动起来的Service信息
*/
String dumpServices();
/**
* dump插件化框架中存储的详细Activity坑位映射表
*/
String dumpActivities();
}

可以看到也是用于插件进程与插件管理进程通信的,包括分配容器,接收广播等。这个接口到后面再详细分析。

再回到PmBase.startPluginProcessLocked()方法,如果返回的client不为空就说明所要求的进程已经启动(比如要发现要启动的进程是UI进程,而宿主UI进程已经启动,所以可直接返回),此时当然就没必要再启动进程了。如果返回的client为null, 说明插件还没启动,此时需要先分配进程pid,会调用到PluginProcessMain.allocProcess()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static final int allocProcess(String plugin, int process) {
if (Constant.PLUGIN_NAME_UI.equals(plugin) || process == IPluginManager.PROCESS_UI) {
return IPluginManager.PROCESS_UI;
}
if (PluginProcessHost.isCustomPluginProcess(process)) {
return process;
}
PluginInfo info = PluginTable.getPluginInfo(plugin);
if (info == null) {
...
return IPluginManager.PROCESS_AUTO;
}
synchronized (PROCESSES) {
return allocProcessLocked(plugin);
}
}

在这里,如果要启动的是UI进程,那么返回IPluginManager.PROCESS_UI;如果是自定义进程(process值在[-100,-100+3)之间),那么就还是返回process.

下面就到了真正启动插件中组件自定义进程的时刻。

3.3.4 组件自定义进程启动

回到PmBase.startPluginProcessLocked(), 在调用allocProcess()方法之后,获得了分配的pid,然后调用PluginProviderStub.proxyStartPluginProcess()启动插件中组件的自定义进程(目前写死了自定义进程最多只能有2个),该方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

可见,在这里就是通过调用ContentResolver的insert()方法,最终调用到相应的ContentProvider,然后启动对应的进程。

那么这种映射关系是如何建立的呢?ContentProvider又是何时新建并插入到Manifest文件中的呢?

首先说映射关系,在RePlugin解析之插件的安装与加载一文中已经提到了这种映射关系是可以由插件自行指定的,比如demo1中有如下映射关系:

1
2
3
4
5
<meta-data
android:name="process_map"
android:value="[
{'from':'com.qihoo360.replugin.sample.demo1:bg','to':'$p0'}
]" />

这就意味着com.qihoo360.replugin.sample.demo:bg这个进程要映射到hostpkg:p0这个进程,在编译过程中通过gradle插件会往Manifest中打入进程名为hostpkg:p0的ContentProvider,如下所示:

1
2
3
4
5
<provider
android:name="com.qihoo360.replugin.component.process.ProcessPitProviderP0"
android:exported="false"
android:process=":p0"
android:authorities="me.ele.repluginhostsample.loader.p.mainN100" />

从PluginProviderStub.proxyStartPluginProcess()方法中调用的ProcessPitProviderBase.buildUri()方法也可以看出:

1
2
3
4
5
6
7
8
9
10
11
12
13
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");
if (BuildConfig.DEBUG) {
Log.d(TAG, "buildUri: uri=" + uri);
}
return uri;
}

比如在这里index==-100,所以最终构造出来的uri就是”content://hostpkg.loader.p.mainN100/main”,当hostpkg为”me.ele.repluginhostsample”时就是”content://me.ele.repluginhostsample.loader.p.mainN100/main”,而这恰好就对应刚刚ProcessPitProviderP0的authorities.

到这里,就真正完成了插件中组件自定义进程的启动。

3.3.5 attachProcess()

前面提到,启动插件中组件的自定义进程时会调用到ProcessPitProviderBase中的insert()方法,然后调用到PluginProviderStub.stubPlugin()方法:

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
public static final Uri stubPlugin(Uri uri, ContentValues values) {
...
String method = values.getAsString(KEY_METHOD);
if (TextUtils.equals(method, METHOD_START_PROCESS)) {
//
Uri rc = new Uri.Builder().scheme("content").authority("process").encodedPath("status").encodedQuery(URL_PARAM_KEY_LOADED + "=" + 1).build();
...
long cookie = values.getAsLong(KEY_COOKIE);
// 首次
if (PMF.sPluginMgr.mLocalCookie == 0L) {
...
PMF.sPluginMgr.mLocalCookie = cookie;
} else { //只要常驻进程启动了,就会走到这个分支
// 常驻进程重新启动了.
if (PMF.sPluginMgr.mLocalCookie != cookie) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "reset cookie: " + cookie);
}
//
PMF.sPluginMgr.mLocalCookie = cookie;
//
PluginProcessMain.connectToHostSvc(); //这里是向插件管理进程报告自己的进程信息,最终会调用到attachProcess(),然后PluginProcessMain中的ALL就会增加一项ProcessClientRecord记录。
}
}
return rc;
}
return null;
}

注释里面已经写得很清楚了,最终会调用到PluginProcessMain.connectToHostSvc()方法,

而PluginProcessMain.connectToHostSvc()方法如下:

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
54
55
56
57
58
59
60
61
static final void connectToHostSvc() {
Context context = PMF.getApplicationContext();
//
IBinder binder = PluginProviderStub.proxyFetchHostBinder(context);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "host binder = " + binder);
}
if (binder == null) {
// 无法连接到常驻进程,当前进程自杀
...
System.exit(1);
}
//
try {
binder.linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (LOGR) {
LogRelease.i(PLUGIN_TAG, "p.p d, p.h s n");
}
// 检测到常驻进程退出,插件进程自杀
if (PluginManager.isPluginProcess()) {
...
System.exit(0);
}
sPluginHostRemote = null;
// 断开和插件化管理器服务端的连接,因为已经失效
PluginManagerProxy.disconnect();
}
}, 0);
} catch (RemoteException e) {
// 无法连接到常驻进程,当前进程自杀
...
System.exit(1);
}
//
sPluginHostRemote = IPluginHost.Stub.asInterface(binder);
....
// 连接到插件化管理器的服务端
// Added by Jiongxuan Zhang
try {
PluginManagerProxy.connectToServer(sPluginHostRemote);
// 将当前进程的"正在运行"列表和常驻做同步
// TODO 若常驻进程重启,则应在启动时发送广播,各存活着的进程调用该方法来同步
PluginManagerProxy.syncRunningPlugins();
} catch (RemoteException e) {
// 获取PluginManagerServer时出现问题,可能常驻进程突然挂掉等,当前进程自杀
...
System.exit(1);
}
// 注册该进程信息到“插件管理进程”中
PMF.sPluginMgr.attach();
}

这个方法在RePlugin解析之插件的安装与加载一文中已经分析过了,这里不再赘述。注意最后一个语句,它的作用就是注册该进程到插件管理进程中。

而PmBase.attach()方法如下:

1
2
3
4
5
6
7
8
final void attach() {
//
try {
mDefaultPluginName = PluginProcessMain.getPluginHost().attachPluginProcess(IPC.getCurrentProcessName(), PluginManager.sPluginProcessIndex, mClient, mDefaultPluginName);
} catch (Throwable e) {
...
}
}

这里是通过IPluginHost接口的代理进行IPC,最终调用到插件管理进程的PmHostSvc.attachPluginProcess()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public String attachPluginProcess(String process, int index, IBinder binder, String def) throws RemoteException {
int pid = Binder.getCallingPid();
IPluginClient client = null;
try {
client = IPluginClient.Stub.asInterface(binder); //可见,对于IPluginClient来说,server端在插件和插件组件中的自定义进程中,而client端在插件管理进程中,所以IPluginClient.Stub的实现PluginProcessPer对象的创建也是在插件进程中
} catch (Throwable e) {
...
}
if (client == null) {
return null;
}
return PluginProcessMain.attachProcess(pid, process, index, binder, client, def, mManager);
}

这个方法很简单,就是先获取IPluginClient的代理,然后调用PluginProcessMain.attachProcess()以记录插件进程(确切地说是插件中组件的进程)的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static final String attachProcess(int pid, String process, int index, IBinder binder, IPluginClient client, String def, PluginManagerServer pms) {
synchronized (PROCESSES) {
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 {
pr.binder.linkToDeath(pr, 0);
} catch (Throwable e) {
...
}
return plugin;
}
}

可以看到,这个方法很简单,就是先记录插件进程名称,pid, IPluginClient的代理client等,最后将ProcessClientRecord对象放入ALL这个缓存中。

3.4 分配坑位

到3.3为止,终于完成插件中组件进程的启动了。下面要进行坑位的选择, 会调用IPluginClient.allocActivityContainer()方法进行坑位的选择,注意这里通过IPluginClient的IPC最终会调用到刚刚启动的那个进程(而不是插件管理进程), allocActivityContainer()的真正实现在PluginProcessPer中,方法如下:

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
public String allocActivityContainer(String plugin, int process, String target, Intent intent) throws RemoteException {
// 一旦有分配,则进入监控状态(一是避免不退出的情况,二也是最重要的是避免现在就退出的情况)
RePlugin.getConfig().getEventCallbacks().onPrepareAllocPitActivity(intent);
String loadPlugin = null;
// 如果UI进程启用,尝试使用传过来的插件,强制用UI进程
if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) {
if (IPC.isUIProcess()) {
loadPlugin = plugin;
process = IPluginManager.PROCESS_UI;
} else {
loadPlugin = plugin;
}
}
// 如果不成,则再次尝试使用默认插件
if (TextUtils.isEmpty(loadPlugin)) {
if (mDefaultPlugin == null) {
...
return null;
}
loadPlugin = mDefaultPlugin.mInfo.getName();
}
//
String container = bindActivity(loadPlugin, process, target, intent);
...
return container;
}

这个方法很简单,先是对于 process和loadPlugin参数进行一些处理,然后调用bindActivity()获取到container:

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
54
55
56
57
58
59
60
61
62
/**
* 加载插件;找到目标Activity;搜索匹配容器;加载目标Activity类;建立临时映射;返回容器
*
* @param plugin 插件名称,类似"demo1"
* @param process 进程,默认值为-1
* @param activity Activity 名称,类似"com.qihoo360.replugin.sample.demo1.MainActivity"
* @param intent 调用者传入的 Intent
* @return 坑位
*/
final String bindActivity(String plugin, int process, String activity, Intent intent) {
/* 获取插件对象 */
Plugin p = mPluginMgr.loadAppPlugin(plugin);
if (p == null) {
...
return null;
}
/* 获取 ActivityInfo */
ActivityInfo ai = p.mLoader.mComponents.getActivity(activity);
if (ai == null) {
...
return null;
}
if (ai.processName == null) {
ai.processName = ai.applicationInfo.processName;
}
if (ai.processName == null) {
ai.processName = ai.packageName;
}
/* 获取 Container */
String container;
// 自定义进程,如果带有":p"的就是自定义进程
if (ai.processName.contains(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2)) {
String processTail = PluginProcessHost.processTail(ai.processName);
container = mACM.alloc2(ai, plugin, activity, process, intent, processTail);
} else {
container = mACM.alloc(ai, plugin, activity, process, intent);
}
if (TextUtils.isEmpty(container)) {
...
return null;
}
...
/* 检查 activity 是否存在 */
Class<?> c = null;
try {
c = p.mLoader.mClassLoader.loadClass(activity);
} catch (Throwable e) {
...
}
if (c == null) {
...
return null;
}
return container;
}

前面获取Plugin对象和ActivityInfo对象就不说了,就说最关键的部分,如果ActivityInfo中的进程名称包含”:p”,那么就意味着是自定义进程,就调用PluginContainers.alloc2()方法(吐槽一下这个方法命名真垃圾),否则调用PluginContainers.alloc()方法。

先来看PluginContainers.alloc2()方法:

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
String alloc2(ActivityInfo ai, String plugin, String activity, int process, Intent intent, String processTail) {
// 根据进程名称,取得该进程对应的 PluginContainerStates
ProcessStates states = mProcessStatesMap.get(processTail);
ActivityState state;
String defaultPluginTaskAffinity = ai.applicationInfo.packageName;
...
/* SingleInstance */
if (ai.launchMode == LAUNCH_SINGLE_INSTANCE) {
synchronized (mLock) {
state = allocLocked(ai, states.mLaunchModeStates.getStates(ai.launchMode, ai.theme), plugin, activity, intent);
}
/* TaskAffinity */
} else if (!defaultPluginTaskAffinity.equals(ai.taskAffinity)) { // 非默认 taskAffinity
synchronized (mLock) {
state = allocLocked(ai, states.mTaskAffinityStates.getStates(ai), plugin, activity, intent);
}
/* other mode */
} else {
synchronized (mLock) {
state = allocLocked(ai, states.mLaunchModeStates.getStates(ai.launchMode, ai.theme), plugin, activity, intent);
}
}
if (state != null) {
return state.container;
}
return null;
}

可以看到会分三种情况,一种是launchMode是LAUNCH_SINGLE_INSTANCE, 还有一种是带有taskAffinity, 最后一种是其他。

这三种情况都是调用allocLocked()方法进行坑位的分配,唯一的不同就是第2个参数。

  • 第一种情况是states.mLaunchModeStates.getStates(ai.launchMode, ai.theme)
  • 第二种情况是states.mTaskAffinityStates.getStates(ai)
  • 第三种是states.mLaunchModeStates.getStates(ai.launchMode, ai.theme), 这其实是最普遍的情形

为了分析清楚是如何分配的,我们需要先看一下ProcessStates, TaskAffinityStates以及LaunchModeStates是什么,以及它们是如何被初始化的。

3.4.1 ProcessStates的定义

ProcessStates的定义很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 保存自定义进程中,每个进程里的坑位信息
*
* @author RePlugin Team
*/
class ProcessStates {
/**
* 保存非默认 TaskAffinity 下,坑位的状态信息。
*/
TaskAffinityStates mTaskAffinityStates = new TaskAffinityStates();
/**
* 保存默认 TaskAffinity 下,坑位的状态信息。
*/
LaunchModeStates mLaunchModeStates = new LaunchModeStates();
}

3.4.2 LaunchModeStates

LaunchModeStates的定义其实也很简单:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class LaunchModeStates {
public static final String TAG = "launchMode";
/**
*
* 目前的策略是,针对每一种 launchMode 分配两种坑位(透明主题(TS)和不透明主题(NTS))
* <p>
* 例:透明主题
*        <N1NRTS0, ActivityState>
* NR + TS - > <N1NRTS1, ActivityState>
*        <N1NRTS2, ActivityState>
* <p>
* 例:不透明主题
*        <N1NRNTS0, ActivityState>
* NR + NTS - > <N1NRNTS1, ActivityState>
*        <N1NRNTS2, ActivityState>
* <p>
* 其中:N1 表示当前为 UI 进程,NR 表示 launchMode 为 Standard,NTS 表示坑的 theme 为 Not Translucent。
*/
private Map<String, HashMap<String, ActivityState>> mStates = new HashMap<>();
/**
* 初始化 LaunchMode 和 Theme 对应的坑位
*
* @param containers 保存所有 activity 坑位的引用
* @param prefix 坑位前缀
* @param launchMode launchMode
* @param translucent 是否是透明的坑
* @param count 坑位数
*/
void addStates(Map<String, ActivityState> allStates, HashSet<String> containers, String prefix, int launchMode, boolean translucent, int count) {
String infix = getInfix(launchMode, translucent);
HashMap<String, ActivityState> states = mStates.get(infix);
if (states == null) {
states = new HashMap<>();
mStates.put(infix, states);
}
for (int i = 0; i < count; i++) {
String key = prefix + infix + i;
// 只有开启“详细日志”时才输出每一个坑位的信息,防止刷屏
if (RePlugin.getConfig().isPrintDetailLog()) {
LogDebug.d(TAG, "LaunchModeStates.add(" + key + ")");
}
ActivityState state = new ActivityState(key);
states.put(key, state);
allStates.put(key, state);
containers.add(key);
}
}
/**
* 根据 launchMode 和 theme 获取对应的坑位集合,launchMode类似0,theme类似0
*/
HashMap<String, ActivityState> getStates(int launchMode, int theme) {
String infix = getInfix(launchMode, isTranslucentTheme(theme));
return mStates.get(infix); //infix类似"NRNTS"
}
/**
* 根据 launchMode 和 '是否透明' 获取中缀符
*
* @return 如果是透明主题,返回 'launchMode'_TS_,否则返回 'launchMode'_NOT_TS_
*/
private static String getInfix(int launchMode, boolean translucent) {
String launchModeInfix = getLaunchModeInfix(launchMode);
return translucent ? launchModeInfix + "TS" : launchModeInfix + "NTS";
}
/**
* 手动判断主题是否是透明主题
*/
public static boolean isTranslucentTheme(int theme) {
return theme == android.R.style.Theme_Translucent
|| theme == android.R.style.Theme_Dialog
|| theme == android.R.style.Theme_Translucent_NoTitleBar
|| theme == android.R.style.Theme_Translucent_NoTitleBar_Fullscreen;
}
/**
* 获取 launchMode 对应的前缀
*/
private static String getLaunchModeInfix(int launchMode) {
switch (launchMode) {
case ActivityInfo.LAUNCH_SINGLE_TOP:
return "STP";
case ActivityInfo.LAUNCH_SINGLE_TASK:
return "ST";
case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
return "SI";
default:
return "NR";
}
}
}

LaunchModeStates的成员非常简单,就是一个Map>对象,其中key为launchMode和主题的组合缩写,比如”NRTS”由NR+TS构成,launchMode总结起来有如下情形:

  • NR表示 launMode为Standard
  • SI表示launchMode为singleInstance
  • ST表示launchMode为singleTask
  • STP表示launchMode为singleTop

而主题只有两种,一种是透明(TS,translucent缩写),另一种是不透明(NTS).

LaunchModeStates的方法都很好理解,就不赘述了。

3.4.3 TaskAffinityStates

TaskAffinityStates的定义如下:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class TaskAffinityStates {
public static final String TAG = "task-affinity";
/**
* TaskAffinity 的组数
*/
private static final int GROUP_COUNT = HostConfigHelper.ACTIVITY_PIT_COUNT_TASK;
/**
*
*第二个元素和第一个完全对应,所有Activity的名字只有一个地方不同,就是"TA0"改成了"TA1",其中"TA0"对应taskAffinity为":t0","TA1"对应taskAffinity为":t1"
* 比如"NRTS",value为"appId.loader.a.ActivityN1TA1NRTS0","appId.loader.a.ActivityN1TA1NRTS1"
* 保存所有 taskAffinity 的坑位的状态,数组索引为 TaskAffinity 索引。
*/
private LaunchModeStates[] mLaunchModeStates = new LaunchModeStates[GROUP_COUNT];
/**
* 初始化 TaskAffinity 的坑位数据
*
* @param prefix {applicationID}.loader.a.Activity
* @param suffix [N1, P0, P1]
* @param allStates 存储在 PluginContainer 中的所有的坑位的状态
* @param containers 所有坑位名称
*/
public void init(String prefix, String suffix, HashMap<String, PluginContainers.ActivityState> allStates,
HashSet<String> containers) {
// 外层循环为组数,内层循环为每组的坑的数量
for (int i = 0; i < GROUP_COUNT; i++) {
if (mLaunchModeStates[i] == null) {
mLaunchModeStates[i] = new LaunchModeStates();
}
LaunchModeStates states = mLaunchModeStates[i];
/* Standard */
states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD);
states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD);
/* SingleTop */
states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP);
states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP);
/* SingleTask */
states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK);
states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK);
/* SingleInstance */
states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE);
states.addStates(allStates, containers, prefix + suffix + "TA" + i, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE);
}
}
/**
* 根据插件 Activity 的信息,找到宿主对应的坑位集合
*/
HashMap<String, PluginContainers.ActivityState> getStates(ActivityInfo ai) {
if (ai != null) {
// 找到应该取第几个 TaskAffinity 中的坑
int index = 0;
try {
index = MP.getTaskAffinityGroupIndex(ai.taskAffinity);
} catch (RemoteException e) {
e.printStackTrace();
}
LaunchModeStates states = mLaunchModeStates[index];
if (states != null) {
return states.getStates(ai.launchMode, ai.theme);
}
}
return null;
}
}

可见TaskAffinityStates其实就是LaunchModeStates的组合,它里面有一个LaunchModeStates数组,在初始化时会将各种launchMode的占位activity添加到LaunchModeStates中。

而TaskAffinityStates的init()被调用的地方有两个,一个是在PluginContainers中的init()方法中:

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
final void init(int process, HashSet<String> containers) {
if (process != IPluginManager.PROCESS_UI
&& !PluginProcessHost.isCustomPluginProcess(process)
&& !PluginManager.isPluginProcess()) {
return;
}
String prefix = IPC.getPackageName() + CONTAINER_ACTIVITY_PART;
// 因为自定义进程可能也会唤起使用 UI 进程的坑,所以这里使用'或'条件
if (process == IPluginManager.PROCESS_UI || PluginProcessHost.isCustomPluginProcess(process)) {
/* UI 进程标识为 N1 */
String suffix = "N1";
// Standard
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD);
// SingleTop
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP);
// SingleTask
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK);
// SingleInstance
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE);
// taskAffinity
mTaskAffinityStates.init(prefix, suffix, mStates, containers);
// 因为有可能会在 UI 进程启动自定义进程的 Activity,所以此处也要初始化自定义进程的坑位数据
for (int i = 0; i < PluginProcessHost.PROCESS_COUNT; i++) {
ProcessStates processStates = new ProcessStates();
// [":p1": state("P1"), ":p2": state("P2")]
mProcessStatesMap.put(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2 + i, processStates);
init2(prefix, containers, processStates, PluginProcessHost.PROCESS_PLUGIN_SUFFIX + i);
}
// 从内存中加载
loadFromPref();
}
}

其实这个方法的逻辑很简单,就是分别添加UI进程和自定义进程的坑位Activity, 其中对于UI进程的坑位Activity分别添加Standard,SingleTop, SingleTask, SingleInstance的坑位Activity,然后调用TaskAffinityStates.init()方法添加这些占坑Activity带上taskAffinity的Activity.

而对于需要在自定义进程中运行的占坑Activity,其实就是轮询3个进程(目前PluginProcessHost.PROCESS_COUNT值为3),然后调用init2()将占坑Activity添加到ProcessStates对象中。

PluginContainers的init2()方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void init2(String prefix, HashSet<String> containers, ProcessStates states, String suffix) {
suffix = suffix.toUpperCase();
// Standard
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_STANDARD);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_MULTIPLE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_STANDARD);
// SingleTop
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TOP);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TOP, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TOP);
// SingleTask
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_TASK);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_TASK, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_TASK);
// SingleInstance
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE);
states.mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE);
// taskAffinity
states.mTaskAffinityStates.init(prefix, suffix, mStates, containers);
}

显然,这里做的事情其实跟init1()很类似,就是将4种模式并且带有进程信息(比如:p0)的占坑Activity添加到LaunchModeStates中,然后再通过TaskAffinityStates的init()在些基础上再加上taskAffinity,然后再添加到TaskAffinityStates中。

需要注意的是,目前RePlugin对于插件进程的限制为3个,对于taskAffinity的限制为2种。

最后LaunchModeStates的值类似下面这种:

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
/**
* debug发现mStates的key如下:
* "NRTS":launchMode为Standard, TS表示透明.对应的value为"appId.loader.a.ActivityN1NRTS0","appId.loader.a.ActivityN1NRTS1"
* "NRNTS":launchMode为Standard,NTS表示不透明,对应的value为"appId.loader.a.ActivityN1NRNTS0",...,"appId.loader.a.ActivityN1NRNTS5"
*
* "SITS":launchMode为singleInstance,TS表示透明,对应的value为"appId.loader.a.ActivityN1SITS0","appId.loader.a.ActivityN1SITS1","appId.loader.a.ActivityN1SITS2"
*"SINTS":launchMode为singleInstance,NTS表示不透明,对应的value为"appId.loader.a.ActivityN1SINTS0","appId.loader.a.ActivitySINTS1"
*
* "STTS":launchMode为singleTask,TS表示透明,对应的value为"appId.loader.a.ActivityN1STTS0","appId.loader.a.ActivityN1STTS0"
* "STNTS":launchMode为singleTask,NTS表示不透明,对应的value为"appId.loader.a.ActivityN1STNTS0",...,"appId.loader.a.ActivityN1STNTS2"
*
* "STPTS":launchMode为singleTop,TS表示透明,对应的value为"appId.loader.a.ActivityN1STPTS0","appId.loader.a.ActivityN1STPTS1"
* "STPNTS":launchMode为singleTop,NTS表示不透明,对应的value为"appId.loader.a.ActivityN1STPNTS0","appId.loader.a.ActivityN1STPNTS1"
*
*
*
* 目前的策略是,针对每一种 launchMode 分配两种坑位(透明主题(TS)和不透明主题(NTS))
* <p>
* 例:透明主题
*        <N1NRTS0, ActivityState>
* NR + TS - > <N1NRTS1, ActivityState>
*        <N1NRTS2, ActivityState>
* <p>
* 例:不透明主题
*        <N1NRNTS0, ActivityState>
* NR + NTS - > <N1NRNTS1, ActivityState>
*        <N1NRNTS2, ActivityState>
* <p>
* 其中:N1 表示当前为 UI 进程,NR 表示 launchMode 为 Standard,NTS 表示坑的 theme 为 Not Translucent。
*/
private Map<String, HashMap<String, ActivityState>> mStates = new HashMap<>();

而TaskAffinityStates的值类似下面这种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* debug发现mLaunchModeStates这个数组长度为2
* 其中第一个元素为里面长度为8的map,各元素如下:
* "NRTS":NR表示lanchMode为Standard,TS表示透明,value为"appId.loader.a.ActivityN1TA0NRTS0","appId.loader.a.ActivityN1TA0NRTS1"
* "SINTS":SIN表示launchMode为singleInstance,TS表示透明,value为"appId.loader.a.ActivityN1TA0SINTS0","appId.loader.a.ActivityN1TA0SINTS1"
* "STTS":ST表示singleTask,TS表示透明,value为"appId.loader.a.ActivityN1TA0STTS0","appId.loader.a.ActivityN1TA0STTS0"
* "NRNTS":NR表示lanchMode为Standard,value为"appId.loader.a.ActivityN1TA0NRNTS0",...,"appId.loader.a.ActivityN1TA0NRTS5",一共6个值
* "STPTS":STP表示singleTop,value为"appId.loader.a.ActivityN1TA0STPTS0","appId.loader.a.ActivityN1TA0STPTS1"
* "STNTS":ST表示singleTask,NTS表示不透明,value为"appId.loader.a.ActivityN1TA0STNTS0",...,"appId.loader.a.ActivityN1TA0STNTS2"
* "STPNTS":STP表示singleTop,NTS表示不透明,value为"appId.loader.a.ActivityN1TA0STPNTS0","appId.loader.a.ActivityN1TA0STPNTS1"
*
*第二个元素和第一个完全对应,所有Activity的名字只有一个地方不同,就是"TA0"改成了"TA1",其中"TA0"对应taskAffinity为":t0","TA1"对应taskAffinity为":t1"
* 比如"NRTS",value为"appId.loader.a.ActivityN1TA1NRTS0","appId.loader.a.ActivityN1TA1NRTS1"
* 保存所有 taskAffinity 的坑位的状态,数组索引为 TaskAffinity 索引。
*/
private LaunchModeStates[] mLaunchModeStates = new LaunchModeStates[GROUP_COUNT];

3.4.4 真正分配坑位

再回到PluginContainers.alloc2()方法:

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
String alloc2(ActivityInfo ai, String plugin, String activity, int process, Intent intent, String processTail) {
// 根据进程名称,取得该进程对应的 PluginContainerStates
ProcessStates states = mProcessStatesMap.get(processTail);
ActivityState state;
String defaultPluginTaskAffinity = ai.applicationInfo.packageName;
...
/* SingleInstance */
if (ai.launchMode == LAUNCH_SINGLE_INSTANCE) {
synchronized (mLock) {
state = allocLocked(ai, states.mLaunchModeStates.getStates(ai.launchMode, ai.theme), plugin, activity, intent);
}
/* TaskAffinity */
} else if (!defaultPluginTaskAffinity.equals(ai.taskAffinity)) { // 非默认 taskAffinity
synchronized (mLock) {
state = allocLocked(ai, states.mTaskAffinityStates.getStates(ai), plugin, activity, intent);
}
/* other mode */
} else {
synchronized (mLock) {
state = allocLocked(ai, states.mLaunchModeStates.getStates(ai.launchMode, ai.theme), plugin, activity, intent);
}
}
if (state != null) {
return state.container;
}
return null;
}

比如,如果是LAUNCH_SINGLE_INSTANCE, 由于ProcessStates中的mLaunchModeStates中已经在前面进行了如下添加操作:

1
2
3
// SingleInstance
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, true, HostConfigHelper.ACTIVITY_PIT_COUNT_TS_SINGLE_INSTANCE);
mLaunchModeStates.addStates(mStates, containers, prefix + suffix, LAUNCH_SINGLE_INSTANCE, false, HostConfigHelper.ACTIVITY_PIT_COUNT_NTS_SINGLE_INSTANCE);
  • 如果是透明主题,会返回一个类似”me.ele.repluginhostsample.loader.a.ActivityN1SINTS0”这样的坑位。
  • 如果是Stanadrd launchMode,并且使用默认的taskAffinity, 主题为不透明,那么会返回一个类似”me.ele.repluginhostsample.loader.a.ActivityN1NRNTS0”这样的坑位,其中NTS表示不透明。
  • 如果该Activity是自定义进程,由于RePlugin目前限制一个插件中最多有3个自定义进程,所以肯定是:p0,:p1,:p2中的一个,比如为:p0则是返回类似”me.ele.repluginhostsample.loader.a.ActivityP0NRNTS0”这样的坑位 。

4. 进入插件后的startActivity()调用

Factory2也只是起一个封装作用,即它只是调用PluginLibraryInternalProxy中相应的方法来完成操作,Factory2中的startActivity(Context,Intent)方法就是调用了PluginLibraryInternalProxy的startActivity(Context,Intent)方法:

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity
* 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制
* @param context Context上下文
* @param intent
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
*/
public boolean startActivity(Context context, Intent intent) {
...
// 兼容模式,直接使用标准方式启动
if (intent.getBooleanExtra(IPluginManager.KEY_COMPATIBLE, false)) {
PmBase.cleanIntentPluginParams(intent);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: COMPATIBLE is true, direct start");
}
return false;
}
// 获取Activity的名字,有两种途径:
// 1. 从Intent里取。通常是明确知道要打开的插件的Activity时会用
// 2. 从Intent的ComponentName中获取
String name = intent.getStringExtra(IPluginManager.KEY_ACTIVITY);
if (TextUtils.isEmpty(name)) {
ComponentName cn = intent.getComponent();
if (cn != null) {
name = cn.getClassName();
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: custom context=" + context);
}
}
}
// 已经是标准坑了(例如N1ST1这样的),则无需再过“坑位分配”逻辑,直接使用标准方式启动
if (mPluginMgr.isActivity(name)) {
PmBase.cleanIntentPluginParams(intent);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: context is container, direct start");
}
return false;
}
// 获取插件名,有三种途径:
// 1. 从Intent里取。通常是明确知道要打开的插件时会用
// 2. 根据当前Activity的坑位名来“反查”其插件名。通常是插件内开启自己的Activity时用到
// 3. 通过获得Context的类加载器来判断其插件名
String plugin = intent.getStringExtra(IPluginManager.KEY_PLUGIN);
/* 检查是否是动态注册的类 */
// 如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。
// 原因:宿主的某些动态注册的类不能运行在坑位中(如'桌面'插件的入口Activity)
ComponentName componentName = intent.getComponent();
if (componentName != null) {
if (LogDebug.LOG) {
LogDebug.d("loadClass", "isHookingClass(" + plugin + "," + componentName.getClassName() + ") = "
+ isDynamicClass(plugin, componentName.getClassName()));
}
if (isDynamicClass(plugin, componentName.getClassName())) {
intent.putExtra(IPluginManager.KEY_COMPATIBLE, true);
intent.setComponent(new ComponentName(IPC.getPackageName(), componentName.getClassName()));
context.startActivity(intent);
return false;
}
}
if (TextUtils.isEmpty(plugin)) {
// 看下Context是否为Activity,如是则直接从坑位中获取插件名(最准确)
if (context instanceof Activity) {
plugin = fetchPluginByPitActivity((Activity) context);
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: custom plugin is empty, query plugin=" + plugin);
}
}
// 没拿到插件名?再从 ClassLoader 获取插件名称(兜底)
if (TextUtils.isEmpty(plugin)) {
plugin = RePlugin.fetchPluginNameByClassLoader(context.getClassLoader());
}
// 仍然拿不到插件名?(例如从宿主中调用),则打开的Activity可能是宿主的。直接使用标准方式启动
if (TextUtils.isEmpty(plugin)) {
PmBase.cleanIntentPluginParams(intent);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start context: plugin and context is empty, direct start");
}
return false;
}
// 获取进程值,看目标Activity要打开哪个进程
int process = intent.getIntExtra(IPluginManager.KEY_PROCESS, Integer.MIN_VALUE);
PmBase.cleanIntentPluginParams(intent);
// 调用“特殊版”的startActivity,不让自动填写ComponentName,防止外界再用时出错
return Factory.startActivityWithNoInjectCN(context, intent, plugin, name, process);
}