0
点赞
收藏
分享

微信扫一扫

Android 全面插件化 RePlugin 流程与源码解析

  RePlugin,360开源的全面插件化框架,按照官网说的,其目的是“尽可能多的让模块变成插件”,并在很稳定的前提下,尽可能像开发普通App那样灵活。那么下面就让我们一起深入♂了解它吧。 (ps :阅读本文请多参考源码图片 ( ̄^ ̄)ゞ )

一、介绍

  RePlugin对比其他插件化,它的强大和特色,在于它只Hook住了ClassLoader。One Hook这个坚持,最大程度保证了稳定性、兼容性和可维护性,详见​​《全面插件化——RePlugin的使命》​​。当然,One Hook也极大的提高了实现复杂程度性,其中主要体现在:

  • 增加了Gradle插件脚本,实现开发中自动代码修改与生成。
  • 分割了插件库和宿主库的代码实现。
  • 代码中存在很多不少​​@deprecated​​​、​​TODO​​和临时修改。
  • 初始化、加载、启动等逻辑比较复杂。

Android 全面插件化 RePlugin 流程与源码解析_开源图一 Replugin项目结构

  本篇将竭尽所能,为各位介绍其流程和内部实现,如果存在一些地方存在纰漏,还请指出。文章篇幅较长,需耐心阅读,阅读时可结合图片源码,同时欢迎收藏,或选择感兴趣点阅读,下面主要涉及:

  • 二、ClassLoader基础知识。
  • 三、Replugin项目原理和结构分析。
  • 四、Replugin的ClassLoader。
  • 五、Replugin的相关类介绍。
  • 六、Replugin的初始化。
  • 七、Replugin启动Activity。

Android 全面插件化 RePlugin 流程与源码解析_Android_02此处应有图

二、ClassLoader基础知识

  既然Replugin选择Hook住ClassLoader,那先简单介绍下ClassLoader的基本知识吧,如熟悉者请略过。

  ClassLoader又叫类加载器,是专门处理类加载,一个APP可以存在多个ClassLoader,它使用的是双亲代理模型,如下图所示,创建一个ClassLoader,需要使用一个已有的ClassLoader对象,作为新建的实例的ParentLoader。

Android 全面插件化 RePlugin 流程与源码解析_加载_03抽象基类ClassLoader

  这样的条件下,一个App中所有的ClassLoader都联系了起来。当加载类时,如果当前ClassLoader未加载此类,就查询ParentLoader是否加载过,一直往上查找,如果存在就返回,如果都没有,就执行该Loader去执行加载工作。这样避免了类重复加载的浪费。其中常见的Loader有:

  • BootClassLoader 是系统启动时创建的,一般不需要用到。
  • PathClassLoader 是应用启动时创建的,只能加载内部dex。
  • DexClassLoader 可以加载外部的dex。

RePlugin中存在两个主要ClassLoaer:

  • 1、RePluginClassLoader 宿主App中的Loader,继承PathClassLoader,也是唯一Hook住系统的Loader。
  • 2、PluginDexClassLoader 加载插件的Loader,继承DexClassLoader。用来做一些“更高级”的特性。

三、Replugin项目原理和结构分析

1、基础原理

  简单来说,其核心是hook住了 ​​ClassLoader​​,在Activity启动前:

  • 记录下目标页​​ActivityA​​​,替换成已自动注册在 AndroidManifest 中的坑位​​ActivityNS​​。
  • 在​​ClassLoader​​​ 中拦截​​ActivityNS​​​的创建,创建出​​ActivityA​​返回。
  • 返回的​​ActivityA​​​占用着​​ActivityNS​​ 这个坑位,坑位由Gradle编译时自动生成在AndroidManifest中。

  在编译时,​​replugin-replugin-library​​脚本,会替换代码中的基础类和方法。如下图【官方原理图】所示,替换的基类里会做一些初始化,所以这一块稍微有点入侵性。此外,​​replugin-host-library​​会生成AndroidManifest配置相关信息打包等,也由Gradle插件自动完成。

  打包独立APK,或者打包为插件,可单可插,这就是RePlugin。


