RePlugin.startActivity
PmInternalImpl.startActivity
PmLocalImpl.loadPluginActivity
PmLocalImpl.getActivityInfo
MP.startPluginProcess
client.allocActivityContainer
下面是详细代码
// ** PmInternalImpl.startActivity **
// 这里先跳过非常规流程的代码,就是直接调用了PmLocalImpl.loadPluginActivity
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
if (download) {
// 下载流程,跳过
}
if (Factory2.isDynamicClass(plugin, activity)) {
// 动态注册的类,跳过
}
// 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)
// Added by Jiongxuan Zhang
if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {
// ...
}
// 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”
// Added by Jiongxuan Zhang
if (!RePlugin.isPluginDexExtracted(plugin)) {
// ...
}
// 缓存打开前的Intent对象,里面将包括Action等内容
Intent from = new Intent(intent);
// 帮助填写打开前的Intent的ComponentName信息(如有。没有的情况如直接通过Action打开等)
if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {
from.setComponent(new ComponentName(plugin, activity));
}
// ********* 关键方法,加载plugin并且分配坑位
ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
if (cn == null) {
// ...
}
// 将Intent指向到“坑位”。这样:
// from:插件原Intent
// to:坑位Intent
intent.setComponent(cn);
context.startActivity(intent);
// 通知外界,已准备好要打开Activity了
// 其中:from为要打开的插件的Intent,to为坑位Intent
RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);
return true;
}
// ** PmLocalImpl.loadPluginActivity **
// 主要是下面标出来的3步
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 {
// 1、获取ActivityInfo,期间会加载插件
ai = getActivityInfo(plugin, activity, intent);
if (ai == null) {
// ...
}
// 存储此 Activity 在插件 Manifest 中声明主题到 Intent
intent.putExtra(INTENT_KEY_THEME_ID, ai.theme);
// 根据 activity 的 processName,选择进程 ID 标识
if (ai.processName != null) {
process = PluginClientHelper.getProcessInt(ai.processName);
}
// 2、容器选择(启动目标进程)
IPluginClient client = MP.startPluginProcess(plugin, process, info); // 就是PluginProcessPer
if (client == null) {
return null;
}
// 3、远程分配坑位
container = client.allocActivityContainer(plugin, process, ai.name, intent);
// ...
} catch (Throwable e) {
// ...
}
PmBase.cleanIntentPluginParams(intent);
return new ComponentName(IPC.getPackageName(), container);
}
第一步 加载插件
// ** PmLocalImpl.getActivityInfo **
// 主要是第一步:加载插件
public ActivityInfo getActivityInfo(String plugin, String activity, Intent intent) {
// 1、获取插件对象
Plugin p = mPluginMgr.loadAppPlugin(plugin);
if (p == null) {
// ...
}
ActivityInfo ai = null;
// 2、activity 不为空时,从插件声明的 Activity 集合中查找
if (!TextUtils.isEmpty(activity)) {
ai = p.mLoader.mComponents.getActivity(activity);
} else {
// activity 为空时,根据 Intent 匹配
ai = IntentMatcherHelper.getActivityInfo(mContext, plugin, intent);
}
return ai;
}
final boolean load(int load, boolean useCache) {
PluginInfo info = mInfo;
// 1、加载插件
boolean rc = loadLocked(load, useCache);
// 2、通过反射,调用插件的Application.onCreate
if (load == LOAD_APP && rc) {
callApp();
}
// 如果info改了,通知一下常驻
// 只针对P-n的Type转化来处理,一定要通知,这样Framework_Version也会得到更新
if (rc && mInfo != info) {
UpdateInfoTask task = new UpdateInfoTask((PluginInfo) mInfo.clone());
Tasks.post2Thread(task);
}
return rc;
}
// Plugin.doLoad
private final boolean doLoad(String tag, Context context, ClassLoader parent, IPluginManager manager, int load) {
if (mLoader == null) {
// 试图释放文件
PluginInfo info = null;
if (mInfo.getType() == PluginInfo.TYPE_BUILTIN) {
// 1、这里,把插件拷贝到指定目录下,同时释放so到指定目录
File dir = context.getDir(Constant.LOCAL_PLUGIN_SUB_DIR, 0);
File dexdir = mInfo.getDexParentDir();
String dstName = mInfo.getApkFile().getName();
boolean rc = AssetsUtils.quickExtractTo(context, mInfo, dir.getAbsolutePath(), dstName, dexdir.getAbsolutePath());
if (!rc) {
// ...
}
File file = new File(dir, dstName);
// 2、根据file名称构造pluginInfo的基本信息
info = PluginInfo.build(file);
if (info == null) {
return false;
}
// 不会改变
} else if (mInfo.getType() == PluginInfo.TYPE_PN_JAR) {
// ...
} else {
// ...
}
//
if (info != null) {
// 替换
mInfo = info;
}
// 3、构造plugin的Loader信息
mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
// 4、解析Dex,这里做的事情比较多,包括:
// 1) 通过PackageManager解析到plugin的packageInfo,并替换sourceDir,publicSourceDir, nativeLibraryDir
// 2) 通过第一步的packageInfo和解析manifest,缓存componentList;注册receiver(就是通过统一的receiver分发);调整process和taskAffinity
// 3) 通过packageManager得到resource对象
// 4) 构造classLoader,即PluginDexClassLoader
// 5) 构造Context
if (!mLoader.loadDex(parent, load)) {
return false;
}
// 5、更新plugin对应的状态
try {
PluginManagerProxy.updateUsedIfNeeded(mInfo.getName(), true);
} catch (RemoteException e) {
// ...
}
// 6、加载plugin的Entry类,并调用其create方法;Entry还没弄明白,估计要看插件library的代码 *******
if (load == LOAD_APP) {
// NOTE Entry对象是可以在任何线程中被调用到
if (!loadEntryLocked(manager)) {
return false;
}
// ...
}
}
if (load == LOAD_INFO) {
return mLoader.isPackageInfoLoaded();
} else if (load == LOAD_RESOURCES) {
return mLoader.isResourcesLoaded();
} else if (load == LOAD_DEX) {
return mLoader.isDexLoaded();
} else {
return mLoader.isAppLoaded();
}
}
到这里就做完了plugin加载,会到上面的getActivityInfo中,加载完插件之后,会通过解析到的ComponentList得到ActivityInfo。
然后,回到loadPluginActivity方法,看看第二步
第二步 启动目标进程
调用的是MP.startPluginProcess方法,最终是跨进程调用到了persist进程,PmHostSvc.startPluginProcess方法,最后调用PmBase.startPluginProcessLocked方法
// PmBase.startPluginProcessLocked
final IPluginClient startPluginProcessLocked(String plugin, int process, PluginBinderInfo info) {
// 强制使用UI进程
if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) {
if (info.request == PluginBinderInfo.ACTIVITY_REQUEST) {
if (process == IPluginManager.PROCESS_AUTO) {
process = IPluginManager.PROCESS_UI;
}
}
// ...
}
// 1、这里执行CHECK任务,检查几个process record的状态,但是还没有看明白,在什么情况下会启动这样的进程
// 执行方法在PluginProcessMain.attachProcessLocked,达到的条件比较苛刻
PluginProcessMain.schedulePluginProcessLoop(PluginProcessMain.CHECK_STAGE1_DELAY);
// 2、尝试获取client,因为这里是从主进程过来的,所以能匹配到主进程之前注册进来的client,也就是PluginProcessPer,
// 存放在PluginProcessMain.ALL成员变量里的
IPluginClient client = PluginProcessMain.probePluginClient(plugin, process, info);
if (client != null) {
return client;
}
// 3、获取失败,则尝试分配,这里最后的条件跟第1步里的一样,从PluginProcessMain.PROCESS中查找分配
// 看代码逻辑还没都写完...,所以应该很少会走到这里
int index = IPluginManager.PROCESS_AUTO;
try {
index = PluginProcessMain.allocProcess(plugin, process);
} catch (Throwable e) {
// ...
}
// 分配的坑位不属于UI、和自定义进程,就返回。
if (!(index == IPluginManager.PROCESS_UI
|| PluginProcessHost.isCustomPluginProcess(index)
|| PluginManager.isPluginProcess(index))) {
return null;
}
// 4、获取到Process index后,尝试启动,如果是主进程调用,在第2步应该就会结束;走到这里的是plugin自定义进程吗
// 比如自定义了:process,个人感觉是;这里启动进程的方式是,通过ProcessPitProviderBase.insert(也就是ContentProvider)
// 启动provider对应的进程,在insert方法里会调用PluginProviderStub.stubPlugin,这个方法会把新启动的进程的client注册
// 到persist进程的PluginProcessMain.All中,这样下次在第2步就能找到了
// 所以,为什么进程信息都保存在persist进程中呢,是因为可能有多个plugin的组件(如provider)运行在同一个进程里?个人觉得是这样
boolean rc = PluginProviderStub.proxyStartPluginProcess(mContext, index);
if (!rc) {
return null;
}
// 5、再次获取,经过第4步,应该能获取到了
client = PluginProcessMain.probePluginClient(plugin, process, info);
if (client == null) {
return null;
}
return client;
}
第三步 分配坑位
第二步获取到了activity对应进程的PluginProcessPer对象,第三步就调用PluginProcessPer.allocActivityContainer来分配坑位。
在初始化的时候,PluginProcessPer分配了多个map来对应不同的activity,这里的分配就是在map中找到合适的坑位,真正的分配放在了PluginContainers类中,看看它的实现
private final ActivityState allocLocked(ActivityInfo ai, HashMap<String, ActivityState> map,
String plugin, String activity, Intent intent) {
// 坑和状态的 map 为空
if (map == null) {
return null;
}
// 首先找上一个活的,或者已经注册的,避免多个坑到同一个activity的映射
// 也就是找一个已经分配给activity的坑
for (ActivityState state : map.values()) {
if (state.isTarget(plugin, activity)) {
return state;
}
}
// 新分配:找空白的,第一个
for (ActivityState state : map.values()) {
if (state.state == STATE_NONE) {
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) {
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) {
found.finishRefs();
found.occupy(plugin, activity);
return found;
}
// never reach here
return null;
}
分配坑位的方法如上,很清晰。只是如果坑位不够了怎么办?后面classLoader会根据这里分配的坑位进行对真正的activity类进行加载,这个过程很快,那么是不是loadClass之后坑位的对应就没有用处了?好像是的。后续分析Replugin代码的时候继续思考这个问题。
==到这里,加载和分配坑位结束了,startActivity即可,走的是android正常的流程,之后在ActivityThread new activity的时候,因为hack了classLoader,所以会走RepluginClassLoader,也是在这里把坑位的类替换成了真正的类。下面看一看这个流程==
第四步 类加载
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
//
Class<?> c = null;
// 1、Replugin框架loadClass
c = PMF.loadClass(className, resolve);
if (c != null) {
return c;
}
//
try {
// 2、用原始的PathClassLoader来loadClass
c = mOrig.loadClass(className);
return c;
} catch (Throwable e) {
//
}
// 3、实际上没什么用,因为在构造函数里什么也没有给父类传
return super.loadClass(className, resolve);
}
这里流程很清晰
PMF.loadClass最终调用到的就是PluginDexClassLoader.loadClass,它会加载插件里的类,如果没有找到类的话,会再调用RepluginClassLoader.loadClass尝试加载宿主的类,这里不会形成循环调用,因为调用关系是RepluginClassLoader.loadClass(坑位的类) ->PluginDexClassLoader.loadClass(坑位对应的activity类)->RepluginClassLoader.loadClass(坑位对应的activity类,这里不会再调用PluginDexClassLoader)