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 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
28
29
/**
* 根据条件,查找 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) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: bindActivity: may be invalid plugin name or load plugin failed: plugin=" + p);
}
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)的调用,方法如下:

1
2
3
final Plugin loadAppPlugin(String plugin) {
return loadPlugin(mPlugins.get(plugin), Plugin.LOAD_APP, true);
}

这里的3个参数都很重要,首先看mPlugins是如何来的。

mPlugins其实包含的就量插件信息,key为插件的包名或别名,而value则为插件对象。它其实是在常驻进程初始化时赋值的。当然,中间如果安装了新的插件,mPlugins中的信息也会有更新。

mPlugins的初始化流程如下:

3.1.1 插件信息的加载

首先看一下插件信息是如何来的,关注initForServer()中调用的Builder.builder()方法.

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
static final void builder(Context context, PxAll all) {
// 搜索所有本地插件和V5插件
Finder.search(context, all);
// 删除不适配的PLUGINs
for (PluginInfo p : all.getOthers()) {
// TODO 如果已存在built-in和V5则不删除
if (LOG) {
LogDebug.d(PLUGIN_TAG, "delete obsolote plugin=" + p);
}
boolean rc = p.deleteObsolote(context);
if (!rc) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "can't delete obsolote plugin=" + p);
}
}
}
// 删除所有和PLUGINs不一致的DEX文件
deleteUnknownDexs(context, all);
// 删除所有和PLUGINs不一致的SO库目录
// Added by Jiongxuan Zhang
deleteUnknownLibs(context, all);
// 构建数据
}

其中的重点在于Finder.search()这个方法调用,因为它会搜索所有本地插件和V5插件,如下:

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
* 扫描插件
*/
static final void search(Context context, PxAll all) {
// 扫描内置插件
FinderBuiltin.loadPlugins(context, all);
// 扫描V5插件
File pluginDir = context.getDir(Constant.LOCAL_PLUGIN_SUB_DIR, 0);
V5Finder.search(context, pluginDir, all);
// 扫描现有插件,包括刚才从V5插件文件更新过来的文件
HashSet<File> deleted = new HashSet<File>();
{
if (LOG) {
LogDebug.d(PLUGIN_TAG, "search plugins: dir=" + pluginDir.getAbsolutePath());
}
searchLocalPlugins(pluginDir, all, deleted);
}
// 删除非插件文件和坏的文件
for (File f : deleted) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "search: delete plugin dir invalid file=" + f.getAbsolutePath());
}
boolean rc = f.delete();
if (!rc) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "search: can't delete plugin dir invalid file=" + f.getAbsolutePath());
}
}
}
deleted.clear();
}

真正执行查询的是FinderBuiltin.loadPlugins()这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
tatic final void loadPlugins(Context context, PxAll all) {
InputStream in;
// 读取内部配置
in = null;
try {
in = context.getAssets().open("plugins-builtin.json");
// TODO 简化参数 all
readConfig(in, all);
} catch (FileNotFoundException e0) {
if (LOG) {
LogDebug.e(PLUGIN_TAG, "plugins-builtin.json" + " not found");
}
} catch (Throwable e) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, e.getMessage(), e);
}
}
CloseableUtils.closeQuietly(in);
}

显然,这里就是查询assets目录下的plugins-builtin.json这个文件,获取所有内建的插件信息,该文件示例如下:

1
[{"high":null,"frm":null,"ver":100,"low":null,"pkg":"com.qihoo360.replugin.sample.demo1","path":"plugins/demo1.jar","name":"demo1"},{"high":null,"frm":null,"ver":100,"low":null,"pkg":"com.qihoo360.replugin.sample.demo2","path":"plugins/demo2.jar","name":"demo2"}]

