RePlugin之Activity啟動(dòng)流程

前面我們了解了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)用棧找下去的, 最后上一張圖片, 就更容易理解

5977803-9e54a59a4a291746 (1).jpg

引自作者:神羅天征_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):

  1. 加載插件loadAppPlugin(plugin)
  2. 根據(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ù)雜了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容