美文网首页
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