这一篇覆盖四块 Android 工程实践:热修复的 ClassLoader 原理、Square 的 OKio IO 库、编译期依赖注入 Dagger,以及原生代码的 64 位适配。
一、热修复原理:用 ClassLoader 把新 dex 插到旧 dex 前面
热修复的底层依赖 Android 的类加载机制。Java 虚拟机(JVM)加载的是 class 文件,而 Android 虚拟机(Dalvik/ART)加载的是 dex 文件。它们加载类时都需要 ClassLoader,ClassLoader 有一个子类 BaseDexClassLoader,而 BaseDexClassLoader 下有一个数组——DexPathList,用来存放 dex 文件。当 BaseDexClassLoader 调用 findClass 方法时,实际上就是遍历这个数组,找到相应的 dex 文件就直接 return。
热修复的解决方法,就是把新的 dex 添加到该集合中,并且是加在旧的 dex 前面,所以新类会被优先取出来并 return 返回,旧的有 bug 的类自然就被覆盖掉了。
三个容易踩的坑
- 插入顺序决定优先级:新 dex 必须放在
DexPathList数组前部,放后面就还是加载到旧类。 - 类被加载后不会再换:已经实例化的类不会因补丁重新加载,注意补丁生效时机与冷启动。
- 不是所有改动都能热修:涉及资源、so 或 Manifest 的变更通常超出纯 dex 替换的范畴,要配合对应方案。
适用边界:线上紧急修 bug、不想发版时考虑热修复;大面积重构或涉及原生代码的改动不适合热修复,该发版发版。
二、OKio:把 IO 变成可组合的管道
Okio 是 Square 的 IO 库,Buffer、Source、Sink 比 Java 流更好用。它用段式缓存减少拷贝,超时和取消更清晰。OkHttp 的请求体和响应体都建立在它上面。读文件、写 socket、算哈希都能用同一套 API。ByteString 适合不可变二进制。对 Android 来说,它比手工管理 byte[] 更不容易越界。
几个容易踩的坑
- 记得关闭 Source:
try资源或use扩展可配对,漏关会泄漏底层句柄。 - 大文件不要一次性
readByteArray:会把整个文件读进内存,用流式读取。 - 尊重取消:与协程结合时要响应取消,否则可能阻塞取消信号。
适用边界:任何需要 IO 的地方都可以用 OKio 替代裸 InputStream/OutputStream;测试可用 Buffer 当内存管道。读 OkHttp 源码前先熟悉 OKio 的超时和缓冲,很多「连接池」之外的细节在这一层。
三、Dagger:编译期生成组件图的依赖注入
Dagger 用 APT 在编译期生成组件图:@Module 提供依赖,@Component 组装,@Scope 控制实例数量。构造 @Inject 最省事,接口用 @Binds。Android 上常拆 ApplicationComponent 与 Activity 子组件,避免单例持有界面。它底层大量用工厂与装饰:Provider 延迟创建,Lazy 缓存一次。JavaPoet 生成的代码可读,出错看生成类比看注解更直接。循环依赖可用 Lazy/Provider 打破。
几个容易踩的坑
- 多模块用 component dependency:不要把所有 Module 塞进一个图,否则图会膨胀难维护。
- ProGuard 要 keep 生成的
Dagger*类:混淆掉会导致运行时找不到实现。 - 测试用
@Component.Builder替换 fake Module:不要在测试里硬改生产组件。
适用边界:需要严格、可静态校验的依赖图时选 Dagger;与 Hilt 相比,Dagger 更灵活也更啰嗦,团队若偏好约定优于配置,Hilt 更省事。
四、64 位支持:上架 Google Play 的硬性门槛
Google Play 要求含原生代码的应用提供 64 位版本。armeabi-v7a 之外必须带 arm64-v8a,x86 模拟器还要 x86_64。只提供 32 位 so 会在新设备上被拒或无法安装。Gradle 用 ndk.abiFilters 或 splits.abi。APK Analyzer 检查 lib/ 目录。体积可用 Android App Bundle 按 ABI 分包。老 NDK 交叉编译要开 -fPIC。
三个容易踩的坑
- 第三方 SDK 是常见短板:自己加了
arm64-v8a,但某个旧 SDK 只有 32 位 so,整包仍会被拒,要升级或换库。 - JNI 宽度陷阱:
long与指针在 64 位下变宽,对齐和原子操作的假设可能失效。 - 别只测 32 位真机:上架前必须用 64 位模拟器跑一遍 so 加载与关键路径。
适用边界:只要应用含任何原生代码(so),就必须提供 64 位版本才能上架 Google Play;纯 Java/Kotlin 无原生依赖的应用不受此限。
附:Android 工程四则速查表
| 主题 | 核心结论 | 关键注意项 | 适用边界 |
|---|---|---|---|
| 热修复 | 新 dex 插到 DexPathList 前部优先加载 |
插入顺序、类加载时机、非纯 dex 改动难修 | 线上紧急修 bug,不发版 |
| OKio | 段式缓存的 IO 管道,比裸流安全 | 关 Source、别一次性读大文件、尊重取消 | 替代 InputStream/OutputStream |
| Dagger | 编译期生成组件图,静态可校验 | component dependency、keep 生成类、Builder 测 fake | 需要严格依赖图;啰嗦可选 Hilt |
| 64 位支持 | 含原生代码必须带 arm64-v8a |
第三方 SDK 短板、JNI 宽度、64 位模拟器验证 | 上架 Google Play 的硬性门槛 |
Android 工程实践四则:热修复原理、OKio、Dagger 与 64 位适配
https://lautung.com/archives/android-engineering-practices
评论