美文网首页
RePlugin之Activity启动流程

RePlugin之Activity启动流程

作者: 涛桑_ | 来源:发表于2019-07-12 19:01 被阅读0次

    前面我们了解了RePlugin插件化的基础, Hook 和 坑位
    在使用插件中的Activity时, 我们这样做的
    RePlugin.startActivity(MainActivity.this, RePlugin.createIntent("app-debug", "com.example.lib.SecondActivity"));
    按照这种使用方式我们递归下去分析
    Replugin.startActivity(),
    然后调用Factory.startActivityWithNoInjectCN
    再经过PluginCommImpl.startActivivty()
    最终来到PluginLibraryInternalProxy.startActivity()
    这里将是真正开始工作的地方,
    会分为以下几个步骤:

    • 可能会下载插件
    • 检查插件状态
    • 寻找坑位,启动坑位Activity
      这个里面进行了很多关于插件状态的判断, 以及处理逻辑, 例如下载, 插件状态不符合, 大插件等
      这些判断完成后呢, 回去调用这里
      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
            // 根据 activity 的 processName,选择进程 ID 标识
            if (ai.processName != null) {
                process = PluginClientHelper.getProcessInt(ai.processName);
            }
            // 容器选择(启动目标进程,如果有必要的话,一般默认会使用UI进程)
            IPluginClient client = MP.startPluginProcess(plugin, process, info);
          ......
            // 远程分配坑位
            container = client.allocActivityContainer(plugin, process, ai.name, intent);
        } catch (Throwable e) {
        }
    
        PmBase.cleanIntentPluginParams(intent);
      ......
        return new ComponentName(IPC.getPackageName(), container);
    }
    

    坑位分配,这是一个远程调用,调用了插件控制进程中的PluginProcessPer.allocActivityContainer函数,进一步调用bindActivity函数。

    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;
        // 自定义进程
        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;
    }
    

    租用坑位就发生在mACM.alloc2中,其中又调用了allocLocked, 这个里面就都是分配坑位的逻辑啦,
    照这个地方, 我们就知道了Activity的坑位,以及Actitvity插件的加载, 两者对应关系通过PluginContainer
    的state存储并一一对应, 好了
    我们只针对startActivity这一块调用栈找下去的, 最后上一张图片, 就更容易理解

    5977803-9e54a59a4a291746 (1).jpg
    引自作者:神罗天征_39a0

    针对上面的图, 我们来把上面概览以代码形式详细展开,直接从loadPluginActivity展开
    • getActivityInfo(plugin, activity, intent);
      对应图中第二步, 寻找该Activity的信息
    • container = client.allocActivityContainer(plugin, process, ai.name, intent); 远程分配坑位
      我们先来看这两步
    getActivityInfo
    /**
         * 根据条件,查找 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;
        }
    

    可以看到, 先是通过mPluginMgr(PMBase)的loadAppPlugin(plugin) 来加载对应插件, 然后在该插件中拿到对应的ActivityInfo信息, 可以看到, 当activity的名字为空时, Replugin提供了IntentMatcherHelper.getActivityInfo(mContext, plugin, intent); 来进行IntentFilter的匹配
    那我们这里有两点:

    1. 加载插件loadAppPlugin(plugin)
    2. 根据activity名字或者IntentFilter来从plugin中获取ActivityInfo
    loadAppPlugin
    final Plugin loadAppPlugin(String plugin) {
            return loadPlugin(mPlugins.get(plugin), Plugin.LOAD_APP, true);
        }
    

    看到这一段,mPlugins.get(plugin), 非常好, 跟我们之前的分析联系起来了, 首先关于mPlugins
    他是一个private final Map<String, Plugin> mPlugins = new ConcurrentHashMap<>(); 我们之前在哪里见到过他呢, 在关于Host启动流程那里PMF.init() 中对sPluginMgr.init(); 进行初始化, 我们没有分析的initForClient 和initForServer 将插件列表信息转到了sPluginMgr的mPlugin中, 那再看return loadPlugin(mPlugins.get(plugin), Plugin.LOAD_APP, true); 当然是获取插件信息 然后加载啦, 这里还要说个小点, 是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,并运行Entry类
        static final int LOAD_APP = 3;
    

    我们这里是要直接启动插件中的Activity 当然是用LOAD_APP这种形式啦, 我们在点进去, 发现他是调用了自身的加载load->loadLocked loadLocked方法很长, 但大部分代码简单的打印,判断, 不多看, 真正的加载逻辑是doLoad(), 我们看到doLoad也很长, 但是也比较简单,就是根据Plugin中信息的不同做了不同的加载, 当然都是有关与各种文件的, 我们这里需要LOAD_APP方式的加载, 找所有有关于LOAD_APP的分支, (感觉这里这么if else 其实可以重构一下)
    我们找到了一个Loader类的loadDex方法, 因为整体是按层级分出来的,LOAD_DEX 在倒数第二部分
    点进去, 更长, 这个里面就不细看了, 里面有关于mPackageInfo的加载, so库的路径的加载, 用于classLoaderd的参数中, 看到这里基本能想到, 接下来会有classLoader的生成, 还有部分对资源路径名字的缓存,又是一段段初始化判断,这里也不出意料的出现了classLoader的加载mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent); 当然是在没有缓存classLoader 的情况下, 到这里我们分析完了第二步, 即关于Activity信息的获取,顺便还可能会加载整个插件

    第三步 坑位一对一

    其实在刚开始我们已经看到了关于坑位的使用, 这里我们继续从源码角度去看坑位的对应
    container = client.allocActivityContainer(plugin, process, ai.name, intent); 远程分配坑位
    一看这里就用到了aidl, 我们点进去瞧瞧, 看到源码有点少我就放心了

    @Override
        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) {
                    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;
        }
    

    关于这部分我没有看太懂, 关于这个坑位的判断的, 能看到bindActivity, 顾名思义, 连接Activity, 当然是在里面做了关于坑位Activity一对一的事情啦,

    /**
         * 加载插件;找到目标Activity;搜索匹配容器;加载目标Activity类;建立临时映射;返回容器
         *
         * @param plugin   插件名称
         * @param process  进程
         * @param activity Activity 名称
         * @param intent   调用者传入的 Intent
         * @return 坑位
         */
        final String bindActivity(String plugin, int process, String activity, Intent intent) {
    
            /* 获取 Container */
            String container;
    
            // 自定义进程
            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;
        }
    

    这个地方有两点好看的, 我们还是本着dfs的原则, 先深入下去 看alloc|alloc2的分配, 都到了allocLocked这个地方

     /**
         * @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) {
            // 坑和状态的 map 为空
            if (map == null) {
                if (LOG) {
                    LogDebug.d(PLUGIN_TAG, "PACM: alloc fail, map is null");
                }
                return null;
            }
    
            // 首先找上一个活的,或者已经注册的,避免多个坑到同一个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;
                }
            }
    
            // 新分配:找空白的,第一个
            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;
    
            // 重用:则找最老的那个
            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;
            }
    
            // 强挤:最后一招,挤掉:最老的那个
            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;
        }
    

    这里是一整段分配规则,最后返回了一个ActivityState, 里面包含对应关系,直到这里, 我们知道坑位分配完了之后会有ActivityState生成,存放到ActivityContainer中/** * 保存进程和进程中坑位状态的 Map */ private final Map<String, ProcessStates> mProcessStatesMap = new HashMap<>();
    到这里, 坑位就分配好了,回到上一层, 到了c = p.mLoader.mClassLoader.loadClass(activity);
    到了这里之后, 我们的Hook的ClassLoader就起作用了, 这里我们就要回到RePluginClassLoader的classLoader方法了, 看看他做了啥,直接看到了这个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);
        }
    

    看到了没, 这个sPluginMgr基本啥都管, 又是他, 他的类名不要搞错了,叫PmBase
    点进去看看, 又是一大串, 我们只看和Activity相关的,

            if (mContainerActivities.contains(className)) {
                Class<?> c = mClient.resolveActivityClass(className);
                if (c != null) {
                    return c;
                }
    

    看到了这个, mClient 是PluginProcessPer这个类

    /**
         * 类加载器根据容器解析到目标的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;
        }
    

    其实就是根据我们之前坑位生成的activity的对应的关系, 拿到相关的ActivityState存的信息, 包括其插件本身的classLoader, 坑位, 以及activity的信息, 然后呢,拿插件本身的classLoader加载本身的class, 到此,RePlugin便成功瞒过了系统, 用插件的Activity使用了, 当然, 关于lib包下, 我们还需要看RePluginActivity会做哪些改变, 这里就不分析啦, 具体我们还是可以看着上面的那个图, 照着源码dfs分析出来的, 除此之外, 更像感叹的是该源码设计之巧妙, 单一个Hook点, 便将整个加载交由了Replugin去做, 就是太复杂了。

    相关文章

      网友评论

          本文标题:RePlugin之Activity启动流程

          本文链接:https://www.haomeiwen.com/subject/glhjkctx.html