而读取里面的json信息的代码如下:

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
private static final void readConfig(InputStream in, PxAll all) throws IOException, JSONException {
String str = IOUtils.toString(in, Charsets.UTF_8);
JSONArray ja = new JSONArray(str);
for (int i = 0; i < ja.length(); i++) {
JSONObject jo = ja.getJSONObject(i);
if (jo == null) {
continue;
}
String name = jo.getString("name");
if (TextUtils.isEmpty(name)) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "built-in plugins config: invalid item: name is empty, json=" + jo);
}
continue;
}
PluginInfo info = PluginInfo.buildFromBuiltInJson(jo);
if (!info.match()) {
if (LOG) {
LogDebug.e(PLUGIN_TAG, "built-in plugins config: mismatch item: " + info);
}
continue;
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "built-in plugins config: item: " + info);
}
all.addBuiltin(info);
}
}
public static final PluginInfo buildFromBuiltInJson(JSONObject jo) {
String pkgName = jo.optString("pkg");
String name = jo.optString("name");
String assetName = jo.optString("path");
if (TextUtils.isEmpty(name) || TextUtils.isEmpty(pkgName) || TextUtils.isEmpty(assetName)) {
if (LogDebug.LOG) {
LogDebug.d(TAG, "buildFromBuiltInJson: Invalid json. j=" + jo);
}
return null;
}
int low = jo.optInt("low", Constant.ADAPTER_COMPATIBLE_VERSION); // Low应指向最低兼容版本
int high = jo.optInt("high", Constant.ADAPTER_COMPATIBLE_VERSION); // High同上
int ver = jo.optInt("ver");
PluginInfo info = new PluginInfo(pkgName, name, low, high, ver, assetName, TYPE_BUILTIN);
// 从 json 中读取 frameVersion(可选)
int frameVer = jo.optInt("frm");
if (frameVer < 1) {
frameVer = RePlugin.getConfig().getDefaultFrameworkVersion();
}
info.setFrameworkVersion(frameVer);
return info;
}

其实就是读取json信息然后赋值给PluginInfo,其实PluginInfo本来应该设计成一个标准的Java bean,而现在竟然仍然用JSONObject来保存信息,然后后面需要时又要读出来,这其实是一个不太好的设计

3.1.2 initForServer()

如果允许开启常驻进程来管理插件进程,则在在首次启动:GuardService这个常驻进程时,会调用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
25
26
27
28
29
30
31
32
33
/**
* Persistent(常驻)进程的初始化
*
*/
private final void initForServer() {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "search plugins from file system");
}
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) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "lst.p: " + e.getMessage(), e);
}
}
}

在其中的refreshPluginMap()中会进行mPlugins的赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 更新所有的插件信息
*
* @param plugins
*/
private final void refreshPluginMap(List<PluginInfo> plugins) {
if (plugins == null) {
return;
}
for (PluginInfo info : plugins) {
Plugin plugin = Plugin.build(info);
putPluginObject(info, plugin);
}
}
private void putPluginObject(PluginInfo info, Plugin plugin) {
// 同时加入PackageName和Alias(如有)
mPlugins.put(info.getPackageName(), plugin);
if (!TextUtils.isEmpty(info.getAlias())) {
// 即便Alias和包名相同也可以再Put一次,反正只是覆盖了相同Value而已
mPlugins.put(info.getAlias(), plugin);
}
}

可见,首先根据之前的插件信息生成Plugin对象,然后分别以插件的包名和别名为key,以Plugin对象作为value插入到mPlugins中,经过初始化后,Demo中的结果如下图:

所以,再回到PluginCommImpl.getActivityInfo()这个方法,显然可以根据”demo1”这个别名获取到对应的Plugin对象。

3.1.3 mComponents

到这里为止,我们只是分析了读取插件信息并且创建Plugin对象的过程,但是还没有分析完插件的加载过程,实际上插件的加载涉及到dex文件,so库等的处理。

