前面我們了解了RePlugin插件化的基礎(chǔ), Hook 和 坑位
在使用插件中的Activity時(shí), 我們這樣做的
RePlugin.startActivity(MainActivity.this, RePlugin.createIntent("app-debug", "com.example.lib.SecondActivity"));
按照這種使用方式我們遞歸下去分析
Replugin.startActivity(),
然后調(diào)用Factory.startActivityWithNoInjectCN,
再經(jīng)過PluginCommImpl.startActivivty(),
最終來到PluginLibraryInternalProxy.startActivity(),
這里將是真正開始工作的地方,
會(huì)分為以下幾個(gè)步驟:
- 可能會(huì)下載插件
- 檢查插件狀態(tài)
- 尋找坑位,啟動(dòng)坑位Activity
這個(gè)里面進(jìn)行了很多關(guān)于插件狀態(tài)的判斷, 以及處理邏輯, 例如下載, 插件狀態(tài)不符合, 大插件等
這些判斷完成后呢, 回去調(diào)用這里
ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
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 {
ai = getActivityInfo(plugin, activity, intent); //分支C:獲取 ActivityInfo
// 根據(jù) activity 的 processName,選擇進(jìn)程 ID 標(biāo)識
if (ai.processName != null) {
process = PluginClientHelper.getProcessInt(ai.processName);
}
// 容器選擇(啟動(dòng)目標(biāo)進(jìn)程,如果有必要的話,一般默認(rèn)會(huì)使用UI進(jìn)程)
IPluginClient client = MP.startPluginProcess(plugin, process, info);
......
// 遠(yuǎn)程分配坑位
container = client.allocActivityContainer(plugin, process, ai.name, intent);
} catch (Throwable e) {
}
PmBase.cleanIntentPluginParams(intent);
......
return new ComponentName(IPC.getPackageName(), container);
}
坑位分配,這是一個(gè)遠(yuǎn)程調(diào)用,調(diào)用了插件控制進(jìn)程中的PluginProcessPer.allocActivityContainer函數(shù),進(jìn)一步調(diào)用bindActivity函數(shù)。
final String bindActivity(String plugin, int process, String activity, Intent intent) {
Plugin p = mPluginMgr.loadAppPlugin(plugin); //獲取插件對象
......
ActivityInfo ai = p.mLoader.mComponents.getActivity(activity); //獲取ActivityInfo
......
String container;
// 自定義進(jìn)程
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);
}
......
return container;
}
租用坑位就發(fā)生在mACM.alloc2中,其中又調(diào)用了allocLocked, 這個(gè)里面就都是分配坑位的邏輯啦,
照這個(gè)地方, 我們就知道了Activity的坑位,以及Actitvity插件的加載, 兩者對應(yīng)關(guān)系通過PluginContainer
的state存儲(chǔ)并一一對應(yīng), 好了
我們只針對startActivity這一塊調(diào)用棧找下去的, 最后上一張圖片, 就更容易理解