Android 全面插件化 RePlugin 流程与源码解析_开源_04官方原理图

2、项目结构

  RePlugin整个项目结构,目前分为四个module,其中又分为两个gradle插件module,两个library的java module,详细如开头【图一 Replugin项目结构】,本文主要分析library相关,如果对gradle插件感兴趣的,可以查看结尾其他推荐。

2.1、replugin-host-gradle :

  对应​​com.qihoo360.replugin:replugin-host-gradle:xxx​​依赖,主要负责在主程序的编译期中生产各类文件:

  • 根据用户的配置文件,生成HostBuildConfig类,方便插件框架读取并自定义其属性,如:进程数、各类型占位坑的数量、是否使用AppCompat库、Host版本、pulgins-builtin.json文件名、内置插件文件名等。
  • 自动生成带 RePlugin 插件坑位的 AndroidManifest.xml文件,文件中带有如:
<activity
android:theme="@style/Theme.AppCompat"
android:name="com.qihoo360.replugin.sample.host.loader.a.ActivityN1STTS0"
android:exported="false"
android:screenOrientation="portrait"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
/>
2.2、replugin-host-library:

  对应​​com.qihoo360.replugin:replugin-host-lib:xxx​​依赖,是一个Java工程,由主程序负责引入,是RePlugin的核心工程,负责初始化、加载、启动、管理插件等。

2.3、replugin-plugin-gradle:

  对应​​com.qihoo360.replugin:replugin-plugin-gradle:xxx​​ ,是一个Gradle插件,由插件负责引入,主要负责在插件的编译期中:配置插件打包相关信息;动态替换插件工程中的继承基类,如下,修改Activity的继承、Provider的重定向等。

/* LoaderActivity 替换规则 */
def private static loaderActivityRules = [
'android.app.Activity' : 'com.qihoo360.replugin.loader.a.PluginActivity',
'android.app.TabActivity' : 'com.qihoo360.replugin.loader.a.PluginTabActivity',
'android.app.ListActivity' : 'com.qihoo360.replugin.loader.a.PluginListActivity',
'android.app.ActivityGroup' : 'com.qihoo360.replugin.loader.a.PluginActivityGroup',
'android.support.v4.app.FragmentActivity' : 'com.qihoo360.replugin.loader.a.PluginFragmentActivity',
'android.support.v7.app.AppCompatActivity': 'com.qihoo360.replugin.loader.a.PluginAppCompatActivity',
'android.preference.PreferenceActivity' : 'com.qihoo360.replugin.loader.a.PluginPreferenceActivity',
'android.app.ExpandableListActivity' : 'com.qihoo360.replugin.loader.a.PluginExpandableListActivity'
]
2.4、replugin-plugin-library:

  对应​​com.qihoo360.replugin:replugin-plugin-lib:xxx​​依赖,是一个Java工程,由插件端负责引入,主要提供通过“Java反射”来调用主程序中RePlugin Host Library的相关接口,并提供“双向通信”的能力,以及各种基类Activity等


  其中的​​RePlugin​​、​​RePluginInternal​​、​​PluginServiceClient​​都是反射宿主App :​​replugin-host-library​​ 中的 ​​RePlugin​​ 、 ​​RePluginInternal​​ 、​​PluginServiceClient​​ 类方法。

四、Replugin的ClassLoader。

  这里主要介绍,宿主和插件使用的ClassLoader,以及它们的创建和Hook住时机。这是RePlugin唯一的Hook点,而其中插件ClassLoader和宿主ClassLoader是相互关系的,如下图

Android 全面插件化 RePlugin 流程与源码解析_开源_05将就的图

1、宿主的ClassLoader

  ​​RePluginClassLoader​​​,宿主的ClassLoader,继承 ​​PathClassLoader​​,构造方法使用原ClassLoader,和原ClassLoader的Parent生成。其中ParentLoader是因为双亲代理模型,创建ClassLoader所需,而原Loader用于保留在后期使用,如下图

