Android插件化之RePlugin原理解析

RePlugin原理解析

DroidPlugin原理解析点击这里

Replugin插件化框架本质上是DroidPlugin插件化设计理念的延续,DroidPlugin存在的不足之处,Replugin都用更好的方案一一优化了

所以,Replugin和DroidPlugin在核心思路上是一致的,理解了DroidPlugin的实现原理,再去思考Replugin的实现会容易很多

看完DroidPlugin的原理后,大家会发现其存在两个优化点

  1. 通过反射系统api,调用系统未暴露的api或设置hook点,这必然会导致插件框架存在兼容性问题
  2. 插桩组件描述是需要手动配置到AndroidManifest的,能不能改成自动的?

DroidPlugin 反射优化点

DroidPlugin需要通过反射系统API的主要有:

  1. ActivityManagerNative持有的AMS binder proxy的hook
    这个主要通过IActivityManager接口动态代理已有的binder proxy,然后将代理对象写入ActivityManagerNative中,达到hook的目的
  2. 还有其他一堆类似1的接口代理hook
  3. 对ActivityThread中mH这个主handler的反射设置callback,主要是为了截获组件的创建,还有对mPackages,LoadedApk等的反射调用
  4. 对系统PackageParse的反射使用,主要目的是获取插件包内的组件信息

总之,就是存在一堆的反射,这会极大的影响插件框架的兼容性

在尝试优化上头的一堆反射实现之前,一定要先明白这些反射的目的是什么:

  1. 对ActivityManagerNative的binder proxy进行反射并动态代理 ----- 最核心的目的就是对components启动做hook,替换intent的target
  2. ActivityThread的hook ----
  • mH — 为了hook Activity的启动,这个是入口,是activity替换成插件的根本
  • mPackages中LoadedApk的创建,以及对LoadedApk classloader的修改,这个是整个插件化的核心
  1. PackageParser的反射,主要是为了根据插件apk,得到组件信息,因为ActivityThread的接口,必须要基于这些系统的组件信息数据类型

简单点说就是两点

  1. 对组件启动时的intent做stub替换
  2. 当AMS->ActivityThread启动组件时,将stub组件还原回真实的插件组件

在详细介绍Replugin是如何优化上面两点前,先说说Replugin对手动在Manifest插桩的优化

Replugin对Manifest插桩的优化

DroidPlugin是手动插桩,如果要优化,只能通过gradle插件来做,对应Replugin的host gradle 插件

replugin-host-gradle

这个gradle插件主要做了两件事情

  1. 创建extension,供配置stub相关的数量,并依次生产manifest的stub组件描述
  2. 在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资源的加载,包括

  1. 代码 – ClassLoader,包含dex和so的加载
  2. 资源 – mResDir

有了代码和资源,LoadedApk就可以创建Android应用所需的Application和组件对象,当然还有ContextImpl

LoadedApk的核心入参是插件的ApplicationInfo,这个也是其必须要反射使用PackageParse的原因

如何优化?能不能不创建LoadedApk,如果要不创建LoadedApk,那必须要有方案来解决如下的两个问题:

  1. 如何创建插件ClassLoader并用其加载插件类
  2. 如何创建插件的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就是这么做的

完整的流程是这样的:

  1. Replugin host app启动时,会遍历所有的插件信息,然后缓存组件信息
  2. 启动插件activity的时候,会从stub activity找到一个匹配的,并修改Intent,并记录stub activity和目标组件activity的关联关系
  3. 在stub activity的class被RepluginClassLoader加载的时候,会先从上面的stub关联关系中找到目标组件以及对应的activiy,接着创建PluginDexClassLoader, 并用其加载目标组件Activity

就这样偷梁换柱了,目标组件是创建了,但是对于Activity来说,还不够,还有几点要解决

  1. Context,组件的创建虽然被偷梁换柱了,但是Activity attach的context还是从外部传入的host app环境的,所以,这个必须要替换成插件自身的
  2. activity如果设置了theme,这个也必须要在activity创建后设置到context中,还是那句话,默认attach的context是host app的
  3. 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这个插件来完成的,基本实现原理如下:

  1. 基于Google Transform,在gradle插件里注册一个自定义的transform
  2. 在transform被执行时,通过process manifest task拿到manifest的文件路径,并从中解析出配置的activities
  3. 遍历transform中的class output dir和jar解压出来的dir,如果class是在manifest中配置过的activity,则通过asm,找到super class是Activity的类并替换成PluginActivity,并重新回写到对应目录同名文件

就这样,完成manifest中配置的activity的super class的替换

Replugin进程管理

在我看来,进程管理意味着如下三点:

  1. 控制进程启动
  2. 控制进程销毁
  3. 和进程进行数据通讯

在应用层面如何启动一个进程?Android没有相关Api,所以必须要另辟蹊径,最简单的方法就是通过Provider

在Replugin的host app,在逻辑上,可以分为两部分:

  1. replugin host server部分,比如进程管理,包管理服务等等,这部分可以放到专门开辟的进程,也可以不开辟,放在host app默认进程
  2. 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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值