RePlugin插件化框架解析——Activity生命周期管理

RePlugin插件化框架解析——Activity生命周期管理插件化要解决的一个关键的技术点就是 Activity 生命周期管理的问题 我们知道 在 Android 中 所有的 Activity 都必须注册在 AndroidManif 中 这是因为 在 Activity 的启动过程中 系统要经过校验 如果没有在 AndroidManif 中注册 启动 Activity 就会失败 在插件化方案中 宿主和插件是独立开发的 宿主的 AndroidManif 是整个 App 的 And

插件化要解决的一个关键的技术点就是Activity生命周期管理的问题。我们知道,在Android中,所有的Activity都必须注册在AndroidManifest中。这是因为,在Activity的启动过程中,系统要经过校验,如果没有在AndroidManifest中注册,启动Activity就会失败。在插件化方案中,宿主和插件是独立开发的,宿主的AndroidManifest是整个App的AndroidManifest,而插件的AndroidManifest,系统是不认的,这样就导致了插件Activity无法在AndroidManifest中注册,从而启动时校验失败的问题。所以RePlugin插件化框架要解决的首要问题就是如果让插件中的Activity通过系统的验证,通过系统来实现Activity生命周期管理的问题。

RePlugin中“坑位Activity”替换“插件Activity”过程

RePlugin使用了“占坑”方案来解决Activity必须在AndroidManifest注册的问题。我们通过在宿主的AndroidManifest中,注册了各种task类型的Activity,作为“坑位”,在启动插件中的Activity过程中,会使用“坑位”Activity替换掉插件的Activity,这样就会通过系统的验证过程了。我们也就实现了狸猫换太子,用“坑位”Activity替换掉了“需要启动的插件的Activity”。

  1. 在插件中,所有Activity都必须继承PluginActivity。

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方法。这时,代码逻辑由插件,进入宿主。

  1. Factory2的startActivity方法:
 public static final boolean startActivity(Context context, Intent intent) { return sPLProxy.startActivity(context, intent); } 
  1. 调用PluginLibraryInternalProxy的startActivity(Context context, Intent intent)方法:
 public boolean startActivity(Context context, Intent intent) { …… …… // 调用“特殊版”的startActivity,不让自动填写ComponentName,防止外界再用时出错 return Factory.startActivityWithNoInjectCN(context, intent, plugin, name, process); } 

该函数做了大量逻辑处理工作:

  • 兼容模式判断及处理。
  • 获取Activity名称。
  • 判断将要启动的Activity是否本身就是坑位Activity,如果是,直接返回false,直接由系统启动。
  • 获取插件名称。
  • 判断要启动的 Activity 是否是动态注册的类,如果是动态注册类,则返回false,直接由系统启动。
  • 再次检查插件名称,并尝试获取,如果最终获取失败,则返回false,直接由系统启动。
  • 获取进程值,看目标Activity要打开哪个进程。
  • 调用“特殊版”的startActivity,不让自动填写ComponentName,防止外界再用时出错。
  1. 调用Factory的startActivityWithNoInjectCN方法。
  2. 调用PluginCommImpl的startActivity方法,该方法又调回PluginLibraryInternalProxy类,并调用多参数的startActivity方法。
  3. PluginLibraryInternalProxy类的startActivity方法(多参):
 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分配“坑位”Activity,并设置给intent。
  • 这时,intent中的Activity信息已经变为“坑位Activity”了,调用context.startActivity(intent),交由系统启动Activity即可。

到了这里,我们就实现了“坑位Activity”替换“插件Activity”,这样系统在启动Activity,执行后续校验逻辑时,会认为是已经在AndroidManifest中注册的“坑位Activity”在启动,校验过程就会顺利的通过。

Activity的校验过程不是本文的重点,我会在其他文章中进行分析。

到了这里,如果我们什么都不做,系统启动的Activity将是“坑位Activity”,这样对我们来说将没有意义,那么我们如何在启动前(通过系统校验之后),将“坑位Activity”替换回真正要启动的“插件Activity”呢?

“坑位Activity”替换回“插件Activity”

在“坑位Activity”替换“插件Activity”过程中,执行的所有操作,都是在宿主和插件中进行的,也就是他们都是在我们自己的APP端进行的。而Activity校验逻辑,是由ActivityManagerService来进行的,相对于我们的APP,ActivityManagerService就是服务端。我们使用“坑位Activity”在服务端通过“验证”之后,想要替换回我们真正想启动的“插件Activity”,有2个地方可以做替换:一个是在服务端处理完成之后,在服务端进行,这个难度太高,而且稳定性也不好,pass了;另一个地方就是回到APP端之后,再进行替换,RePlugin使用的就是这种方式。

我们来看具体过程。

hook点的选择

我们首先需要选择一个Hook点,在启动Activity之前,把“坑位Activity”替换回“插件Activity”。这个Hook点,RePlugin非常巧妙的使用了ClassLoader。

  1. 首先,我们通过宿主调用application.getBaseContext()获得Application的Context对象,它对应的实现类其实是ContextImpl;
  2. 通过ContextImpl的mPackageInfo属性,我们可以拿到一个LoadedApk类的实例对象;
  3. LoadedApk类对象的mClassLoader就是我们App类加载时所使用的ClassLoader对象;
  4. 我们在RePlugin插件化框架启动时,将LoadedApk类对象的mClassLoader,使用Hook方式,替换成我们自己的ClassLoader对象。

这样,我们就可以使用ClassLoader,在Activity对象加载时,将坑位Activity”替换回“插件Activity”了。

ClassLoader实现及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); } 

