前言
目前app线上的tinker版本为 1.9.14.19,在最近的几个版本技术需求中,我们希望对tinker尝试进行升级,并对内部封装的tinker的包装sdk进行升级。升级过程中的一些热修流程和记录如下。
热修耗时时长分析
测试中,所有测试热修基本均可生效,但是对补丁合成生效时间有要求。耗时具体体现在:
- tinker初始化
- 下载补丁包
- recover dex耗时(合并新dex文件)
- parallel optimize dex(oat耗时)
- 保存补丁信息(更新patch.info文件)
测试结果发现除了parallel optimize dex这一步,其他步骤耗时对比几乎不变,且耗时时长集中在parallel optimize dex这一环节。
升级前后数据对比
start to parallel optimize dex的时间对比(oat耗时)
机型 | 耗时(ms) 线上包(版本:1.9.14.19) | 耗时(ms) 升级包(版本:1.9.14.25.2) |
---|---|---|
pixel 6a(Android 13) | 63333 | 93393 |
1802 | 93400 | |
1858 | 93386 | |
vivo x80(Android 13) | 63360 | 93399 |
2849 | 93457 | |
小米10(android 12) | 63375 | 93435 |
63338 | 93368 | |
华为mate40(android 12) | 4487 | 37506 |
5007 | 34520 | |
小米8(Android 9) | 6205 | 6439 |
6148 | 6220 | |
vivo x9plus(Android 7.1.2) | 78312 (热修不生效) | 78035 (热修不生效) |
131601 (热修不生效) | 79475 (热修不生效) | |
小米5s Plus(Android 6.0.1) | 76613 | 87717 |
64617 | 62501 |
不同Android系统下tinker的oat流程总结
- 华为荣耀系统tinker会通过registerDexModule方法注册dex,不会进行重试步骤(1.9.14.25.2版本重试10次,1.9.14.19版本重试3次);
- android8.0 之下直接使用DexFile.loadDex加载补丁触发dex2oat;
- android8 ~ android10,通过NewClassLoaderInjector.triggerDex2Oat进行dex2oat;
- android10以后不再支持从应用进程调用dex2oat,仅接受系统生成的OAT文件(TinkerDexOptimizer.triggerPMDexOptOnDemand方法);
- 重试的逻辑发生在TinkerDexOptimizer.triggerPMDexOptOnDemand方法中,DexFile.loadDex和进程内通过NewClassLoaderInjector.triggerDex2Oat进行dex2oat不会走该逻辑;该重试的逻辑会增加30s耗时。
- 在老旧手机上步骤二recover dex耗时明显比新设备耗时长,这个和机型性能有关,升级的差异不在步骤二。
相关逻辑请参考版本tinker源码TinkerDexOptimizer.OptimizeWorker类的run方法。
附:patch合成生效时间分析
目前热修是肯定需要重启app的,但是在app拉取热修配置,下载patch包,然后重启app之前的这个时间节点上,tinker需要对patch包进行合成生效的操作,这部分会有耗时。
tinker核心代码:
TinkerPatchService
- doApplyPatch(this, intent);
tinker issue上已经有人反馈了:
补丁从安装到提示安装成功需要多久,我就改了个Toast打印的字符串,需要大概100s,这正常吗?项目比较大 · Issue #1316 · Tencent/tinker · GitHub
最新版本补丁合成很慢(50s),不管 dex2oat 是否成功都会循环 N 次 · Issue #1670 · Tencent/tinker · GitHub
这个问题tinker *v1.9.14.25* 已经做了优化:
**1、**base包更新或有新 patch 合成成功后删除老 patch 的逻辑改为异步实现以降低启动耗时。
**2、**dexopt 触发重试次数缩减到 10 次,避免部分机型 apply patch 耗时太长。
但是从tinker目前的反馈来看,最新的几个版本会出现合成时间变长的case,目前还处于open状态。为了验证升级之后的tinker,在合成生效时间上与之前版本的差异,进行了线上tinker版本(1.9.14.19)的对照测试。测试见下。
附:tinker版本升级内容记录
- 修复了 OPPO & VIVO 部分机型加载 Tinker 时偶现的闪退问题。
- 增加了 Patch Dex 的 Dex2Oat 重试次数,以降低解释模式加载 Patch Dex 的概率。
- 尝试修复极少数情况下因为 resources.apk 被意外修改导致 patch 后的资源找不到或失效的问题。
- 修复了偶现的 Tinker ID 注入失败导致 Patch 包编译失败的问题。
- 优化了判断 resources.apk 被意外修改导致资源 patch 失效的检测逻辑。
- 修复了连续两次 patch 中间更新了 base 包时可能导致最后一次 patch 失效的问题。
- 在 Patch 加载不成功时 installNativeLibraryABIWithoutTinkerInstalled() 方法不再注入 patch so 路径,避免 dex 和 so 不一致的问题。
- 修复了 Android 4.4 设备上 getCurrentInstructionSet() 失败的问题
- 修复了 R8 生成的特殊 DebugInfo 导致编译失败的问题。
- 增加 performSecondaryDexOpt 的重试次数,在重试失败后再尝试 registerDexModule,以尽量减小 Android R 上 odex 没有生成导致的性能开销。
- 在 performSecondaryDexOpt 重试无效前不在 OPPO、VIVO、XIAOMI、REDMI 的 Android S 或更新的系统上调 registerDexModule,规避已知的性能问题。
- 更新 *getProcessName* 的实现,在 *Android P* 及更新的系统上优先尝试 *Application.getProcessName()* 方法。
- 增加了32位 Android N 及更旧的系统上使用解释模式触发 dexopt 的开关以帮助减少 32位上 VmSize 的开销。
- 修复部分机型上资源有变更时 patch 失败的问题。
- base包更新或有新 patch 合成成功后删除老 patch 的逻辑改为异步实现以降低启动耗时。
- dexopt 触发重试次数缩减到 10 次,避免部分机型 apply patch 耗时太长。
- 修复了 anim 资源新增、修改检测逻辑,在 anim 资源发生新增或修改时会中断编译。
- 获取当前进程名的方法在 api level >= 18 的机器上优先使用反射 ActivityThread 的方式实现,若失败则改用 BufferedReader 读 proc 的方式实现。
- 修复了部分机型上无法加载新增资源及修改资源不生效的问题(flutter加载asset时必现)。
- 新增了异步触发patched dex的dex2oat并且不等待其执行完毕的patch合成接口,以满足开启时长较短的App紧急修复问题时需要Patch能快速生效的需求。