而内建插件的加载流程如下:

  • Plugin.load(int, boolean)方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    final boolean load(int load, boolean useCache) {
    PluginInfo info = mInfo;
    boolean rc = loadLocked(load, useCache);
    // 尝试在此处调用Application.onCreate方法
    // Added by Jiongxuan Zhang
    if (load == LOAD_APP && rc) {
    callApp();
    }
    // 如果info改了,通知一下常驻
    // 只针对P-n的Type转化来处理,一定要通知,这样Framework_Version也会得到更新
    if (rc && mInfo != info) {
    UpdateInfoTask task = new UpdateInfoTask((PluginInfo) mInfo.clone());
    Tasks.post2Thread(task);
    }
    return rc;
    }

    显然,这个方法是先调用loadLocked()方法完成加载,然后调用callApp()以调用到插件的Application.onCreate()方法。

  • loadLocked()方法如下:

    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
    private boolean loadLocked(int load, boolean useCache) {
    // 若插件被“禁用”,则即便上次加载过(且进程一直活着),这次也不能再次使用了
    // Added by Jiongxuan Zhang
    int status = PluginStatusController.getStatus(mInfo.getName(), mInfo.getVersion());
    if (status < PluginStatusController.STATUS_OK) {
    if (LOG) {
    LogDebug.d(PLUGIN_TAG, "loadLocked(): Disable in=" + mInfo.getName() + ":" + mInfo.getVersion() + "; st=" + status);
    }
    return false;
    }
    if (mInitialized) {
    if (mLoader == null) {
    if (LOG) {
    LogDebug.i(MAIN_TAG, "loadLocked(): Initialized but mLoader is Null");
    }
    return false;
    }
    if (load == LOAD_INFO) {
    boolean rl = mLoader.isPackageInfoLoaded();
    if (LOG) {
    LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, pkginfo loaded = " + rl);
    }
    return rl;
    }
    if (load == LOAD_RESOURCES) {
    boolean rl = mLoader.isResourcesLoaded();
    if (LOG) {
    LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, resource loaded = " + rl);
    }
    return rl;
    }
    if (load == LOAD_DEX) {
    boolean rl = mLoader.isDexLoaded();
    if (LOG) {
    LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, dex loaded = " + rl);
    }
    return rl;
    }
    boolean il = mLoader.isAppLoaded();
    if (LOG) {
    LogDebug.i(MAIN_TAG, "loadLocked(): Initialized, is loaded = " + il);
    }
    return il;
    }
    mInitialized = true;
    // 若开启了“打印详情”则打印调用栈,便于观察
    if (RePlugin.getConfig().isPrintDetailLog()) {
    String reason = "";
    reason += "--- plugin: " + mInfo.getName() + " ---\n";
    reason += "load=" + load + "\n";
    StackTraceElement elements[] = Thread.currentThread().getStackTrace();
    for (StackTraceElement item : elements) {
    if (item.isNativeMethod()) {
    continue;
    }
    String cn = item.getClassName();
    String mn = item.getMethodName();
    String filename = item.getFileName();
    int line = item.getLineNumber();
    if (LOG) {
    LogDebug.i(PLUGIN_TAG, cn + "." + mn + "(" + filename + ":" + line + ")");
    }
    reason += cn + "." + mn + "(" + filename + ":" + line + ")" + "\n";
    }
    if (sLoadedReasons == null) {
    sLoadedReasons = new ArrayList<String>();
    }
    sLoadedReasons.add(reason);
    }
    // 这里先处理一下,如果cache命中,省了后面插件提取(如释放Jar包等)操作
    if (useCache) {
    boolean result = loadByCache(load);
    // 如果缓存命中,则直接返回
    if (result) {
    return true;
    }
    }
    Context context = mContext;
    ClassLoader parent = mParent;
    PluginCommImpl manager = mPluginManager;
    //
    String logTag = "try1";
    String lockFileName = String.format(Constant.LOAD_PLUGIN_LOCK, mInfo.getApkFile().getName());
    ProcessLocker lock = new ProcessLocker(context, lockFileName);
    if (LOG) {
    LogDebug.i(PLUGIN_TAG, "loadLocked(): Ready to lock! logtag = " + logTag + "; pn = " + mInfo.getName());
    }
    if (!lock.tryLockTimeWait(5000, 10)) {
    // 此处仅仅打印错误
    if (LOGR) {
    LogRelease.w(PLUGIN_TAG, logTag + ": failed to lock: can't wait plugin ready");
    }
    }
    //
    long t1 = System.currentTimeMillis();
    boolean rc = doLoad(logTag, context, parent, manager, load);
    if (LOG) {
    LogDebug.i(PLUGIN_TAG, "load " + mInfo.getPath() + " " + hashCode() + " c=" + load + " rc=" + rc + " delta=" + (System.currentTimeMillis() - t1));
    }
    //
    lock.unlock();
    if (LOG) {
    LogDebug.i(PLUGIN_TAG, "loadLocked(): Unlock! logtag = " + logTag + "; pn = " + mInfo.getName());
    }
    if (!rc) {
    if (LOGR) {
    LogRelease.e(PLUGIN_TAG, logTag + ": loading fail1");
    }
    }
    if (rc) {
    // 打印当前内存占用情况,只针对Dex和App加载做输出
    // 只有开启“详细日志”才会输出,防止“消耗性能”
    if (LOG && RePlugin.getConfig().isPrintDetailLog()) {
    if (load == LOAD_DEX || load == LOAD_APP) {
    LogDebug.printPluginInfo(mInfo, load);
    LogDebug.printMemoryStatus(LogDebug.TAG, "act=, loadLocked, flag=, End-1, pn=, " + mInfo.getName() + ", type=, " + load);
    }
    }
    try {
    // 至此,该插件已开始运行
    PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
    } catch (Throwable e) {
    if (LOGR) {
    LogRelease.e(PLUGIN_TAG, "p.u.1: " + e.getMessage(), e);
    }
    }
    return true;
    }
    //
    logTag = "try2";
    lock = new ProcessLocker(context, lockFileName);
    if (!lock.tryLockTimeWait(5000, 10)) {
    // 此处仅仅打印错误
    if (LOGR) {
    LogRelease.w(PLUGIN_TAG, logTag + ": failed to lock: can't wait plugin ready");
    }
    }
    // 清空数据对象
    mLoader = null;
    // 删除优化dex文件
    File odex = mInfo.getDexFile();
    if (odex.exists()) {
    if (LOG) {
    LogDebug.d(PLUGIN_TAG, logTag + ": delete exist odex=" + odex.getAbsolutePath());
    }
    odex.delete();
    }
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            // support for multidex below LOLLIPOP:delete Extra odex,if need
            try {
                FileUtils.forceDelete(mInfo.getExtraOdexDir());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        t1 = System.currentTimeMillis();
        rc = doLoad(logTag, context, parent, manager, load);
        if (LOG) {
            LogDebug.i(PLUGIN_TAG, "load2 " + mInfo.getPath() + " " + hashCode() + " c=" + load + " rc=" + rc + " delta=" + (System.currentTimeMillis() - t1));
        }
        //
        lock.unlock();
        if (!rc) {
            if (LOGR) {
                LogRelease.e(PLUGIN_TAG, logTag + ": loading fail2");
            }
            return false;
        }
    
        // 打印当前内存占用情况,只针对Dex和App加载做输出
        // 只有开启“详细日志”才会输出,防止“消耗性能”
        if (LOG && RePlugin.getConfig().isPrintDetailLog()) {
            if (load == LOAD_DEX || load == LOAD_APP) {
                LogDebug.printPluginInfo(mInfo, load);
                LogDebug.printMemoryStatus(LogDebug.TAG, "act=, loadLocked, flag=, End-2, pn=, " + mInfo.getName() + ", type=, " + load);
            }
        }
    
        try {
            // 至此,该插件已开始运行
            PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
        } catch (Throwable e) {
            if (LOGR) {
                LogRelease.e(PLUGIN_TAG, "p.u.2: " + e.getMessage(), e);
            }
        }
    
        return true;
    }
    
    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
    #### 2.2 进入插件后的startActivity()调用
    Factory2也只是起一个封装作用,即它只是调用PluginLibraryInternalProxy中相应的方法来完成操作,Factory2中的startActivity(Context,Intent)方法就是调用了PluginLibraryInternalProxy的startActivity(Context,Intent)方法:
    ​```java
    /**
    * @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);
    }

2.上下文替换