Android 全面插件化 RePlugin 流程与源码解析_初始化_06

  如下两图,​​RePluginClassLoader​​​ 在创建时,浅拷贝原Loader的资源到 ​​RePluginClassLoader​​ 中,用于欺骗系统还处于原Loader,并且从原Loader中反射出常用方法,用于重载方法中使用。

Android 全面插件化 RePlugin 流程与源码解析_android_07拷贝资源Android 全面插件化 RePlugin 流程与源码解析_android_08方式方法

  宿主Loader中,主要是重载了 ​​loadClass​​​,其中从 ​​PMF​​(RePlugin中公开接口类)中查找class,如果存在即返回插件class,如果不存在就从原Loader中加载。从而实现了对加载类的拦截。

  这里的 ​​PMF​​​ 在加载class时,其实用的是下面【2、插件的ClassLoader 】:​​PluginDexClassLoader​​,这个后面流程会讲到。

Android 全面插件化 RePlugin 流程与源码解析_Android_09

2、插件的ClassLoader

  ​​PluginDexClassLoader​​,继承DexClassLoader,构造时持有了宿主的ClassLoader,从宿主ClassLoader中反射获取loadClass方法,当自己的loadClass方法找不到类时,从宿主Loader中加载。

Android 全面插件化 RePlugin 流程与源码解析_加载_10


3、创建和Hook

  创建:上面1、2中两个Loader,是宿主在初始化时创建的,初始化时可以选择配置​​RePluginCallbacks​​,callback中提供方法默认创建Loader,你也可以实现自定义的ClassLoader,但是需要继承以上的Loader,如下图

//初始化方式创建
RePlugin.getConfig().getCallbacks()
.createClassLoader(oClassLoader.getParent(), oClassLoader);

Android 全面插件化 RePlugin 流程与源码解析_初始化_11RePluginCallbacks

  Hook:初始化时,​​PatchClassLoaderUtils​​​会在Application的​​attachBaseContext()​​​中,通过​​patch(application)​​Hook住宿主的ClassLoader,patch内部如下图

Android 全面插件化 RePlugin 流程与源码解析_Android_12hook ClassLoader

五、Replugin的相关类介绍

  提前介绍一些功能类,后面就不做详细介绍。

1、RePlugin :RePlugin的对外入口类,提供install、uninstall、preload、startActivity、fetchPackageInfo、fetchComponentList,fetchClassLoader等等统一的方法入口,用户操作的主要是它。


2、RePlugin.App:RePlugin中的内部类,针对Application的入口类,所有针对插件Application的调用应从此类开始和初始化,想象成插件的Application吧。

3、PmBase:RePlugin常用mPluginMgr变量表示,可以看作插件管理者。初始化插件、加载插件等一般都是从它开始。

4、PluginContainers:插件容器管理中心。

5、PmLocalImpl:各种本地接口实现,如startActivity,getActivityInfo,loadPluginActivity等。

6、PmInternalImpl:类似Activity的接口实现,内部实现了真正startActivity的逻辑、还有插件Activity生命周期的接口。


-   

Android 全面插件化 RePlugin 流程与源码解析_加载_13准备好了吗,骚年

六、Replugin的初始化

  那就是从 Application 初始化开始看起,枯燥的流程就要开始了,忍住兄弟,我们能赢。首先我们先看下面这流程图,大致了解启动流程:

Android 全面插件化 RePlugin 流程与源码解析_开源_14将就的看吧

1、attachBaseContext

  首先是从 Application 的 ​​attachBaseContext​​​ 初始化开始。如下图,这里主要是配置 ​​RePluginConfig​​​ 和 ​​RePluginCallbacks​​​ ,然后根据 Config 去初始化插件。值得注意的是,​​RePluginConfig​​​ 中的 ​​RePluginCallbacks​​ 提供了默认方法创建 RePlugin 的 ClassLoader,还记得上面的介绍吗?

Android 全面插件化 RePlugin 流程与源码解析_开源_15看图看图

2、插件App.attachBaseContext

  继续上面的流程,进入​​RePlugin.App.attachBaseContext(this, c)​​​,如下图,这里主要是初始化插件相关的进程、配置信息、插件的主框架和接口、根据默认路径、加载默认插件等。插件的初始化从这里开始,其中主要为 ​​PMF.init()​​​ 和 ​​PMF.callAttach()​​。

Android 全面插件化 RePlugin 流程与源码解析_Android_16继续看图看图

3、主程序接口 PMF.init()/PMF.callAttach()

  先进入到 ​​PMF.init()​​​ ,如下图,这里主要实例化了 ​​PmBase​​​ 类,并初始化了它,创建了内部使用的 ​​PmLocalImpl​​​ 和 ​​PmInternalImp​​​ 接口 ,同时Hook住主程序的 ClassLoader,替换为 ​​RePluginClassLoader​​​,所以接下来的流程,主要是在 ​​PmBase​​ 。

Android 全面插件化 RePlugin 流程与源码解析_加载_17PMF.init(),看图吧

  ​​PmBase​​​,按照项目中的变量名 ​​mPluginMgr​​,可以理解为插件的管理者,它管理内部直接或间接的,管理着坑位分配、ClassLoader、插件、进程、启动\停止页面的接口等,如下图。

Android 全面插件化 RePlugin 流程与源码解析_android_18PmBase创建,还是看图

  ​​PmBase​​​ 的初始化,也就是插件的初始化,这里会启动各类进程,初始化各种默认插件集合,为后续加载做准备。其中默认插件和配置文件的位置,一般默认是在 assert 的 ​​plugins-builtin.json​​ 和 "plugins" 文件夹下。

Android 全面插件化 RePlugin 流程与源码解析_加载_19PmBase.init() 看图看图

  接着​​PMF.callAttach()​​​ 其实就是 ​​PmBase.callAttach()​​,如下图这里开始真正加载插件,初始化插件的 ​​PluginDexClassLoader​​​ 、加载插件、初始化插件环境和接口。其中在执行 ​​p.load()​​​ 的时候,会通过 ​​Plugind.callAppLocked()​​ 创建插件的 Application,并初始化。

Android 全面插件化 RePlugin 流程与源码解析_加载_20PMF.callAttach() 看图呗

  以上是在主APP的初始化,深入 ​​PmBase​​​ 中,​​Plugin.load()​​​在加载时,会调用​​PluginDexClassLoader​​​, 通过类名加载 ​​Entry​​​ 类,然后反射出​​create​​​方法,执行插件的初始化。其中 ​​Entry​​ 位于Plugin-lib库中。这里初始化就去到了插件中了,插件中初始化时,会通过反射的到宿主host类的方法。

Android 全面插件化 RePlugin 流程与源码解析_android_21

4、Application的onCreate

  这里主要是切换handler到主线程,注册各种广播接收监听,如增加插件、卸载插件、更新插件,可以看出这里设计很多内部进程通信的。

Android 全面插件化 RePlugin 流程与源码解析_开源_22


-


七、Replugin启动Activity

  这里仅描述了Activity启动的其中一个流程,也是简化版的,实际代码逻辑复杂多了,但是万变不离其宗,这里帮你梳理流程,描述一些关键的点,让你快速理解Activity的启动流程。

Android 全面插件化 RePlugin 流程与源码解析_android_23再将就下吧,看图

1、startActivity

  从上面的流程图我们知道,启动插件Activity可以从​​RePlugin.startActivity​​​开始,startActivity经历了 ​​Factory​​​ 、 ​​PmLocalImpl​​​ ,其实大部分启动的逻辑其实主要在 ​​PmInternalImpl​​ 中。

  具体流程如下图,这里简化了实际代码,关键在于 ​​loadPluginActivity​​。这里获取了插件对应的坑位,然后保存了目标Activity的信息,通过系统启动坑位。

  因为已经Hook住了ClassLoader,在 ​​loadClass​​​ 时再加载出目标Activity,这样坑位中承载的,便是绕过系统打开的目标Activity。下面我们进入 ​​loadPluginActivity​​。

Android 全面插件化 RePlugin 流程与源码解析_android_24说了看图

