动态加载技术,也叫插件化技术;在技术驱动型公司中扮演着相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和 CPU 占用,还可以实现热插拔,即不在发布新版本的情况下更新某些模块;动态加载是一项很复杂的技术
我们很早开始就在 Android 项目中采用了动态加载技术,主要目的是为了达到让用户不用重新安装 APK 就能升级应用的功能(特别是 SDK项目),这样一来不但可以大大提高应用新版本的覆盖率,也减少了服务器对旧版本接口兼容的压力,同时如果也可以快速修复一些线上的 BUG
这种技术并不是常规的 Android 开发方式,早期并没有完善的解决方案
动态加载技术的原理与实现 动态加载技术原理Android 动态加载技术基本原理:在程序运行时加载一些外部的可执行的文件,然后调用这些文件的某个方法执行业务逻辑;因为文件是可执行的,出于安全问题,Android 并不允许直接加载手机外部存储这类 noexec(不可执行)存储路径上的可执行文件
在 Android 应用中调用它们前:要把这些可执行文件拷贝到 data/packagename/ 内部储存文件路径,确保库不会被第三方应用恶意修改成拦截,然后再将这些可执行文件加载到当前的运行环境并调用需要的方法执行相应的逻辑,从而实现动态调用
动态加载技术如何实现?首先需要获得想要动态加载的可执行文件: 通过 JDK 的编译命令 javac 把 Java 代码编译成 .class 文件,再使用 jar 命令把 .class 文件封装成 .jar 文件,再用 Android SDK 的 DX 工具把 .jar 文件优化成 .dex 文件,即需要的可执行文件
与 JVM不 同,Android 的虚拟机不能用 ClassLoader 类直接加载 .dex,而是需要用 DexClassLoader 类;DexClassLoader 类是 ClassLoader 类的子类,可以加载 jar/apk/dex,可以从 SD 卡中加载未安装的 apk;但 DexClassLoader 并不能直接加载外部存储的 .dex 文件,而是要先拷贝到内部存储里
调用成功后,就可以成功从外部路径动态加载一个 .dex 文件,并执行里面的代码逻辑;但是还不能直接启动插件(指经过处理的 dex 或者 apk)的 Activity
- Activity 等组件需要在 Manifest 中注册后才能以标准 Intent 的方式启动,通过 ClassLoader 加载并实例化的 Activity 实例只是一个普通的 Java 对象,能调用对象的方法,但是它没有生命周期,而且 Activity 等系统组件是需要 Android 的上下文环境的(Context 等资源)
没有这些东西 Activity 根本无法工作想要使用插件里的 Activity 需要解决两个问题:如何使插件 APK 里的 Activity 具有生命周期;如何使插件 APK 里的 Activity 具有上下文环境(使用 R 资源)
- 首先要处理插件 Activity 的生命周期,因为一个 Activity 的启动,如果不采用标准的 Intent 方式,没有经历过 Android 系统 Framework 层级的一系列初始化和注册过程,它的生命周期方法是不会被系统调用的
- 可以通过在主项目里创建一个 ProxyActivity,再由它去代理调用插件 Activity 的生命周期方法;用 ProxyActivity(一个标准的 Activity 实例)的生命周期同步控制插件 Activity 的生命周期
同步的方式既可以在 ProxyActivity 生命周期里用反射调用插件 Activity 相应生命周期的方法,又可以把插件 Activity 的生命周期抽象成接口
- 在 ProxyActivity 的生命周期里调用,然后在插件 Activity 里使用 R 资源; 因为 res 里的每一个资源都会在 R.java 里生成一个对应的 Integer 类型的 id,APP 启动时会先把 R.java注册到当前的上下文环境,在代码里以 R 文件的方式使用资源时正是通过使用这些 id 访问 res 资源,然而插件的 R.java 并没有注册到当前的上下文环境,所以插件的 res 资源也就无法通过 id 使用
- 想要解决此问题,可以通过获取一个 AssetManager 实例,使用其 “addAssetPath” 方法加载 APK 里的资源,再使用 DisplayMetrics、Configuration、CompatibilityInfo 实例一起创建所需要的 Resources 实例
通过 JDK 的编译命令 javac 把 jiava 代码编译成 .class 文件,再使用 jar 命令把 .class 文件封装成 .jar 文件;最后使用 Android SDK 的 DX 工具把 .jar 文件优化成 .dex 文件
通过 DexClassLoader 加载后使用反射或者接口方式调用里面的方法:
private void getOutData(){
File optimizadDexOutputPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"test_dexloafer.jar");
// 无法直接从外部路径加载.dex文件,需要指定APP内部路径作为缓存目录(.dex文件会被解压到此目录)
File dexOutputDir = this.getDir("dex",0);
//构造类加载器
DexClassLoader dexClassLoader = new DexClassLoader(optimizadDexOutputPath.getAbsolutePath(),dexOutputDir.getAbsolutePath(),null,ClassLoader.getSystemClassLoader());
}
使用反射的方式
使用 DexClassLoader 加载进来的类无法直接调用,可以通过反射的方式调用:
private void getOutData(){
File optimizadDexOutputPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"test_dexloafer.jar");
File dexOutputDir = this.getDir("dex",0);
DexClassLoader dexClassLoader = new DexClassLoader(optimizadDexOutputPath.getAbsolutePath(),dexOutputDir.getAbsolutePath(),null,ClassLoader.getSystemClassLoader());
Class libProviderClazz = null;
try {
libProviderClazz = dexClassLoader.loadClass("包名.类名");
//遍历所有的方法
Method[] methods = libProviderClazz.getDeclaredMethods();
for (int i = 0;i mmkv已经有 xml/json,为什么要用 protobuf 项目中使用 protobuf 语言规范 字段约束 编码协议

APK 瘦身方案
- 瘦身原因
- APK 组成
- 代码瘦身
- 代码混淆
- 三方库处理
- 移除无用代码
- 资源瘦身
- 冗余资源
- 图片处理
- 资源混淆
- SO 瘦身
- SO 移除
- 动态加载 SO

由于篇幅原因,手册的部分内容就展示到这里了,有需要这份 Android 性能调优手册 的朋友:可以私信发送 ”进阶“ 即可 直达获取
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命
Android 架构师之路还很漫长,与大家一同共勉