插件化框架解读之四大组件调用原理 -Activity(三)上篇
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {。。。}
第 2 步重点走读下上面 startActivity 方法,首先会判断要跳转的插件是否已经存在了,如果不存在则回调 callback 接口去提示用户下载等逻辑操作,如果插件状态不正确,则回调外部 callback 去提示用户插件不可用或者去升级,如果插件首次加载并且是大插件则异步加载并弹窗显示正在加载。然后在调用 context.startActivity 方法前会去通过下面的 loadPluginActivity 方法将目标插件 Activity class 替换为“坑位”Activity,这样其实传给 AMS 的还是宿主的坑位 Activity。PmInternalmpl.java
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {if (LOG) {LogDebug.d(PLUGIN_TAG, "start activity: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process + " download=" + download);}
// 1. 是否启动下载// 若插件不可用(不存在或版本不匹配),则直接弹出“下载插件”对话框// 因为已经打开 UpdateActivity,故在这里返回 True,告诉外界已经打开,无需处理 if (download) {if (PluginTable.getPluginInfo(plugin) == null) {if (LOG) {LogDebug.d(PLUGIN_TAG, "plugin=" + plugin + " not found, start download ...");}
// 如果用户在下载即将完成时突然点按“取消”,则有可能出现插件已下载成功,但没有及时加载进来的情况// 因此我们会判断这种情况,如果是,则重新加载一次即可,反之则提示用户下载// 原因:“取消”会触发 Task.release 方法,最终调用 mDownloadTask.destroy,导致“下载服务”的 Receiver 被注销,即使文件下载了也没有回调回来// NOTE isNeedToDownload 方法会调用 pluginDownloaded 再次尝试加载 if (isNeedToDownload(context, plugin)) {return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);}}}...// 2. 如果插件状态出现问题,则每次弹此插件的 Activity 都应提示无法使用,或提示升级(如有新版)// Added by Jiongxuan Zhangif (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {if (LOG) {LogDebug.d(PLUGIN_TAG, "PmInternalImpl.startActivity(): Plugin Disabled. pn=" + plugin);}return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);}
// 3. 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”// Added by Jiongxuan Zhangif (!RePlugin.isPluginDexExtracted(plugin)) {PluginDesc pd = PluginDesc.get(plugin);if (pd != null && pd.isLarge()) {...return RePlugin.getConfig().getCallbacks().onLoadLargePluginForActivity(context, plugin, intent, process);}}
// WARNING:千万不要修改 intent 内容,尤其不要修改其 ComponentName// 因为一旦分配坑位有误(或压根不是插件 Activity),则外界还需要原封不动的 startActivity 到系统中// 可防止出现“本来要打开宿主,结果被改成插件”,进而无法打开宿主 Activity 的问题
// 缓存打开前的 Intent 对象,里面将包括 Action 等内容 Intent from = new Intent(intent);
// 帮助填写打开前的 Intent 的 ComponentName 信息(如有。没有的情况如直接通过 Action 打开等)if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {from.setComponent(new ComponentName(plugin, activity));}
//4. 会去将目标 Activity 替换为坑位 ActivityComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);if (cn == null) {if (LOG) {LogDebug.d(PLUGIN_TAG, "plugin cn not found: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process);}return false
;}
// 将 Intent 指向到“坑位”。这样:// from:插件原 Intent// to:坑位 Intentintent.setComponent(cn);
//5. 调用系统 startActivity 方法 context.startActivity(intent);
// 6. 通知外界,已准备好要打开 Activity 了// 其中:from 为要打开的插件的 Intent,to 为坑位 IntentRePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);
return true;}
坑位 Activity 的替换规格,逻辑是在 PmLocalmpl.allocActivityContainer 方法中执行,这个有兴趣的可以自己去跟踪下,坑位 Activity 替换规格,这里就不深入去分析了。第 3 步上述几步使用坑位 Activity 来替换目标 Activity 之后,AMS 内部都是基于这个坑位 Activity 来实现对其合法性、生命周期进行回调啦,AMS 一系列流程走完,然后 AMS 再通过宿主进程的 ActivityThread 提供的 IApplicationThread Binder 代理对象去实现宿主进程中类加载坑位 Activity 啦,并对其回调相关接口如 onCreate。正常 startActivity 是这样没错,但是我们明明是希望调用的宿主的 Activity 啊,又不是坑位 Activity?是的这里需要对坑位 Activity 进行恢复真身处理,恢复为目标插件 Activity。在哪里恢复呢?没错,就是在我们唯一 hook 点:RePluginClassLoader 中。我们在《Replugin 插件化技术解读之框架初始化、插件安装与加载(二)》一文中已经分析的很明白,从 RePluginClassLoader 的 loadClass 方法中会首先去使用 PMF.loadClass 去加载插件,去生成插件的 Loader 对象,初始化好插件的 DexClassLoader、Resource、PluginContext 等资源,然后用插件的 ClassLoader 去尝试加载目标 Activity。具体执行逻辑在 PluginProcessPer.resolveActivityClass 方法中。
/**
类加载器根据容器解析到目标的 activity
@param container
@return*/final Class<?> resolveActivityClass(String container) {String plugin = null;String activity = null;
// 先找登记的,如果找不到,则用 forward activityPluginContainers.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;
Plugin p = mPluginMgr.loadAppPlugin(plugin);if (p == null) {return null;}ClassLoader cl = p.getClassLoader();if (LOG) {Class<?> c = null;try {c = cl.loadClass(activity);} catch (Throwable e) {if (LOGR) {LogRelease.e(PLUGIN_TAG, e.getMessage(), e);}}return c;}
这样我们插件目标 Activity 就恢复成功,目标 Activity 内部逻辑就能正常执行啦。总结:开启插件 Activity 的大致流程为:插件 Activity -> Replugin.startActivity -> 解析出插件 Activity 对应的 pluginName -> 挑选坑位 Activity 替换 ->调用系统 startActivity 方法 -> AMS 回调执行坑位 Activity 类加载 ->加载并初始化目标插件资源、上下文、类加载器(首次) ->Hook 掉 ClassLoader 的类加载中恢复为插件 Activity -> 插件 Activity 启动,并拥有完整生命周期。
评论