生成补丁包的原理
先借用一张图来说明:
接下来我们看一下构建补丁包相关的一些关键类的类图
- Decoder的设计采用了装饰器模式
- 差量dex的生成:
- DexDiffDecoder.patch中没有真正生成,而是把所有的dex新旧对应Map放入一个List中(multiDex的apk可能有多个dex),如果新apk中的dex是新增的,则直接copy到即将生成的补丁文件中
- 之后会调用DexDiffDecoder.generatePatchedDexInfoFile和DexPatchGenerator.executeAndSaveTo,生成补丁文件,同时,会把具体的差异描述写入dex_mata.txt文件
- DexDiffDecoder.diffDexPairAndFillRelatedInfo函数中会调用DexPatchGenerator.executeAndSaveTo(outputStream),针对dex的15个区域进行比较,生成差异dex文件,具体的算法参考 dexdiff算法
- 在此阶段会多生成一个test.dex,它是用来在加载的时候验证dex的加载是否成功的
- 差量res文件、so文件的生成:ResDiffDecoder、BsDiffDecoder bsdiff算法(二进制文件patch算法)
- Let’s RTFSC
合并补丁包的原理
接下来我们看一下合并补丁包相关的一些关键类的类图
合并的过程发生在TinkerPatchService中,在独立进程中进行的:
1android:process=":patch"运行时,将差异patch.dex重新跟原始安装包的旧Dex还原为新的Dex。这个过程可能比较耗费时间与内存,所以我们是单独放在一个后台进程:patch中
- 开始真正合并之前把patch文件copy到了data/data/pkage/tinker目录下,这样做是因为有可能在patch的过程中外部的文件已经被删掉
we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
- 通过DexDiffPatchInternal.extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)验证差分dex 相关合法性(MD5、crc值、是否已存在)通过之后,提取出原dex文件和拆分的dex文件
- 通过DexDiffPatchInternal.patchDexFile(baseApk, patchPkg, oldDexEntry, patchFileEntry, patchInfo, patchedDexFile)来生成合并之后的全量dex,最终是通过DexPatchApplier.executeAndSaveTo(outPutstream)来实现
- DexPatchApplier.executeAndSaveTo(OutputStream out)中会对15个dex区域进行patch操作,针对old dex和patch dex进行合并,生成全量dex文件。每个区域的合并算法采用二路归并,在old dex的基础上对元素进行删除,增加,替换操作。这里的算法和生成补丁的DexDiff是一个逆向的过程。
- Let’s RTFSC
加载补丁包的原理
- 启动的是TinkerApplication
- TinkerApplication通过反射的方式将实际的app业务隔离,这样可以在热更新的时候修改实际的application的内容
- 在TinkerApplication中的onBaseContextAttached中会通过反射调用TinkerLoader的tryLoad加载已经合成的dex
- 主要的工作是在TinkerLoader.tryLoadPatchFilesInternal(tinkerApp, resultIntent)中完成的,做了以下事情:
- tinkerFlag是否开启,否则不加载
- check tinker目录是否生成,没有则表示没有生成全量的dex,不需要重新加载
- tinker/patch.info是否存在,否则不加载
- 读取patch.info,读取失败则不加载
- 比较patchInfo的新旧版本,都为空则不加载
- 判断版本号是否为空,为空则不加载
- 判断patch version directory(//tinker/patch.info/patch-641e634c)是否存在
- 判断patchVersionDirectoryFile(//tinker/patch.info/patch-641e634c/patch-641e634c.apk)是否存在
- checkTinkerPackage,(如tinkerId和oldTinkerId不能相等,否则不加载)
- 检测dex的完整性,包括dex是否全部生产,是否对dex做了优化,优化后的文件是否存在(//tinker/patch.info/patch-641e634c/dex)
- 同样对so res文件进行完整性检测
- 最多重试3次
- loadTinkerJars/loadTinkerResources/
优缺点总结
- 可以替换class、libs、resources
- 补丁包较其它方案要小
- 接入比较简单
- 在单独进程合并补丁包
- 采用Application代理模式,使类Application初始化类也可做热修复,更重要的是解决Android N系统上的混合编译对热修复带来的影响。
- 加载补丁失败,有最多重试3次的保护机制
- 提供多进程文件操作的文件锁(FileLock)
- 成功率较高(官方数据95.5%)
- 需要重启生效
- 不支持新增四大组件(最新的代码可支持Activity)
- 受google应用分发机制,海外版本不能做动态更新
- 多个dex会产生多个patch dex
- 对Application的结构有所改造
参考: