当前位置: 首页 > >

Android插件化框架总结

发布时间:

为什么使用插件机制

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


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

插件框架的分类

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


    如何在插件Activity中启动另一个Activity?如何加载插件APK中的类?如何加载插件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的启动流程,发现用动态代理或反射可以拦*舳鞒蹋婊黄渲械腎ntent,统称之为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加载本地资源时,就会出现混乱。



友情链接: