插件化要解决的一个关键的技术点就是Activity生命周期管理的问题。我们知道,在Android中,所有的Activity都必须注册在AndroidManifest中。这是因为,在Activity的启动过程中,系统要经过校验,如果没有在AndroidManifest中注册,启动Activity就会失败。在插件化方案中,宿主和插件是独立开发的,宿主的AndroidManifest是整个App的AndroidManifest,而插件的AndroidManifest,系统是不认的,这样就导致了插件Activity无法在AndroidManifest中注册,从而启动时校验失败的问题。所以RePlugin插件化框架要解决的首要问题就是如果让插件中的Activity通过系统的验证,通过系统来实现Activity生命周期管理的问题。
RePlugin使用了“占坑”方案来解决Activity必须在AndroidManifest注册的问题。我们通过在宿主的AndroidManifest中,注册了各种task类型的Activity,作为“坑位”,在启动插件中的Activity过程中,会使用“坑位”Activity替换掉插件的Activity,这样就会通过系统的验证过程了。我们也就实现了狸猫换太子,用“坑位”Activity替换掉了“需要启动的插件的Activity”。
PluginActivity中的startActivity方法:
@Override
public void startActivity(Intent intent) {
//
if (RePluginInternal.startActivity(this, intent)) {
// 这个地方不需要回调startActivityAfter,因为Factory2最终还是会回调回来,最终还是要走super.startActivity()
return;
}
super.startActivity(intent);
}
该方法首先调用RePluginInternal.startActivity(this, intent),如果成功,就返回,说明已经查找到相应的坑位了;如果失败,说明不需要坑位机制,直接交给系统启动即可。
调用RePluginInternal.startActivity(this, intent):
/**
* @param activity Activity上下文
* @param intent
* @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
* @hide 内部方法,插件框架使用
* 启动一个插件中的activity
* 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制
*/
public static boolean startActivity(Activity activity, Intent intent) {
if (!RePluginFramework.mHostInitialized) {
return false;
}
try {
Object obj = ProxyRePluginInternalVar.startActivity.call(null, activity, intent); //反射调用
if (obj != null) {
return (Boolean) obj;
}
} catch (Exception e) {
if (LogDebug.LOG) {
e.printStackTrace();
}
}
return false;
}
final String factory2 = "com.qihoo360.i.Factory2"; //Factory2类的路径
startActivity = new MethodInvoker(classLoader, factory2, "startActivity", new Class>[]{Activity.class, Intent.class}); //反射Factory2的startActivity方法。
通过反射调用的方式,直接调用宿主中,插件化框架中的Factory2类的startActivity方法。这时,代码逻辑由插件,进入宿主。
public static final boolean startActivity(Context context, Intent intent) {
return sPLProxy.startActivity(context, intent);
}
public boolean startActivity(Context context, Intent intent) {
……
……
// 调用“特殊版”的startActivity,不让自动填写ComponentName,防止外界再用时出错
return Factory.startActivityWithNoInjectCN(context, intent, plugin, name, process);
}
该函数做了大量逻辑处理工作:
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
……
// 缓存打开前的Intent对象,里面将包括Action等内容
Intent from = new Intent(intent);
// 帮助填写打开前的Intent的ComponentName信息(如有。没有的情况如直接通过Action打开等)
if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {
from.setComponent(new ComponentName(plugin, 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);
if (LOG) {
LogDebug.d(PLUGIN_TAG, "start activity: real intent=" + intent);
}
context.startActivity(intent); //这里已经替换成坑位Activity了,接下来由系统启动即可。
// 通知外界,已准备好要打开Activity了
// 其中:from为要打开的插件的Intent,to为坑位Intent
RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);
return true;
}
该方法较长,主要执行内容如下:
到了这里,我们就实现了“坑位Activity”替换“插件Activity”,这样系统在启动Activity,执行后续校验逻辑时,会认为是已经在AndroidManifest中注册的“坑位Activity”在启动,校验过程就会顺利的通过。
Activity的校验过程不是本文的重点,我会在其他文章中进行分析。
到了这里,如果我们什么都不做,系统启动的Activity将是“坑位Activity”,这样对我们来说将没有意义,那么我们如何在启动前(通过系统校验之后),将“坑位Activity”替换回真正要启动的“插件Activity”呢?
在“坑位Activity”替换“插件Activity”过程中,执行的所有操作,都是在宿主和插件中进行的,也就是他们都是在我们自己的APP端进行的。而Activity校验逻辑,是由ActivityManagerService来进行的,相对于我们的APP,ActivityManagerService就是服务端。我们使用“坑位Activity”在服务端通过“验证”之后,想要替换回我们真正想启动的“插件Activity”,有2个地方可以做替换:一个是在服务端处理完成之后,在服务端进行,这个难度太高,而且稳定性也不好,pass了;另一个地方就是回到APP端之后,再进行替换,RePlugin使用的就是这种方式。
我们来看具体过程。
我们首先需要选择一个Hook点,在启动Activity之前,把“坑位Activity”替换回“插件Activity”。这个Hook点,RePlugin非常巧妙的使用了ClassLoader。
这样,我们就可以使用ClassLoader,在Activity对象加载时,将坑位Activity”替换回“插件Activity”了。
在RePlugin中,RePluginClassLoader就是我们自定义的ClassLoader类,它必须继承自PathClassLoader类。
RePluginClassLoader的loadClass方法:
@Override
protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException {
//
Class> c = null;
c = PMF.loadClass(className, resolve);
if (c != null) {
return c;
}
//
try {
c = mOrig.loadClass(className);
// 只有开启“详细日志”才会输出,防止“刷屏”现象
if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
LogDebug.d(TAG, "loadClass: load other class, cn=" + className);
}
return c;
} catch (Throwable e) {
//
}
//
return super.loadClass(className, resolve);
}
过程如下:
我们来看PMF的loadClass方法:
public static final Class> loadClass(String className, boolean resolve) {
return sPluginMgr.loadClass(className, resolve);
}
转发调用PmBase类的loadClass方法:
final Class> loadClass(String className, boolean resolve) {
// 加载Service中介坑位
……
if (mContainerActivities.contains(className)) {
Class> c = mClient.resolveActivityClass(className); //将“坑位Activity”替换为目标“插件Activity”
if (c != null) {
return c;
}
……
return DummyActivity.class; //空Activity,防止崩溃
}
……
“坑位Service”解析
……
坑位Provider”解析
……
……
return loadDefaultClass(className);
}
该方法实现:
这里我们重点来看将“坑位Activity”替换为目标“插件Activity”的PluginProcessPer类的resolveActivityClass方法:
final Class> resolveActivityClass(String container) {
String plugin = null;
String activity = null;
// 先找登记的,如果找不到,则用forward activity
PluginContainers.ActivityState state = mACM.lookupByContainer(container); //找到“坑位”对应的Activity信息
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; //获得“坑位Activity”所对应的目标“插件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(); //找到插件的ClassLoader对象
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”替换为目标“插件Activity”的过程。
使用“坑位”方案,通过系统逻辑验证过程:
“坑位Activity”替换为目标“插件Activity”的过程: