Android插件化框架总结

为什么使用插件机制

Android插件化可以带来以下好处:

  • 可以解决65536问题:但是在5.x以后加上mutildex这个需求变得不哪么强了
  • 插件可以动态升级:对动态升级有需求的APP来说,这个吸引力很大
  • 可以减小APK包大小:前提是插件不内置,通过异步进行下载

插件框架的分类

Android插件框架要解决的三个基本问题:

  1. 如何在插件Activity中启动另一个Activity?
  2. 如何加载插件APK中的类?
  3. 如何加载插件APK中的资源?

第2、3问题的答案比较明确,都是通过创建自定义的DexClassLoader、AssetManager(Resource)来确现的。但是第1个问题的实现方案就比较多了。
Android系统启动Activity时只能启动在manifest.xml中注册过的Activity,这是在AMS中进行校验的,无法改变。所以只能用占坑的方式,在manifest.xml中提前注册一些ActivityProxy,然后在启动插件的时候欺骗系统是要启动这些ActivityProxy。在此就根据“欺骗”方式的不同进行分类。

继承方式

怎么样“欺骗”系统是在启动ActivityProxy呢?

继承Activity

首先想到的是让插件开发时继承系统Activity,然后重写里面的startActivityForResult()方法,修改里面的Intent指向ActivityProxy。但是很遗憾,Activity.startActivityForResult()是final方法,无法复写。

所以需要在继承的Activity中添加一个对象(that),来实现这些final的方法,在开发的时候用that.startActivity()。如下面的类结果图:

这里写图片描述

优点:

  • 插件框架开发相对简单,插件中也可以使用Activity

缺点:

  • 由于Activity中的很多final方法,是无法重写的,所以开发会有很多限制(startActivity finish等)

  • 调activity中的方法时要用that,比如that.finish(),that.setResult(),new View(that)等等,this和that用法容易混淆

以上限制使得改造一个插件的成本偏高,而且容易出错

继承ContextWrapper

既然Activity中有很多final方法无法重写,可以直接继承ContextWrapper,参考Activity来实现里面的方法。因为Activity也是继承的ContextWrapper类,所以这个方法是可行的,相当于我们自定义了一个Activity。这样就可以直接重写startActivityForResult()方法,修改里面的目标Intent,完成“欺骗”系统的目的。
这里写图片描述

优点:

  • 开发插件时符合平时开发习惯

缺点:

  • 插件框架开发成本高
  • 开发过程中插件不能直接运行(因为它继承的不是真正的Activity,但是可以通过优化开发流程避免这个问题,开发时继承Activity,打正式包时集成ContextWrapper)

Hook方式

通过研究Activity的启动流程,发现用动态代理或反射可以拦截启动流程,替换其中的Intent,统称之为Hook方式。

根据拦截的地方不同,又分为Hook Instrumentation和Hook ActivityManagerNative。

Hook Instrumentation

在Activity中有个成员变量Instrumentation,Activity的启动和生命周期都会调用它。

在ActivityTread中也有个Instrumentation,当Activity对象真实创建的时候会调用这个它来创建。

所以在启动插件的时候就可以通过替换Activity、ActivityTread中的Instrumentation为自定义的对象,就可以拦截修改Intent,并且在真实创建Activity时再指向插件中的Activity。这种方式也是[small]框架的思想

优点:
- Hook的点比较少,兼容性好
缺点:
- Instrumentation只支持Activity,所以这种方式不能支持Serivce、ContentProvider等

Hook ActivityManagerNative

ActivityManagerNative是和AMS交互的client端,它是运行在应用进程的,所以可以通过动态代理拦截里面的所有的方法。其中不只有启动Activity的方法,还包括Service等。[DroidPlugin]框架用的这种方式,只不过它Hook的更多。

优点:
- 功能强大,支持Android四大组件
缺点:
- Hook的地方比较多,可能会出现兼容问题


真实运行的Activity

以上介绍的都是通过一定的手段“欺骗”系统(AMS),要启动占坑的ActivityProxy。但是AMS真的相信了你要启动ActivityProxy,所以验证通过后它会通知ActivityTread.mH要启动ActivityProxy。在这里也有两种处理方式:

代理Activity

不做处理,就真实启动ActivityProxy,然后再在ActivityProxy生命周期中加载插件的Activity类,然后通过反射同步调用插件Activity的生命周期。
优点:
- 不需要反射等其它操作
- 而且可以在ActivityProxy生命周期中做一些事情,比如:清理缓存、处理插件切换等
缺点:
- 需要反射调用Activity生命周期

Hook mH(Instrumentation)

因为Activity真实创建的过程都是通过ActivityTread.mH调用Instrumentation来创建的,所以可以通过Hook这两个对象让它返回插件中的Activity。这样运行起来的就是插件Activity,而不需要ActivityProxy。
优点:
- 不需要反射调用Activity生命周期
缺点:
- 需要Hook mH或Instrumentation,增加框架的不稳定性
- 不能在插件Activity生命周期中做一些事情


方案选择

我们选择的方案是Hook ActivityManagerNative + 代码Activity,这是在功能和稳定性之间做的平衡

进程管理

插件动行在哪个进程,如何管理?根据插件进程数量可以分为以下几种性况:

  • 每个插件一个进程:如果插件太多,会造成进程数膨胀,只适合少量插件的情况。
  • 固定进程数,插件共用一定数量的进程:两个插件不能同时启动,适合独立插件,插件退出进程会被杀。
  • 所有插件共用一个进程:会出现View类冲突和单独问题

View类冲突

当多个插件在一个进程中时,如果插件中有两个相同的View(包名和类名都相同),哪么在加载第二个插件的时候可能会出现类型转换错误。这是因为LayoutInflater中会缓存View对象,当加载第二个插件中的相同View时会把缓存返回,这样就造成了类型转换错误。

解决方案:判断插件切换时,清除LayoutInflater中的缓存

同理Fragment也会出现这种情况。

单例问题

当多个插件共用主APP中的单例时,这个单例在插件进程中只会初始化一次,如果这个单例中需要Context时。这个Context是属于哪个插件呢?

这种情况在用Context加载本地资源时,就会出现混乱。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值