2、loadPluginActivity

  ​​loadPluginActivity​​​ 其实是 ​​PmBase​​​ 中的 ​​PmLocalImpl​​​ 内部方法。如下图,这里主要是根据获取到 ​​ActivityInfo​​,然后根据坑位去为目标Activity分配坑位。

  其中 ​​getActivityInfo​​​ 是通过插件名称,获得插件对象 ​​Plugin​​​, ​​Plugin​​​可能是初始化中已加载的,如果未加载就加载返回,然后根据 ​​Plugin​​​ 中缓存的坑位信息,返回 ​​ActivityInfo​​。

  下面进入 ​​allocActivityContainer​​ 看坑位的分配,只有分配到坑位,插件的Activity才可以启动,这是一个IPC过程。

Android 全面插件化 RePlugin 流程与源码解析_初始化_25看图没?

2、allocActivityContainer

  ​​allocActivityContainer​​​ 在类 ​​PluginProcessPer​​​ 中,还记得我们在 ​​PmBase.init()​​ 时初始化过它么? 分配坑位也是RePlugin的核心之一。

  在 ​​allocActivityContainer​​​ 中, 主要逻辑是​​bindActivity​​​ ,如下图,​​bindActivity​​​ 去找到目标Activity匹配的容器,然后加载目标Activity判断是否存在,并建立映射,返回容器。然后分配的逻辑,在 ​​PluginContainers.alloc​​ 中。

Android 全面插件化 RePlugin 流程与源码解析_Android_26看我大图

3、PluginContainers.alloc

  ​​alloc​​​ / ​​alloc2​​​ 方法分配坑位,最后都是到了 ​​allocLocked​​ 方法中,其实RePlugin中,如下图,便是坑位分配的逻辑:

  • 如果存在未启动的坑位,就使用它。
  • 如果没有就找最老的:已经被释放的、或者时间最老的。
  • 如果还不行,那么挤掉最老的一个。

Android 全面插件化 RePlugin 流程与源码解析_Android_27看图说话

4、PulginActivity

  上面的流程总结,是替换目标Activity,加载插件,分配坑位,启动目标坑位,拦截ClassLoader的loadClass去加载返回目标Activity。

  这个时候启动的Activity还不完整,从模块框架中我们知道,在编译时,RePlugin会把继承的Activity替换为如 ​​PluginActivity​​​(当前还有AppComPluginActivity等)。这时候加载启动的目标Activity,其实是继承了 ​​PluginActivity​​。

  如下图, ​​PluginActivity​​ 重载Activity中的一些方法,实现了Activity的补全和自定义操作,如坑位管理,启动宿主Activity等。

Android 全面插件化 RePlugin 流程与源码解析_Android_28

  至此,一个插件Activity就启动起来了,头晕目眩了没?为了实现 One Hook 这个信念,RePlugin 实现了复杂的流程,从代码中可以看出,这些年作者们从中走的的各种坑、各种妥协与坚持、复杂的技术积累、已经经历了多年的严酷考验。

  不知道有多少人能完整看到这,码字不易,如有疏漏还是多多包涵,由于篇(tou)幅(lan)原因,关于Service等的就不多做叙述了,不知道本文对你是否能有些帮助,欢迎留言讨论。

最后说“一”句

  为什么要去了解一个库实现原理呢?学习框架的架构思想?这是一个原因。但是归根结底,是帮助你在使用库的过程中,能靠自己解决各种问题。程序员的日常一般都忙于各种工作,各种技术群中的大佬们,大部分时候,没办法一一解答你的各种咨询,所以使用它、了解它、多尝试靠自己去探索突破吧。

其他推荐

  • RePlugin Github 官方库
  • RePlugin源码解析之replugin-host-gradle(宿主gradle插件) —— @osan
  • RePlugin源码解析之replugin-plugin-gradle(插件的gradle插件) —— @osan
  • To My Github

Android 全面插件化 RePlugin 流程与源码解析_加载_29注意到了吗?最后的总是我!

举报

相关推荐

android之activity全面解析

0 条评论