【引子】
1、两种开发模式
组件化和插件化
(1)组件化开发:
就是将一个app分成多个模块,每个模块都是一个组件lib(即“公共代码”:统计模块,网络模块、图片处理模块等等),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk。
(2)插件化开发:
将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个插件apk,最终打包的时候将宿主apk和插件apk分开或者联合打包。在宿主apk运行时,动态加载插件apk。
2、什么是动态加载
•应用在运行的时候通过加载一些本地不存在的可执行文件实现一些特定的功能
•这些可执行文件是可以替换的
•更换静态资源(比如换启动图、换主题、或者用服务器参数开关控制广告的隐藏现实等)不属于动态加载
【为什么要动态加载】
1、安卓的组件化
app工程的组件分类:
一般分为两种:application组件和lib组件。
(1)application组件是指该组件本身就可以运行并打包成apk
(2)lib组件是指该组件属于app的一部分,可以供其它组件使用但是本身不能打包成apk。
为什么要有组件化?
假如一个app工程只有一个组件,随着app业务的壮大模块越来越多,代码量超10万是很正常的,这个时候我们会遇到以下问题
(1)公共资源、业务、模块混在一起耦合度太高,维护和开发成本太高
(2)不方便单元测试
2、安卓的插件化
为什么要有插件化?
插件化开发相比于组件化开发的好处,大致如下:
(1)规避一些安卓市场的限制
比如“广告模块”进行插件化,宿主apk送审的初始版本没有广告(安卓市场开始扫描APK里面的Manifest甚至dex文件,查看开发者的APK包里是否有广告的代码,如果有就有可能审核不通过),送审通过后通过热部署更新插件化的广告模块
(2)65535方法数:Android平台史上最坑天花板(没有之一)
(3)免安装:插件apk免安装,便于插件化模块更新。
(4)热部署:动态更新插件,提高到达率(提升版本到达率:优化A/B Test的效果)。
(5)团队合作:宿主工程和插件工程分开,协同并行开发。
(6)编译速度:宿主工程和插件工程单独编译,节省编译时间
(7)启动速度:插件模块可以用懒加载的方式在需要的时候才初始化,从而提高应用初始化时的启动速度
(8)减少应用大小:按需下载插件和加载插件
【发展历史】
•2012.07 AndroidDynamicLoader 大众点评的屠毅敏
——用Fragment来实现。通过动态加载不同的Fragement,把想换的页面都换掉。
•2013年初 自定义控件动态下载 23Code
——提供了一个壳,在这个壳里可以动态下载插件,然后动态运行。可以在壳外编写各种各样的控件,放在这个框架下去运行。
•2014年初 ATLAS 阿里的伯奎
——Altas见视频,讲了淘宝的这项技术的大方向。但是很多技术细节没有分享。
•2014.10 dynamic-load-apk 任玉刚
——这跟后续介绍的很多插件化项目都不太一样。它没有Hook太多的系统底层方法,而是从上层,即App应用层解决问题,创建一个继承自Activity的ProxyActivity类,然后让插件中的所有Activity都继承自ProxyActivity,并重写Activity所有的方法。
•2015.08 DroidPlugin 360的张勇
——把任意的App都加载到宿主里。可以基于这个框架写一个宿主App,然后就可以把别人写的App都当作插件来加载。这个框架的功能的确很强大,但强大的代价就是要改写很多Android系统的底层代码
接下来登场的是热修复技术。
•之后 插件化框架,百花齐放
•2015.09 热修复技术,开始萌芽(AndFix)
【技术流派】
(1)动态替换(hook):在Activity里做Hook、更抽象的层面Hook(startActivity)、AMS。
<1>在Activity里做Hook:重写getAsset的几个方法,从而使用自己的ResourceManager和AssetPath
<2>更抽象的层面:也就是在startActivity方法的位置做Hook,涉及的类包括ActivityThread、Instrumentation
<3>最高层次则是在AMS上做修改:也就是张勇的解决方案,这里需要修改的类非常多,AMS、PMS等都需要改动
总之,在越抽象的层次上做Hook,需要做的改动就越大,但好处就是更加灵活了。没有哪一个方法更好,一切看你自己的选择。
(2)静态代理:插件继承代理类。
——写一个PluginActivity继承自Activity基类,把Activity基类里面涉及生命周期的方法全都重写一遍,插件中的Activity是没有生命周期的,所以要让插件中的Activity都继承自PluginActivity,这样就有生命周期了。
(3)Dex合并:两个dex中有同样的class的话。先加载哪个dex类,就使用那个dex中的对应class。
——原生Apk自带的Dex是通过PathClassLoader来加载的,而插件Dex则是通过DexClassLoader来加载的。但有一个顺序问题,是由Davlik的机制决定的,如果宿主Dex和插件Dex都有一个相同命名空间的类的方法,那么先加载哪个Dex,哪个Dex中的这个类的方法将会占山为王,后面其他同名方法都替换了。
【核心技术点】
1、类的加载
关于动态加载apk,理论上可以用到的有DexClassLoader、PathClassLoader和URLClassLoader。
DexClassLoader:可以加载文件系统上的jar、dex、apk
PathClassLoader :可以加载/data/app目录下的apk,只能加载已经安装的apk
URLClassLoader :可以加载java中的jar,但是由于dalvik不能直接识别jar,所以此方法在android中无法使用。
关于jar、dex和apk,dex和apk是可以直接加载的,因为它们都是或者内部有dex文件;而原始的jar是不行的,必须转换成dalvik所能识别的字节码文件,转换工具可以使用android sdk中platform-tools目录下的dx转换命令:
dx--dex --output=dest.jar src.jar
类的加载——插件apk中的activity其实就是一个普通的对象,不是真正意义上的activity(没有在宿主程序中注册且没有完全初始化),不具有activity的性质,因为系统启动activity是要做很多初始化工作的,而我们在应用层通过反射去启动activity是很难完成系统所做的初始化工作的,所以activity的大部分特性都无法使用包括activity的生命周期管理,这就需要我们自己去管理。DL采用了接口机制,将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理,并且没有采用反射,当我们想增加一个新的生命周期方法的时候,只需要在接口中声明一下同时在代理activity中实现一下即可。
2、资源加载
Asset资源和Resource资源。
通过反射调用AssetManager的addAssetPath方法,我们可以将一个插件apk中的资源加载到AssetManager中,然后再通过AssetManager来创建一个新的Resources对象,然后就可以通过这个Resources对象来访问插件apk中的资源了。
3、activity生命周期的管理
采用接口机制,将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理。
——插件apk中的activity其实就是一个普通的对象,不是真正意义上的activity(没有在宿主程序中注册且没有完全初始化),不具有activity的性质,因为系统启动activity是要做很多初始化工作的,而我们在应用层通过反射去启动activity是很难完成系统所做的初始化工作的,所以activity的大部分特性都无法使用包括activity的生命周期管理,这就需要我们自己去管理。DL采用了接口机制,将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理,并且没有采用反射,当我们想增加一个新的生命周期方法的时候,只需要在接口中声明一下同时在代理activity中实现一下即可。
【插件apk的加载总流程】