引自作者:神羅天征_39a0
針對上面的圖, 我們來把上面概覽以代碼形式詳細(xì)展開,直接從loadPluginActivity展開
-
getActivityInfo(plugin, activity, intent);
對應(yīng)圖中第二步, 尋找該Activity的信息 -
container = client.allocActivityContainer(plugin, process, ai.name, intent);遠(yuǎn)程分配坑位
我們先來看這兩步
getActivityInfo
/**
* 根據(jù)條件,查找 ActivityInfo 對象
*
* @param plugin 插件名稱
* @param activity Activity 名稱
* @param intent 調(diào)用者傳遞過來的 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 不為空時(shí),從插件聲明的 Activity 集合中查找
if (!TextUtils.isEmpty(activity)) {
ai = p.mLoader.mComponents.getActivity(activity);
} else {
// activity 為空時(shí),根據(jù) Intent 匹配
ai = IntentMatcherHelper.getActivityInfo(mContext, plugin, intent);
}
return ai;
}
可以看到, 先是通過mPluginMgr(PMBase)的loadAppPlugin(plugin) 來加載對應(yīng)插件, 然后在該插件中拿到對應(yīng)的ActivityInfo信息, 可以看到, 當(dāng)activity的名字為空時(shí), Replugin提供了IntentMatcherHelper.getActivityInfo(mContext, plugin, intent); 來進(jìn)行IntentFilter的匹配
那我們這里有兩點(diǎn):
- 加載插件loadAppPlugin(plugin)
- 根據(jù)activity名字或者IntentFilter來從plugin中獲取ActivityInfo
loadAppPlugin
final Plugin loadAppPlugin(String plugin) {
return loadPlugin(mPlugins.get(plugin), Plugin.LOAD_APP, true);
}
看到這一段,mPlugins.get(plugin), 非常好, 跟我們之前的分析聯(lián)系起來了, 首先關(guān)于mPlugins
他是一個(gè)private final Map<String, Plugin> mPlugins = new ConcurrentHashMap<>(); 我們之前在哪里見到過他呢, 在關(guān)于Host啟動(dòng)流程那里PMF.init() 中對sPluginMgr.init(); 進(jìn)行初始化, 我們沒有分析的initForClient 和initForServer 將插件列表信息轉(zhuǎn)到了sPluginMgr的mPlugin中, 那再看return loadPlugin(mPlugins.get(plugin), Plugin.LOAD_APP, true); 當(dāng)然是獲取插件信息 然后加載啦, 這里還要說個(gè)小點(diǎn), 是RePlugin里對插件的加載有各種程度,
// 只加載Service/Activity/ProviderInfo信息(包含ComponentList)
static final int LOAD_INFO = 0;
// 加載插件信息和資源
static final int LOAD_RESOURCES = 1;
// 加載插件信息、資源和Dex
static final int LOAD_DEX = 2;
// 加載插件信息、資源、Dex,并運(yùn)行Entry類
static final int LOAD_APP = 3;
我們這里是要直接啟動(dòng)插件中的Activity 當(dāng)然是用LOAD_APP這種形式啦, 我們在點(diǎn)進(jìn)去, 發(fā)現(xiàn)他是調(diào)用了自身的加載load->loadLocked loadLocked方法很長, 但大部分代碼簡單的打印,判斷, 不多看, 真正的加載邏輯是doLoad(), 我們看到doLoad也很長, 但是也比較簡單,就是根據(jù)Plugin中信息的不同做了不同的加載, 當(dāng)然都是有關(guān)與各種文件的, 我們這里需要LOAD_APP方式的加載, 找所有有關(guān)于LOAD_APP的分支, (感覺這里這么if else 其實(shí)可以重構(gòu)一下)
我們找到了一個(gè)Loader類的loadDex方法, 因?yàn)檎w是按層級分出來的,LOAD_DEX 在倒數(shù)第二部分
點(diǎn)進(jìn)去, 更長, 這個(gè)里面就不細(xì)看了, 里面有關(guān)于mPackageInfo的加載, so庫的路徑的加載, 用于classLoaderd的參數(shù)中, 看到這里基本能想到, 接下來會(huì)有classLoader的生成, 還有部分對資源路徑名字的緩存,又是一段段初始化判斷,這里也不出意料的出現(xiàn)了classLoader的加載mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent); 當(dāng)然是在沒有緩存classLoader 的情況下, 到這里我們分析完了第二步, 即關(guān)于Activity信息的獲取,順便還可能會(huì)加載整個(gè)插件
第三步 坑位一對一
其實(shí)在剛開始我們已經(jīng)看到了關(guān)于坑位的使用, 這里我們繼續(xù)從源碼角度去看坑位的對應(yīng)
container = client.allocActivityContainer(plugin, process, ai.name, intent); 遠(yuǎn)程分配坑位
一看這里就用到了aidl, 我們點(diǎn)進(jìn)去瞧瞧, 看到源碼有點(diǎn)少我就放心了
@Override
public String allocActivityContainer(String plugin, int process, String target, Intent intent) throws RemoteException {
// 一旦有分配,則進(jìn)入監(jiān)控狀態(tài)(一是避免不退出的情況,二也是最重要的是避免現(xiàn)在就退出的情況)
RePlugin.getConfig().getEventCallbacks().onPrepareAllocPitActivity(intent);
String loadPlugin = null;
// 如果UI進(jìn)程啟用,嘗試使用傳過來的插件,強(qiáng)制用UI進(jìn)程
if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) {
if (IPC.isUIProcess()) {
loadPlugin = plugin;
process = IPluginManager.PROCESS_UI;
} else {
loadPlugin = plugin;
}
}
// 如果不成,則再次嘗試使用默認(rèn)插件
if (TextUtils.isEmpty(loadPlugin)) {
if (mDefaultPlugin == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "a.a.c p i n");
}
return null;
}
loadPlugin = mDefaultPlugin.mInfo.getName();
}
//
String container = bindActivity(loadPlugin, process, target, intent);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: eval plugin " + loadPlugin + ", target=" + target + ", container=" + container);
}
return container;
}
關(guān)于這部分我沒有看太懂, 關(guān)于這個(gè)坑位的判斷的, 能看到bindActivity, 顧名思義, 連接Activity, 當(dāng)然是在里面做了關(guān)于坑位Activity一對一的事情啦,
/**
* 加載插件;找到目標(biāo)Activity;搜索匹配容器;加載目標(biāo)Activity類;建立臨時(shí)映射;返回容器
*
* @param plugin 插件名稱
* @param process 進(jìn)程
* @param activity Activity 名稱
* @param intent 調(diào)用者傳入的 Intent
* @return 坑位
*/
final String bindActivity(String plugin, int process, String activity, Intent intent) {
/* 獲取 Container */
String container;
// 自定義進(jìn)程
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);
}
/* 檢查 activity 是否存在 */
Class<?> c = null;
try {
c = p.mLoader.mClassLoader.loadClass(activity);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
}
}
if (c == null) {
if (LOG) {
LogDebug.w(PLUGIN_TAG, "PACM: bindActivity: plugin activity class not found: c=" + activity);
}
return null;
}
return container;
}
這個(gè)地方有兩點(diǎn)好看的, 我們還是本著dfs的原則, 先深入下去 看alloc|alloc2的分配, 都到了allocLocked這個(gè)地方
/**
* @param ai
* @param map
* @param plugin
* @param activity
* @param intent
* @return
*/
private final ActivityState allocLocked(ActivityInfo ai, HashMap<String, ActivityState> map,
String plugin, String activity, Intent intent) {
// 坑和狀態(tài)的 map 為空
if (map == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: alloc fail, map is null");
}
return null;
}
// 首先找上一個(gè)活的,或者已經(jīng)注冊的,避免多個(gè)坑到同一個(gè)activity的映射
for (ActivityState state : map.values()) {
if (state.isTarget(plugin, activity)) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: alloc registered container=" + state.container);
}
return state;
}
}
// 新分配:找空白的,第一個(gè)
for (ActivityState state : map.values()) {
if (state.state == STATE_NONE) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: alloc empty container=" + state.container);
}
state.occupy(plugin, activity);
return state;
}
}
ActivityState found;
// 重用:則找最老的那個(gè)
found = null;
for (ActivityState state : map.values()) {
if (!state.hasRef()) {
if (found == null) {
found = state;
} else if (state.timestamp < found.timestamp) {
found = state;
}
}
}
if (found != null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: alloc recycled container=" + found.container);
}
found.occupy(plugin, activity);
return found;
}
// 強(qiáng)擠:最后一招,擠掉:最老的那個(gè)
found = null;
for (ActivityState state : map.values()) {
if (found == null) {
found = state;
} else if (state.timestamp < found.timestamp) {
found = state;
}
}
if (found != null) {
if (LOG) {
LogDebug.w(PLUGIN_TAG, "PACM: force alloc container=" + found.container);
}
found.finishRefs();
found.occupy(plugin, activity);
return found;
}
if (LOG) {
LogDebug.w(PLUGIN_TAG, "PACM: alloc failed: plugin=" + plugin + " activity=" + activity);
}
// never reach here
return null;
}
這里是一整段分配規(guī)則,最后返回了一個(gè)ActivityState, 里面包含對應(yīng)關(guān)系,直到這里, 我們知道坑位分配完了之后會(huì)有ActivityState生成,存放到ActivityContainer中/** * 保存進(jìn)程和進(jìn)程中坑位狀態(tài)的 Map */ private final Map<String, ProcessStates> mProcessStatesMap = new HashMap<>();
到這里, 坑位就分配好了,回到上一層, 到了c = p.mLoader.mClassLoader.loadClass(activity);
到了這里之后, 我們的Hook的ClassLoader就起作用了, 這里我們就要回到RePluginClassLoader的classLoader方法了, 看看他做了啥,直接看到了這個(gè)c = PMF.loadClass(className, resolve);
好了 開始dfs分析
/**
* @param className
* @param resolve
* @return
*/
public static final Class<?> loadClass(String className, boolean resolve) {
return sPluginMgr.loadClass(className, resolve);
}
看到了沒, 這個(gè)sPluginMgr基本啥都管, 又是他, 他的類名不要搞錯(cuò)了,叫PmBase
點(diǎn)進(jìn)去看看, 又是一大串, 我們只看和Activity相關(guān)的,
if (mContainerActivities.contains(className)) {
Class<?> c = mClient.resolveActivityClass(className);
if (c != null) {
return c;
}
看到了這個(gè), mClient 是PluginProcessPer這個(gè)類
/**
* 類加載器根據(jù)容器解析到目標(biāo)的activity
* @param container
* @return
*/
final Class<?> resolveActivityClass(String container) {
String plugin = null;
String activity = null;
// 先找登記的,如果找不到,則用forward activity
PluginContainers.ActivityState state = mACM.lookupByContainer(container);
if (state == null) {
// PACM: loadActivityClass, not register, use forward activity, container=
if (LOGR) {
LogRelease.w(PLUGIN_TAG, "use f.a, c=" + container);
}
return ForwardActivity.class;
}
plugin = state.plugin;
activity = state.activity;
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: loadActivityClass in=" + container + " target=" + activity + " plugin=" + plugin);
}
Plugin p = mPluginMgr.loadAppPlugin(plugin);
if (p == null) {
// PACM: loadActivityClass, not found plugin
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "load fail: c=" + container + " p=" + plugin + " t=" + activity);
}
return null;
}
ClassLoader cl = p.getClassLoader();
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: loadActivityClass, plugin activity loader: in=" + container + " activity=" + activity);
}
Class<?> c = null;
try {
c = cl.loadClass(activity);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
}
}
if (LOG) {
LogDebug.d(PLUGIN_TAG, "PACM: loadActivityClass, plugin activity loader: c=" + c + ", loader=" + cl);
}
return c;
}
其實(shí)就是根據(jù)我們之前坑位生成的activity的對應(yīng)的關(guān)系, 拿到相關(guān)的ActivityState存的信息, 包括其插件本身的classLoader, 坑位, 以及activity的信息, 然后呢,拿插件本身的classLoader加載本身的class, 到此,RePlugin便成功瞞過了系統(tǒng), 用插件的Activity使用了, 當(dāng)然, 關(guān)于lib包下, 我們還需要看RePluginActivity會(huì)做哪些改變, 這里就不分析啦, 具體我們還是可以看著上面的那個(gè)圖, 照著源碼dfs分析出來的, 除此之外, 更像感嘆的是該源碼設(shè)計(jì)之巧妙, 單一個(gè)Hook點(diǎn), 便將整個(gè)加載交由了Replugin去做, 就是太復(fù)雜了。