前面我们了解了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这一块调用栈找下去的, 最后上一张图片, 就更容易理解
引自作者:神罗天征_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的匹配
那我们这里有两点:
- 加载插件loadAppPlugin(plugin)
- 根据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
我们之前在哪里见到过他呢, 在关于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 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
到这里, 坑位就分配好了,回到上一层, 到了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去做, 就是太复杂了。