插件化技术和热修复技术都属于动态加载技术。
15.1 动态加载技术
在Android传统开发中,一旦应用的代码被打包成APK并被上传到各个渠道市场,我们就不能修改应用的源码了,只能通过服务器来控制应用中预留的分支代码。但是很多时候我们无法提前预知需求和突然发生的情况,也就不能提前在应用代码中预留分支代码,这时就需要采用动态加载技术。
在应用程序运行时,动态加载一些程序中原本不存在的可执行文件并运行这些文件里的代码逻辑。可执行文件总的来说分为两种,一种是动态链接库so,另一种是dex相关文件(dex 以及包含dex的jar/apk文件)。
随着应用开发技术和业务的逐步发展,动态加载技术派生出两个技术,分别是热修复技术和插件化技术,其中热修复技术主要用来修复Bug, 插件化技术则主要用于解决应用越来越庞大以及功能模块的解耦。
15.2 插件化的产生
15.2.1 应用开发的痛点和瓶颈
- 业务复杂,模块耦合
- 应用间的接入
- 65536限制,内存占用大
15.2.2 插件化思想
15.2.3 插件化定义
插件化的客户端由宿主和插件两个部分组成。宿主就是指先被安装到手机中的APK,就是平常我们加载的普通APK。插件一般是指经过处理的APK.so和dex等文件,插件可以被宿主进行加载,有的插件也可以作为APK独立运行。
讲到这里就可以引出插件化的定义:将一个应用按照插件的方式进行改造的过程就叫作插件化。
在协作方面,插件可以由一个人或者一个小组来进行开发,这样各个插件之间,以及插件和宿主之间的耦合度会降低。应用间的接入和维护也变得便捷,每个应用团队只需要负责自己的那一部分就可以了。应用以及主dex的体积也会相应变小,间接地避免了65536限制。第一次加载到内存的只有淘宝主客户端,当使用到其他插件时才会加载相应插件到内存,这样就减少了内存的占用。
15.3 插件化框架对比
15.4 Activity插件化
四大组件的插件化是插件化技术的核心知识点,而Activity 插件化更是重中之重,Activity插件化主要有3种实现方式,分别是反射实现、接口实现和Hook技术实现。反射实现会对性能有所影响,主流的插件化框架没有采用此方式,关于接口实现可以阅读dynamic-load-apk的源码,目前Hook技术实现是主流,因此本章主要介绍Hook技术实现。
Hook技术实现主要有两种解决方案,一种是通过Hook IActivityManager来实现,另一种是Hook Instrumentation实现。在讲到这两个解决方案前,我们需要从整体上了解Activity的启动流程。
15.4.1 Activity的启动过程回顾
首先Launcher进程向AMS请求创建根Activity, AMS会判断根Activity所需的应用程序进程是否存在并启动,如果不存在就会请求Zygote进程创建应用程序进程。应用程序进程启动后, AMS会请求应用程序进程创建并启动根Activity。
普通Activity和根Activity的启动过程大同小异,不涉及应用程序进程的创建,与Launcher也没关系:
在应用程序进程中的Activity 向AMS请求创建普通Activity (步骤1),AMS会对这个Activity的生命周期和栈进行管理,校验Activity等。如果Activity 满足AMS的校验,AMS就会请求应用程序进程中的ActivityThread去创建并启动普通Activity (步骤2)。
15.4.2 Hook IActivityManager方案实现
AMS存在于SystemServer 进程中,我们无法直接修改,只能在应用程序进程中做文章。可以采用预先占坑的方式来解决没有在AndroidManifest.xml 中显式声明的问题,具体做法就是先使用一个在AndroidManifest.xml中注册的Activity来进行占坑,用来通过AMS的校验。接着用插件Activity替换占坑的Activity。
- 注册Activity进行占坑
- 使用占坑Activity通过AMS验证
- 还原插件Activity
- 插件Activity的生命周期
15.4.3 Hook Instrumentation方案实现
Hook Instrumentation 实现同样也需要用到占坑Activity,与Hook IActivityManager实现不同的是,用占坑Activity替换插件Activity以及还原插件Activity 的地方不同。
15.4.4 总结
15.5 Service插件化
15.5.1 插件化方面Service与Activity的不同
我们需要了解在插件化方面Activity和Service有何不同:
- Activity是基于栈管理的,一个栈中的Activity的数量不会太多,因此插件化框架处理的插件Activity 数量是有限的,可以声明有限的占坑Activity 来实现。除去硬件和系统限制,插件化框架处理的插件Service的数量可以是近乎无限的,无法用有限的占坑Service来实现。
- 在Standard模式下多次启动同一个占坑Activity 可以创建多个Activity 实例,但是多次启动占坑Service并不会创建多个Service实例。
- 用户和界面的交互会影响到Activity 的生命周期,因此插件Activity 的生命周期需要交由系统来管理,Hook IActivityManager方案中还原插件Activity 就是为了这一点。Service的生命周期不受用户影响,可以由开发者管理生命周期,没有必要还原插件。
15.5.2 代理分发实现
Activity插件化的重点在于要保证它的生命周期,而Service插件化的重点是保证它的优先级,这就需要用一个真正的Service 来实现,而不是像占坑Activity那样起一个占坑的作用。当启动插件Service时,就会先启动代理Service,当这个代理Service 运行起来之后,在它的onStartCommand等方法里面进行分发,执行插件TargetService 的onCreate等方法,这一方案就叫作代理分发。
- 启动代理Service
- 代理分发
15.6 ContentProvider插件化
15.6.1 ContentProvider的启动过程回顾
15.6.2 VirtualApk的实现
- VirtualApk 初始化
- 启动代理ContentProvider
- 代理分发
15.7 BroadcastReceiver的插件化
15.7.1 广播插件化思路
IntentFilter的,讲到这里我们得到了一个新思路,那就是将静态注册的BroadcastReceiver全部转换为动态注册来处理,虽然静态和动态的BroadcastReceiver 的生命周期不同,但是为了实现插件化,这个缺点显然不是关键问题。
15.7.2 VirtualApk的实现
15.8 资源的插件化
15.8.1 系统资源加载
15.8.2 VirtualApk实现
资源的插件化方案主要有两种:一种是合并资源方案,将插件的资源全部添加到宿主的Resources中,这种方案插件可以访问宿主的资源。另一 种是构建插件资源方案,每个插件都构造出独立的Resources,这种方案插件不可以访问宿主资源。VirtualApk 采用了以上两种方案。
15.9 so的插件化
so热修复主要有两种方案:
- 将so补丁插入到NativeLibraryElement数组的前部,让so补丁的路径先被返回和加载。
- 调用System的load方法来接管so的加载入口。
so的插件化的方案和so热修复第一种方案类似,简单来说就是将so插件插入到NativeLibraryElement数组中,并且将存储so插件的文件添加到nativeLibraryDirectories 集合中就可以了。