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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 初识JMM_一!,识J

    初识JMM_一!,识J1.什么是JMM?JMM:(JavaMemoryModel的缩写)作用:缓存一致性协议,用于定义数据读写的规则。JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(MainMemory)中,每个线程都有一个私有的本地内存(LocalMemory)所有的变量都存储在主内存中,每个线程还有自己的工作内存,工作内存存储在高速缓存或者寄存器中,保存了该线程使用的变量的主内存副本拷贝。线程只能直接操作工作内存中的变量,不同线程之间的变量值传递需要通过主内存来完成。

    2025年9月13日
    11
  • 宏基因组注释和可视化神器MEGAN入门

    宏基因组注释和可视化神器MEGAN入门文章目录 MEGAN 宏基因组功能和物种分类 MEGAN 功能简介原理简要示意图 MEGAN 特有文件格式 RMAMEGAN 下载 MEGAN 使用 MEGAN linux 版本安装 MEGAN 使用指南 Linux 提取注释内容 物种和功能 提取物种注释数据 提取功能 Win 版安装和使用 MEGAN 安装 Win 版使用指南 MEGAN 主界面介绍 MEGAN 输入介绍 MEGAN 分析进阶双样本比对 MEGAN 界面可视化 两样本 r

    2025年10月6日
    5
  • gradle搭建springboot_gradle和maven的区别

    gradle搭建springboot_gradle和maven的区别以下是我以一个刚入行职场菜鸡的个人见解,不喜勿碰。自我感觉java入门很是简单,网上的各种教程满天飞,但是需要深刻的认识到java的具体的思想就比较需要去花费功夫了。那么这就需要我们看spring的源码了。那问什么要看spring源码呢?下面我引用别人的写的博客。https://blog.csdn.net/cjm812752853/article/details/76222491/接下来讲如何将s…

    2022年8月12日
    14
  • 光棍节程序员闯关秀过关全攻略(附带小工具)[通俗易懂]

    光棍节程序员闯关秀过关全攻略(附带小工具)[通俗易懂]光棍节程序员闯关秀过关全攻略。程序员的寂寞谁能懂?"SF光棍节程序员闯关秀"智力挑战小游戏火热上线,看看你能闯到第几关?游戏地址:http://segmentfault.com

    2022年7月1日
    42
  • javaweb参考文献_javaweb参考文献

    javaweb参考文献_javaweb参考文献计算机毕业设计外文参考文献[1].Abdellatif,T.andF.Boyer.AnodeallocationsystemfordeployingJavaEEsyst.计算机毕业设计外文参考文献[1]…….Web应用程序安全外文翻译参考文献Web应用程序安全外文翻译参考文献(文档含中英文对照即英文原文和中文翻译)原文:BasicSecur…

    2022年9月30日
    5
  • Java反射:用最直接的大白话来聊一聊Java中的反射机制[通俗易懂]

    思考:在讲反射之前,先思考一个问题,java中如何创建一个对象,有哪几种方式?Java中创建对象大概有这几种方式:1、使用new关键字:这是我们最常见的也是最简单的创建对象的方式2、使用Clone的方法:无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面的对象的内容全部拷贝进去3、使用反序列化:当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对…

    2022年4月11日
    46

发表回复

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

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