文章目录
- RePlugin原理解析
-
- DroidPlugin 反射优化点
- Replugin对Manifest插桩的优化
- Replugin对组件启动Intent替换行为的优化
- Replugin对stub组件的还原优化
- replugin-plugin-gradle的用处
- Replugin进程管理
- DroidPlugin和Replugin的差异
RePlugin原理解析
DroidPlugin原理解析点击这里
Replugin插件化框架本质上是DroidPlugin插件化设计理念的延续,DroidPlugin存在的不足之处,Replugin都用更好的方案一一优化了
所以,Replugin和DroidPlugin在核心思路上是一致的,理解了DroidPlugin的实现原理,再去思考Replugin的实现会容易很多
看完DroidPlugin的原理后,大家会发现其存在两个优化点
- 通过反射系统api,调用系统未暴露的api或设置hook点,这必然会导致插件框架存在兼容性问题
- 插桩组件描述是需要手动配置到AndroidManifest的,能不能改成自动的?
DroidPlugin 反射优化点
DroidPlugin需要通过反射系统API的主要有:
- ActivityManagerNative持有的AMS binder proxy的hook
这个主要通过IActivityManager接口动态代理已有的binder proxy,然后将代理对象写入ActivityManagerNative中,达到hook的目的
- 还有其他一堆类似1的接口代理hook
- 对ActivityThread中mH这个主handler的反射设置callback,主要是为了截获组件的创建,还有对mPackages,LoadedApk等的反射调用
- 对系统PackageParse的反射使用,主要目的是获取插件包内的组件信息
总之,就是存在一堆的反射,这会极大的影响插件框架的兼容性
在尝试优化上头的一堆反射实现之前,一定要先明白这些反射的目的是什么:
- 对ActivityManagerNative的binder proxy进行反射并动态代理 ----- 最核心的目的就是对components启动做hook,替换intent的target
- ActivityThread的hook ----
- mH — 为了hook Activity的启动,这个是入口,是activity替换成插件的根本
- mPackages中LoadedApk的创建,以及对LoadedApk classloader的修改,这个是整个插件化的核心
- PackageParser的反射,主要是为了根据插件apk,得到组件信息,因为ActivityThread的接口,必须要基于这些系统的组件信息数据类型
简单点说就是两点
- 对组件启动时的intent做stub替换
- 当AMS->ActivityThread启动组件时,将stub组件还原回真实的插件组件
在详细介绍Replugin是如何优化上面两点前,先说说Replugin对手动在Manifest插桩的优化
Replugin对Manifest插桩的优化
DroidPlugin是手动插桩,如果要优化,只能通过gradle插件来做,对应Replugin的host gradle 插件
replugin-host-gradle
这个gradle插件主要做了两件事情
- 创建extension,供配置stub相关的数量,并依次生产manifest的stub组件描述
- 在getProcessManifest task的onLast中,对manifest做字符串replace替换
注意,manifest中添加了stub组件描述,可以不需要创建stub实体类,因为manifest中的stub组件,至少在pms,是不会去校验组件类在目标app中是否存在的,所以对于stub activity,我们只需要确保在其真正的创建前,把它替换成真实的activity就可以了
Replugin对组件启动Intent替换行为的优化
DroidPlugin通过对ActivityManagerNative的binder service proxy进行动态代理来实现对组件启动等功能的hook,这块如果想优化,最简单的做法就是:
- 能不能实现一个自定义的Context,然后让插件app启动时,统一使用这个自定义的context
Replugin对stub组件的还原优化
在思考如何优化前,我们再次回顾下DroidPlugin对stub还原时做的那么多hook,本质的目的其实就一个
- 基于插件的包信息,创建LoadedApk并保存到ActivityThread
那LoadedApk又负责什么?看命名就知道了,它负责了对应插件Apk资源的加载,包括
- 代码 – ClassLoader,包含dex和so的加载
- 资源 – mResDir
有了代码和资源,LoadedApk就可以创建Android应用所需的Application和组件对象,当然还有ContextImpl
LoadedApk的核心入参是插件的ApplicationInfo,这个也是其必须要反射使用PackageParse的原因
如何优化?能不能不创建LoadedApk,如果要不创建LoadedApk,那必须要有方案来解决如下的两个问题:
- 如何创建插件ClassLoader并用其加载插件类
- 如何创建插件的Context,并跟插件Application和组件关联
Replugin的方案是:
- 只hook LoadedApk内部的classloader,在启动的时候,创建RepluginClassLoader,内部通过浅拷贝origin classloader的私有变量值,接着将RepluginClassLoader对象设置回LoadedApk
注意,这是Replugin唯一的一处通过反射修改系统私有属性的地方,所以,它具有很强的兼容性
既然我们hook app的classloader,那理论上,app后续加载的所有class都能被监测和修改
上面说过了,在启动组件的时候,我们会把将要启动的component替换成stub component,那在stub component类被RepluginClassLoader加载的时候,我们是不是可以把它替换成对应启动的component?当然可以,Replugin就是这么做的
完整的流程是这样的:
- Replugin host app启动时,会遍历所有的插件信息,然后缓存组件信息
- 启动插件activity的时候,会从stub activity找到一个匹配的,并修改Intent,并记录stub activity和目标组件activity的关联关系
- 在stub activity的class被RepluginClassLoader加载的时候,会先从上面的stub关联关系中找到目标组件以及对应的activiy,接着创建PluginDexClassLoader, 并用其加载目标组件Activity
就这样偷梁换柱了,目标组件是创建了,但是对于Activity来说,还不够,还有几点要解决
- Context,组件的创建虽然被偷梁换柱了,但是Activity attach的context还是从外部传入的host app环境的,所以,这个必须要替换成插件自身的
- activity如果设置了theme,这个也必须要在activity创建后设置到context中,还是那句话,默认attach的context是host app的
- plugin Application的创建需要触发
第三条plugin Application的触发比较简单,在组件第一次加载的时候,通过对应PluginDexClassLoader加载Application并attach PluginContext就好了
第1,2条,本质都是对Context的替换
replugin-plugin-gradle的用处
如果要替换Activity的context,最简单的做法是,实现一个PluginActivity,然后让插件的Activity都派生自PluginActivity,PluginActivity在onAttachContext函数中,完成对Context的替换
这一切都是通过replugin-plugin-gradle这个插件来完成的,基本实现原理如下:
- 基于Google Transform,在gradle插件里注册一个自定义的transform
- 在transform被执行时,通过process manifest task拿到manifest的文件路径,并从中解析出配置的activities
- 遍历transform中的class output dir和jar解压出来的dir,如果class是在manifest中配置过的activity,则通过asm,找到super class是Activity的类并替换成PluginActivity,并重新回写到对应目录同名文件
就这样,完成manifest中配置的activity的super class的替换
Replugin进程管理
在我看来,进程管理意味着如下三点:
- 控制进程启动
- 控制进程销毁
- 和进程进行数据通讯
在应用层面如何启动一个进程?Android没有相关Api,所以必须要另辟蹊径,最简单的方法就是通过Provider
在Replugin的host app,在逻辑上,可以分为两部分:
- replugin host server部分,比如进程管理,包管理服务等等,这部分可以放到专门开辟的进程,也可以不开辟,放在host app默认进程
- host app ui进程,我觉得叫ui进程,更多的还是针对activity来说的,因为它是stub activity的启动进程
第一部分要不要开辟进程,可以在host gradle插件中配置
每一个插件进程启动时,会通过provider先创建其进程,在进程启动后,会实现IPluginClient native binder,然后在host server的进程管理类PluginProcessMain通过ProcessClientRecord记录
DroidPlugin和Replugin的差异
DroidPlugin和Replugin不是完全不同的插件化方案,它们在大的设计思路上是一致的,只不过在具体实现上,使用了两种不同的实现方案,两种方案没有好坏之说,只能说适用的场景不一样
- DroidPlugin,全部基于运行期hook,优点是市面的Apk都可以使用,更加灵活,缺点是存在兼容性
- Replugin,将大部分的hook通过gradle插件在编译时实现,整个框架只有一处hook点,优点是稳定性更强了,缺点是插件Apk需要基于插件sdk编译,在功能上要依赖插件sdk,相对来说会没那么灵活
具体采用哪一种,还是要看需求场景,如果你需要对已经发布的APK做插件化,那只能用DroidPlugin;如果只是对App内部业务做拆分,分离编译环境,实现热更新等,那可以选择用Replugin