Appearance
dynamic-delivery-hot-update
1. 实验目标
用最小骨架演示三类「动态化 / 热更新」机制(对应 docs/05-bridge-hybrid/08):
- Android App Bundle / Dynamic Feature:用 Play Core 按需拉取 feature 模块
- ClassLoader 热更新(Tinker/Instant Run 思路):在
BaseDexClassLoader的dexElements前插入补丁 dex,使补丁类优先加载 - React Native / CodePush 思路:JS bundle 远程下发、运行时 reload(本实验给出加载顺序骨架,不引入 RN 依赖)
本实验以骨架代码 + 注释形式给出(见「7. 关键源码」);完整可运行的 Dynamic Feature 需要 Android App Bundle 工程,暂不接入 Android Host 构建。
2. 工程要点
- Dynamic Feature:在
build.gradle声明apply plugin: 'com.android.dynamic-feature',主工程android.dynamicFeatures += [':feature'];运行时SplitInstallManager请求安装 - 类加载热更新:反射拿到
BaseDexClassLoader.pathList.dexElements,将补丁 dex 的dexElements拼到数组最前面(双亲委派下优先命中补丁) - 资源/so 同理需单独处理;补丁需签名校验,否则成投毒入口
3. 运行方式
bash
# 1) Dynamic Feature(需 bundletool)
./gradlew :app:assembleDebug
bundletool build-apks --bundle app.aab --output app.apks
bundletools install-apks --apks app.apks
# 2) 类加载热更新:直接运行「7. 关键源码」中的 hotfixClassLoader 演示(JVM 环境即可验证加载顺序)4. 预期现象
- Dynamic Feature:首次进入相关功能时触发下载,下载完成后才能进入(按需加载)
- 类加载热更新:打补丁后,反射查询
dexElements顺序,补丁 dex 位于数组首位 → 补丁类被优先解析
5. 常见误区
| 误区 | 实际情况 |
|---|---|
| 热更新能改任何代码 | 不能改 <application>、不能改 AndroidManifest、不能改 SO 加载方式(有平台限制与合规风险) |
| 补丁直接覆盖类即可 | 必须插到 dexElements 前面,且要处理资源/so/签名,否则崩溃或无效 |
| Dynamic Feature = 热更新 | 前者是「按需安装」(需商店),后者是「不发包修 bug」,机制与合规边界不同 |
6. 对应知识库文档
7. 关键源码
kotlin
// 类加载热更新:将补丁 dex 插到 pathList.dexElements 最前面(示意)
fun installHotfixPatch(classLoader: BaseDexClassLoader, patchDex: File) {
val pathList = classLoader.javaClass.getDeclaredField("pathList").apply { isAccessible = true }.get(classLoader)
val dexElementsField = pathList.javaClass.getDeclaredField("dexElements").apply { isAccessible = true }
val oldElements = dexElementsField.get(pathList) as Array<Any>
val makeDexElements = pathList.javaClass.getDeclaredMethod(
"makeDexElements", List::class.java, File::class.java, List::class.java, ClassLoader::class.java
).apply { isAccessible = true }
val patchElement = (makeDexElements.invoke(
pathList, listOf(patchDex), patchDex.parentFile, mutableListOf<IOException>(), classLoader
) as Array<Any>)[0]
val newElements = Array(oldElements.size + 1) { i ->
if (i == 0) patchElement else oldElements[i - 1] // 补丁置顶:优先命中
}
dexElementsField.set(pathList, newElements)
}接入状态
📋 文档化(未接入宿主编译) :真·Dynamic Feature(com.android.dynamic-feature feature module)或 Tinker 热修需要额外模块/补丁构建链,过重;本 README 保留 Play Core 拉取与 Dex 插桩骨架供对照,未生成可运行的宿主 App 入口。