在《Replugin插件化技术解读之框架初始化、插件安装与加载(一)》和《Replugin插件化技术解读之框架初始化、插件安装与加载(二)》中讲解了Replugin插件框架的初始化以及外置、内置插件的安装加载,那么到这里整个插件框架的所有准备初始化工作都执行完毕啦,我们从现在起就可以愉快的调用插件中的四大组件来玩耍啦。
一、开启插件Activity流程
第1步:开启插件Activity的入口在Replugin.StartActivity(Context context, Intent intent),其他intent内部一定要传入想要开启的插件名。
public static boolean startActivity(Context context, Intent intent) {
// TODO 先用旧的开启Activity方案,以后再优化
ComponentName cn = intent.getComponent();
if (cn == null) {
// TODO 需要支持Action方案
return false;
}
String plugin = cn.getPackageName();
String cls = cn.getClassName();
return Factory.startActivityWithNoInjectCN(context, intent, plugin, cls, IPluginManager.PROCESS_AUTO);
}
Factory.startActivityWithNoInjectCN内部调用sPluginManager.startActivity方法,那么sPluginManager是哪里传进去的?
PMF.java
Factory.sPluginManager = PMF.getLocal();
Factory2.sPluginManager = PMF.getInternal();
看下PMF.init的方法可以看到sPluginManager是PMF内部维护的sPluginMgr.mLocal,也就是Pmbase内部的PmLocalmpl mLocal对象,继续跟踪发现真正实现startActivity逻辑的是PmBase内部的PmInternalmpl.startActivity下面这个方法。
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。
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 Zhang
if (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 Zhang
if (!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替换为坑位Activity
ComponentName 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:坑位Intent
intent.setComponent(cn);
//5. 调用系统startActivity方法
context.startActivity(intent);
// 6. 通知外界,已准备好要打开Activity了
// 其中:from为要打开的插件的Intent,to为坑位Intent
RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);
return true;
}
坑位Activity的替换规格,逻辑是在PmLocalmpl.allocActivityContainer方法中执行,这个有兴趣的可以自己去跟踪下,坑位
Activity替换规格,这里就不深入去分析了。
/**
* 类加载器根据容器解析到目标的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;
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内部逻辑就能正常执行啦。
在《Replugin插件化技术解读之目录结构解读》一文中,我们了解到其实插件在动态编译过程中会在字节码层做适量修改,如插件的所有Activity都会被继承replugin-plugin-lib中的PluginActivity这样一个基类。所以我们插件内部其实Activity内部最后执行startActivity都会先走PluginActivity的此方法;
PluginActivity.java
static void initLocked(final ClassLoader classLoader) {
final String factory2 = "com.qihoo360.i.Factory2";
final String factory = "com.qihoo360.i.Factory";
// 初始化Factory2相关方法
createActivityContext = new MethodInvoker(classLoader, factory2, "createActivityContext", new Class>[]{Activity.class, Context.class});
handleActivityCreateBefore = new MethodInvoker(classLoader, factory2, "handleActivityCreateBefore", new Class>[]{Activity.class, Bundle.class});
handleActivityCreate = new MethodInvoker(classLoader, factory2, "handleActivityCreate", new Class>[]{Activity.class, Bundle.class});
handleActivityDestroy = new MethodInvoker(classLoader, factory2, "handleActivityDestroy", new Class>[]{Activity.class});
handleRestoreInstanceState = new MethodInvoker(classLoader, factory2, "handleRestoreInstanceState", new Class>[]{Activity.class, Bundle.class});
startActivity = new MethodInvoker(classLoader, factory2, "startActivity", new Class>[]{Activity.class, Intent.class});
startActivityForResult = new MethodInvoker(classLoader, factory2, "startActivityForResult", new Class>[]{Activity.class, Intent.class, int.class, Bundle.class});
// 初始化Factory相关方法
loadPluginActivity = new MethodInvoker(classLoader, factory, "loadPluginActivity", new Class>[]{Intent.class, String.class, String.class, int.class});
显然,反射的是Factory2中的startActivity方法,而Factory.startActivity其实就是调用的PmInternalmpl.startActivity方法,显然这个跟上面宿主启动插件Activity对上啦。
那么RepluginInternal.initLocked方法是什么时候走的呢?容易看到是在RepluginFramework.init中调用,而RepluginFramework.init初始化时在Entry的create方法中,这里是不是很熟悉啦。没错,在《Replugin插件化技术解读之框架初始化、插件安装与加载(二)》一文中我们知道在加载插件的时候会通过回调执行插件的Entry的create方法初始化插件,显然这里这条线又连上啦。
这样插件内部启动startActivity说白了就是通过反射调用宿主Factory2中startActivity方法去重走了遍上述分析的标准启动插件Activity流程。注意,这里其实仅仅是通过插件Activity继承PluginActivity重写了Activity内部StartActivity方法,改变了Activity内startActivity方法启动步骤。
我们显然还要修改Context上下文对应的startActivity方法才能保证这里调用的时候也会走到我们设定好的宿主startActivityq启动流程,
那么插件内部其他startActivity的地方都要进行这样的调整才行,这个就大家自己跟下就能明白了,原理差不多。
public abstract class PluginActivity extends Activity {
@Override
protected void attachBaseContext(Context newBase) {
newBase = RePluginInternal.createActivityContext(this, newBase);
super.attachBaseContext(newBase);
}
PluginActivity的attachBaseContext方法中我们会使用RepluginInternal.createActivityContext方法,通过反射,最终吊起来PmInternalImpl.createActivityContext方法。创建对应插件的PluginContext对象,PluginContext前文已经介绍,然后用这个PluginContext替换掉插件内部的上下文BaseContext,其实也就是Activity继承的ContextWrapper类的Context mBase对象。跟踪Android源码很容易发现,ContextWrapper内部封装了startActivity startService等方法,所以这里我们要将这个Context替换掉。那么PluginContext内部又做了什么呢?显然它也重写了startActivity方法。
@Override
public void startActivity(Intent intent) {
if (mContextInjector != null) {
mContextInjector.startActivityBefore(intent);
}
super.startActivity(intent);
if (mContextInjector != null) {
mContextInjector.startActivityAfter(intent);
}
}
这里 super.startActivity其实就是对应宿主上下文了,直接走宿主startActivity流程。
其他还有在PluginApplicationClient为插件创建的Application对象,在PluginApplicationClient.callAttachBaseContext方法中使用反射调用了Application.attachf方法,用PluginContext对象替换了原来的Context,这个就各位看官自己去梳理了哈。