现在网络上有许多关于动态加载的介绍的文章,谈及的关键词汇有动态加载
、插件化
、热部署
、热修复
等,对于一些刚接触这方面开发技术的人来说,可能容易混淆。
动态加载的类型
无论是插件化、热部署还是热修复,这些技术的根源都可是说是动态加载,这也是我把 “动态加载” 作为这个系列文章主题的原因。
动态加载,就是在程序运行时,加载外部的可执行文件并运行。这里的运行时就是指应用冷启动并开始工作后;外部可以是可以是 SD 卡,可以是 data 目录,也可以是 jniLib 目录,这些可执行文件是没有随着应用一起编译的。
Android 的动态加载按照工作机制的不同,可以分为虚拟机层动态加载
和 Native 层动态加载
两大类。
运行在虚拟机
简单来说就是只用 JAVA 代码搞定的类型。
基于虚拟机的动态加载技术的核心是类加载器 ClassLoader,通过它我们能够加载一些新的类,这种方式也是目前大部分技术文章谈到的加载方式
。其中,根据 ClassLoader 使用方式的不同,又演变出 “热部署”、“插件化”、“热修复” 等技术。
热部署
加载外部可执行文件的 ClassLoader 实例与原 APP 的 ClassLoader 实例是互相独立的(不在同一棵代理树上),加载进来的新的类与原 APP(宿主)里存在的类互相独立,根据 Java 对类的定义,因为这些类的 ClassLoader 不同,所以他们即便包名和类名一致,或者有继承关系,他们也属于不懂的类。所以以这种方式加载进来的类与原有的类不能互通,不能污染宿主原有的类,适合用来动态加载一些独立的业务,比如一些推广的游戏,在宿主上提供一个入口,用户不需要安装游戏就能运行。因为这种方式起到不用安装就能部署游戏的作用,所以称为热部署。
插件化
加载外部可执行文件的 ClassLoader 实例与宿主的 ClassLoader 实例不是互相独立的,用宿主的 ClassLoader 加载过的类就无法从外部可执行文件中再次加载,它们可以共用一个公共库,习惯上把外部可执行文件称为插件。插件里可以存放公共库里一些借口的实现类,可以有一些新的 Activity 或者 Service 等组件,可以把一些宿主里的业务挪到插件中,插件可以自主升级,不用随着宿主 APP 发版。
热修复
在使用插件化技术的同时,也可以使用插件中的新的类来替换宿主同名的类,这样就能修复宿主中原有的类存在的 BUG。相比插件化,热修复因为不需要考虑组件和 res 资源的问题,所以相对简单得许多,要保证插件种新的类的加载要在加载宿主中原有类的之前。
拆分 DEX
相信大家都知道打包 DEX 时 65536 方法数超标问题,也就是一个 DEX 只能有 65536 个方法,因此有了 multi-dex 的解决方案,把本来只有一个的 DEX,拆分成复数以上的 DEX,运行时挨个加载进来,这其实也算是一种动态加载,只不过实现过程对开发者是透明的。
除此之外,还有另一种拆分 DEX 是用于减少冷启动的时间的。冷启动是指应用第一次从用户点击到完成初始化工作的全部过程。随着现在 APP 的体积不断增长,一些 APP 的 DEX 文件十分庞大,APP 在启动的时候,单单加载所有的 DEX 文件就需要非常多的耗时,所以用户点击 APP 的时候会有一个明显的卡顿过程。因此有一种拆分 DEX 的方案是 “拆分一个启动闪屏用的 DEX,里面只存放启动闪屏界面需要用到的类,因此非常小,其他类放到其他 DEX 里面”,启动的时候因为只需要加载闪屏的 DEX,所以非常快,APP 进入闪屏后,通过异步任务去完成其他 DEX 的加载,就能消除卡顿的过程。
第一种拆分 DEX 是官方支持的,开发者只需要打开 multi-dex 功能即可;第二种拆分 DEX 则需要开发者自己设计。
基于 ClassLoader 的动态加载都有个共同的特点,就是新的类一旦加载进内存了,就无法再次替换了,所以无法在运行时候升级功能,需要重启 APP 才能生效。
运行在 Native
有另一种动态加载方式是工作在 Native 层的,相比于 ClassLoader,在 Native 层的动态加载不需要重新启动 APP 就能生效,这类的加载有 加载 SO 库 和 基于 JNI HOOK 的热修复。
加载 SO 库
加载 SO 库是最常见的 Native 动态加载,我们项目经常中使用 SO 库,编译 APP 的时候,SO 并不会参与编译,会原封不动被拷贝到 APK 包里的 lib 目录下,安装 APK 的时候,系统会扫描 lib 文件夹下支持当前设备 CPU 类型(比如 arm 或 x86)的 SO 库(APK 包会带有多种 CPU 类型对应的 SO 库,安装的时候只需要对应类型的)并拷贝到系统安装目录,APP 在运行时可以调用 System#loadLibrary
方法动态加载对应的 SO 库,此外还可以调用System#load
加载指定路径上的 SO 库。
现在的 APK 里面往往带有非常多的 SO 库,而 APP 运行时只需要用到对应 CPU 类型的 SO 库,因此把 SO 库从 APK 包里剥离出来也是 APK 瘦身的有效手段。
JNI HOOK
基于 JNI HOOK 的热修复技术的代表框架有阿里巴巴的 AndFix。Android 中,修复 BUG 的方式就是更新类的方法,和 ClassLoader 通过加载新的类来更换方法的实现的想法一样,AndFix 也是通过更换方法的做法来实现热修复,不过做法比较取巧。Android 中执行 Native 方法的时候,会去 SO 库中查找对应的 C/C++ 方法,而 AndFix 先把普通 Java 方法用 Native 方法代替,再通过更换不同 SO 库还更换 Native 方法的实现。
网友评论