过程如下:

  1. 首先执行PMF的loadClass进行类加载,成功后直接返回。
  2. 如果失败,执行Hook之前的原生classloader对象的loadClass进行类加载,成功后返回。
  3. 如果失败,执行super的loadClass方法。

我们来看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); } 

该方法实现:

  • 载Service中介坑位。
  • 将“坑位Activity”替换为目标“插件Activity”。
  • 将“坑位Service”替换为目标“插件Service”
  • 将“坑位Provider”替换为目标“插件Provider”
  • “动态类”、大插件等逻辑处理。

这里我们重点来看将“坑位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信息”,存储在PluginContainers.ActivityState对象中。
  • 获得目标插件的对象实例。
  • 获得“坑位Activity”所对应的目标“插件Activity”
  • 获得目标插件的ClassLoader对象。
  • 通过目标插件的ClassLoader对象,加载目标“插件Activity”,并返回该实例。

至此,我们就完成了“坑位Activity”替换为目标“插件Activity”的过程。

小结

使用“坑位”方案,通过系统逻辑验证过程:

  • RePlugin使用了“占坑”方案来解决Activity必须在AndroidManifest注册的问题。
  • 在宿主的AndroidManifest中,注册了各种task类型的Activity,作为“坑位”。
  • 在插件中,所有Activity都必须继承PluginActivity。
  • 在“插件Activity”启动过程中,经过一系列处理,为“插件Activity”分配“坑位Activity”,并设置给intent。
  • 这时,intent中的Activity信息已经变为“坑位Activity”了,调用context.startActivity(intent),交由系统启动Activity。

“坑位Activity”替换为目标“插件Activity”的过程:

  • “坑位Activity”在通过系统“验证”之后,我们需要替换回我们真正想启动的“插件Activity”。
  • 我们在APP启动时,Hook宿主的ClassLoader。
  • 在系统加载到“坑位Activity”时,我们把“插件Activity”替换回来。
  • 插件Activity类加载过程,使用的是插件的ClassLoader对象。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/230379.html原文链接:https://javaforall.net

(0)
上一篇 2026年2月26日 下午8:01
下一篇 2026年2月26日 下午8:22


相关推荐

  • 国内常用公共DNS服务器、各省运营商DNS服务器汇总

    原文  http://www.francissoung.com/2016/03/14/常用公共DNS服务器/主题 DNS服务器 服务器 运营商不知道大家有没有过网络是正常的,QQ可以正常登录、游戏也可以正常玩,但是网页无论如何都打不开。这就是电脑网络设置的DNS服务器有问题,不是其故障就是不工作了。换了DNS服务器成功解决问题。下边给大家分享一下国内比较大型大众常用

    2022年4月17日
    441
  • 【java基础】java关键字总结及详解

    【java基础】java关键字总结及详解Java关键字是电脑语言里事先定义的,有特别意义的标识符,有时又叫保留字,还有特别意义的变量。Java的关键字对Java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等,关键字不能用作变量名、方法名、类名、包名和参数。(一)总表:java关键字共53个(其中包含两个保留字const,goto) abstract assert …

    2022年7月8日
    30
  • 新手coze扣子SDK保姆级部署,并且使用Jwt会话隔离

    新手coze扣子SDK保姆级部署,并且使用Jwt会话隔离

    2026年3月12日
    1
  • TeX Live2018_latex安装教程

    TeX Live2018_latex安装教程Y·S2018年8月5日15:00:32点击链接https://tug.org/texlive/注:Latex不止TeX这一种,这里只给出了TeX的安装,如果想尝试别的软件的同学可以自行寻找其他教程。并执行如下操作:第一步第二步第三步第四步第五步装载下载好了的TexLive安装包:分以下几种情况:…

    2022年4月29日
    45
  • SSL协议详解「建议收藏」

    SSL协议详解「建议收藏」SecureSocketsLayerSSL协议概述SSL解决的问题(功能)协议的使用SSL在协议栈的位置SSL协议的分层模型SSL体系结构SSL的两个重要概念主要工作流程SSL握手协议的握手过程SSL记录层的功能SSL协议脆弱性分析SSL协…

    2022年6月2日
    33
  • ajax上传文件

    ajax上传文件一般的时候都是用的 struts 提交表单进行文件上传我做了一个校验 想用 ajax 进行文件的验证 这时候就需要使用 ajax 上传文件进行验证 nbsp 首先需要一个 js 包 nbsp jquery nbsp 和 nbsp ajaxfileuplo jsajaxfileup 下载地址 nbsp 使用方法 前台 js submitbtn click function dm

    2026年3月20日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号