Tinker 常见问题

Tinker 官网

Tinker GitHub 主页

Tinker 常见问题

大概目录:
* Tinker 编译相关问题?

* Tinker 库中有什么类是不能修改的?

* 什么类需要放在主 dex 中?

* 我应该使用哪个作为补丁包下发,如何做多次修复?

* 如何对 Library 文件作补丁?

* 如何对资源文件作补丁,为什么有时候会提示大量没有改变的图片发生变更?

* Tinker 中的 dex 配置 'raw' 与 'jar' 模式应该如何选择?

* 如何兼容多渠道包?

* tinker 是否兼容加固?

* Google Play 版本是否可以有 Tinker 相关代码?

* tinker 与 instant run 的兼容问题?

* 每次编译我应该保留哪些文件,如何兼容 AndResGuard?

* tinkerId 应该如何选择?

* 如何使生成的补丁包更小?

* 关于使用的 ClassLoader 问题?

* 什么时候调用 installTinker?

* Proguard 5.2.1 applymapping 出现 Warning?

* TinkerPatch 补丁管理后台与 Tinker 的关系?

* Tinker 的最佳实践?

一、Tinker 编译相关问题?

编译过程相关的 issue 请先查看是否是以下情况:

  1. 无法打开 sample 工程: 请使用单独的 IDE 窗口打开 tinker-sample-android 工程;

  2. tinkerId is not set:是因为没有正确地配置 IDE 的 git 路径,若不是通过 clone 方式下载 tinker,需要本地手动 commit 一次。这里你也可以使用其他字符作为 tinkerId

  3. 对于编译与补丁时发生的异常,请到 Tinker 自定义扩展 中查看具体错误码的原因。并通过 "Tinker." 过滤 Tinker 相关的日志提交到 issue 中;

  4. 若自定义 TinkerResultService,请务必将新的 Service 添加到 Manifest 中;

  5. 权限问题;请务必已经将读取 sdk 权限添加到 AndroidManifest.xml 中,并且已允许权限运行;

  6. 若使用 DefaultLifeCycle 注解生成 Application,需要将原来 Application 的实现移动到 ApplicationLike 中,并将原来的 Application 类删掉;

  7. 关于 Application 的改造这一块大家比较疑惑,这块请认真阅读自定义 Application 类,大部分的 app 应该都能在半小时内完成改造。

  8. 如果出现 Class ref in pre-verified class resolved to unexpected implementation 异常,请确认以下几点:Application中传入 ApplicationLike 的参数时是否采用字符串而不是 Class.getName 方式;新的 Application 是否已经加入到 dex loader pattern 中;额外添加到 dex loader pattern 中类的引用类也需要加载到 loader pattern 中。

二、Tinker 库中有什么类是不能修改的?

Tinker 库中不能修改的类一共有 26 个,即 com.tencent.tinker.loader.* 类。加上你的 Appliction 类,只有 26 个类是无法通过 Tinker 来修改的。即使类似 Tinker.java 等管理类,也是可以通过 Tinker 本身来修改。

注意,在 1.7.6 版本之前,我们需要手动将不能修改的类添加到 tinkerPatch.dex.loader pattern 中。对于 1.7.6 以后的版本会自动生成。

但是若使用 newApk 或者命令行编译,需要手动添加 Application 类与 loader 类

三、什么类需要放在主 dex 中?

Tinker 并不干涉你分包与多 dex 的加载逻辑,但是你需要确保以下几点:

  1. com.tencent.tinker.loader.* 类,你的 Application 类需要在主 dex,并且已经在 dex.loader 中配置;

  2. 若你自定义了 TinkerLoader 类,你需要将 TinkerLoader 的自定义类,以及它用的到类也放在主 dex,并且已经在 dex.loader 中配置;

  3. ApplicationLike 的继承类也需要放在主 dex 中,但是它无须在 dex.loader 中配置,因为它是可以使用 Tinker 修改的类。最后,如果你需要在加载其他 dex 之前加载 Tinker 的管理类,你也可以将 com.tencent.tinker.* 都加入到主 dex。

  4. 你的 ApplicationLike 实现类的直接引用类以及在调用 Multidex install 之前加载的类也都需要放到主 dex 中。

注意:Tinker 会自动生成需要放在主 dex 的 keep 规则。在 1.7.6 版本之前,你需要手动将生成规则拷贝到自己的 multiDexKeepProguard 文件中。例如 Sample 中的 multiDexKeepProguard file("keep_in_main_dex.txt")。在 1.7.6 版本之后,这里会通过脚本自动处理,无须手动填写。

另外,如果 minsdkverion >= 21multiDexEnabled 会被忽略。我们可以在 build/intermediates/multi-dex 查找最终的 keep 规则以及结果。

四、我应该使用哪个作为补丁包下发,如何做多次修复?

patch_signed_7zip.apk 是已签名并且经过 7z 压缩的补丁包,但是你最好重命名一下,不要让它以 .apk 结尾,这是因为有些运营商会挟持以 .apk 结尾的资源。

另外一点,我们在发起补丁请求时,需要先将补丁包先拷贝到 dataDir 中。因为在 sdcard 中,补丁包是极其容易被清理软件删除。这里可以参考 UpgradePatchRetry.java 的实现。

对于补丁包的版本问题,我们可以在 packageConfig 中增加,例如 sample 中的

packageConfig {
    /**
     * patch version via packageConfig
     */
     configField("patchVersion", "1.0")
}

Tinker 支持对同一基准版本做多次补丁修复,在生成补丁时,oldApk 依然是已经发布出去的那个版本。即补丁版本二的 oldApk 不能是补丁版本一,它应该依然是用户手机上已经安装的基准版本。

五、如何对 Library 文件作补丁?

当前官方并没有直接将补丁的 lib 路径添加到 DexPathList 中,理论上这样可以做到程序完全没有感知的对 Library 文件作补丁。这里主要是因为在多 abi 的情况下,某些机器获取的并不准确。当前对 Library 文件作补丁可参考 Tinker API 概览,tinker 1.7.7 版本中也提供了一键反射的方案给大家选择。

大家可以根据自己的项目需要选择合适的方案,事实上,无论是对 Library 还是 Application,官方都是采用尽量少去反射的策略,这也是为了提高 Tinker 框架的兼容性。上线前,我们应当严格测试补丁是否正确加载了修改后的 So 库。

六、如何对资源文件作补丁,为什么有时候会提示大量没有改变的图片发生变更?

Tinker 采用全量合成方式实现资源替换,这里有以下几点是使用者需要明确的:

  1. remoteView 是无法修改,例如 transition 动画,notification icon 以及桌面图标;

  2. 对于资源文件的更新(尤其是 assets),需要注意代码中是否采用直接读取 sourceApk 路径方式读取,这样方式是无法更新的;
    Tinker 只会将满足 res pattern 的资源放在最后的合成补丁资源包中。一般为了减少合成资源大小,官方不建议输入 classes.dexlib 文件的 pattern

  3. 若一个文件 :assets/classes.dex,它既满足 dex pattern,又满足 res pattern。Tinker 只会处理 dex pattern,然后在合成资源包会忽略 assets/classes.dex 的变更。library 也是如此。

  4. 只要资源发生变成的前提下 Tinker 才会合成新的资源包,这一定程度会增加占 Rom 体积,请在考虑后使用。

注意:若出现资源变更,我们需要使用 applyResourceMapping 方式编译,这样不仅可以减少补丁包大小,同时防止 remote view id 变更造成的异常情况。最后我们应该查看编译过程中生成的 resources_out.zip 是否满足我们的要求。

有时候会发现大量明明没有改变的 png 发现变更,解压发现的确两次编译这些 png 的 md5 不一致。经分析,aapt 在其中一次编译将 png 优化成 8-bit,另外一次却没有,从而导致 png 改变了。如果你们 app 出现了这种情况,官方建议关闭 aaptpng 的优化:

aaptOptions{
    cruncherEnabled false
}

若你对安装包大小非常 care,可以提前使用命令行工具将所有图片手动优化一次。我们也可以选择一些有损压缩工具,获得更大的压缩效果。

如果你确认 png 并没有修改,你可以在 tinker 的配置使用 ignoreChange 来忽略所有 png 文件的修改。

res {
    ignoreChange = ["*.png"]
}

七、Tinker 中的 dex 配置 'raw' 与 'jar' 模式应该如何选择?

它们应该说各有优劣势,大概应该有以下几条原则:

  1. 如果你的 minSdkVersion 小于 14,那你务必要选择 'jar' 模式;

  2. 以一个 10M 的 dex 为例,它压缩成 jar 大约为 4M,即 'jar' 模式能节省 6M 的 ROM 空间。

  3. 对于 'jar' 模式,我们需要验证压缩包流中 dex 的 md5,这会更耗时,在小米 2S 上数据大约为 'raw' 模式 126 ms,'jar' 模式为 246 ms

因为在合成过程中 Tinker 已经校验了各个文件的 Md5,并将它们存放在 /data/data/.. 目录中。默认每次加载时 Tinker 并不会去校验 tinker 文件的 Md5,但是你也可通过开启 loadVerifyFlag 强制每次加载时校验,但是这会带来一定的时间损耗。

简单来说,'jar' 模式更省空间,但是运行时校验的耗时大约为 'raw' 模式的两倍。如果你没有打开运行时校验,推荐使用 'jar' 模式。

八、如何兼容多渠道包?

关于渠道包的问题,若使用 flavor 编译渠道包,会导致不同的渠道包由于 BuildConfig 变化导致 classes.dex 差异。这里建议的方式有:

  1. 将渠道信息写在 AndroidManifest.xml 或文件中,例如 channel.ini

  2. 将渠道信息写在 apk 文件的 zip comment 中,这种是建议方式,例如可以使用项目 packer-ng-plugin 或者可使用 V2 Schemewalle

  3. 若不同渠道存在功能上的差异,建议将差异部分放于单独的 dex 或采用相同代码不同配置方式实现;

事实上,tinker 也支持多 flavor 直接编译多个补丁包,具体可参考多 Flavor 打包

九、tinker 是否兼容加固?

tinker 1.7.8 可以通过 isProtectedApp 开启加固支持,这种模式仅仅可以使用在加固应用中。

加固厂商 测试
腾讯云·乐固 Tested
爱加密 Tested
梆梆加固 Tested
360加固 Tested,需要 5 月 8 日以后加固的版本
其他 请自行测试,只要满足下面规则的都可以支持

这里是否支持加固,需要加固厂商明确以下两点:

  1. 不能提前导入类;

  2. art 平台若要编译 oat 文件,需要将内联取消。

十、Google Play 版本是否可以有 Tinker 相关代码?

由于 Google play 的使用者协议,对于 GP 渠道我们不能使用 Tinker 动态更新代码,这里会存在应用被下架的风险。但是在 Google play 版本,我们依然可以存在 Tinker 的相关代码,但是我们需要屏蔽补丁的网络请求与合成相关操作。

十一、tinker 与 instant run 的兼容问题?

事实上,若编译时都使用 assemble*tinkerinstant run 是可以兼容的。但是不少用户基础包与补丁包混用两种模式导致补丁过大,所以 tinker 编译时禁用 instant run,我们可以在设置中禁用 instant run 或使用 assemble 方式编译。

大家日常 debug 时若想开启 instant run 功能,可以将 tinker 暂时关闭:

ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = false
}

十二、每次编译我应该保留哪些文件,如何兼容 AndResGuard?

正如 sample 中 app/build.gradle,每个可能用到 Tinker 发布补丁的版本,需要在编译后保存以下几个文件:

  1. 编译后生成的 apk 文件,即用来编译补丁的基础版本;

  2. 若使用 proguard 混淆,需要保持 mapping.txt 文件;

  3. 需要保留编译时的 R.txt 文件;

  4. 若你同时使用了资源混淆组件 AndResGuard,你也需要将混淆资源的 resource_mapping.txt 保留下来,同时将 r/* 也添加到 res pattern 中。具体我们可以参考 build.gradle

微信通过将补丁编译与 Jenkins 很好的结合起来,只需要点击一个按钮,即可方便的生成补丁包。也可以参考 tinkerpatch-andresguard-sample

十三、tinkerId 应该如何选择?

tinkerId 是用来区分基准安装包的,我们需要严格保证一个基准包的唯一性。在设计的初期,官方使用的是基准包的 CentralDirectoryCRC,但某些 APP 为了生成渠道包会对安装包重新打包,导致不同的渠道包的 CentralDirectory 并不一致。

编译补丁包时,Tinker 会自动读取基准包 AndroidManifesttinkerId 作为 package_meta.txt 中的 TINKER_ID。将本次编译传入的 tinkerId,作为 package_meta.txt 中的 NEW_TINKER_ID。当前 NEW_TINKER_ID 并没有被使用到,只是保留作为配置项。如果我们使用 git rev 作为 tinkerid,这时只要使用 git diff TINKER_ID NEW_TINKER_ID 即可获得所有的代码差异。

我们需要保证 tinkerId 一定是要唯一性的,这里推荐使用 git rev 或者 svn rev。如果我们升级了客户端版本,但 tinkerId 与旧版本相同,会导致可能会加载旧版本的补丁。这里我们一定要注意,升级可客户端版本,需要更新 tinkerId

十四、如何使生成的补丁包更小?

对于代码来说,我们最好记住以下几条规则:

  1. 编译补丁包时,proguard 使用 applymapping 模式;

  2. 对于多 dex 的情况,保持原本的分包规则,尽量减少由于分包变化而带来的变更。在生成补丁包过程中,对于 class 分包的变化将会输出 Warning:Class Moved 日志,我们应该尽量减少这种变化;

  3. 大量静态常量的改变与资源 R 文件的变更,这里推荐使用 applyResouceMapping 方式保持资源 ID。大量类分包的改变对补丁包的影响不大,但是对于合成的时间消耗与占 ROM 的体积影响更大。我们每次生成补丁后,都应该查看 TinkerPatch 输出文件夹的日志;

  4. 其他的例如使用 force jumbo 模式以及使用 7zip 压缩补丁包。

十五、关于使用的 ClassLoader 问题?

Tinker 没有使用 parent classloader 方案,而是使用 Multidex 插入 dexPathList 方式,这里主要考虑到分平台内部类可能存在校验 classloader 的问题。

  1. SDK >= 24,即 Android N 版本,当补丁存在时,Tinker 将 PathClassloader 替换为 AndroidNClassLoader,但是它依然继承与 PathClassLoader。我们依然可以像以往那样对它进行类似 makeDexElements 的操作。;

  2. SDK < 14,Tinker 没有对 classloader 做处理,这里需要注意补丁的 Dex 是插入在 dexElement 的前方。

十六、什么时候调用 installTinker?

首先我们推荐在最开始的时候就是执行 installTinker 操作,但是即使你不去 installTinker,也不会影响 Tinker 对代码、So 与资源的加载。installTinker 只是做了以下几件事件:

  1. 回调 LoadReporter,返回加载结果;

  2. 初始化各个自定义类与 Tinker 实例,可以调用 Tinker 相关 API,发起升级补丁以及处理相关的回调。

事实上,微信只在主进程与 :patch 进程执行 installTinker 操作。其他进程只要不处理回调结果,不发起补丁请求即可。在 SampleUncaughtExceptionHandler 中,为了防止 Crash 并没有执行 installTinker,全部使用的是 TinkerApplicationHelper 中的 API,详细可以查看 Tinker API 概览

十七、Proguard 5.2.1 applymapping 出现 Warning?

这是因为 5.2.1 增加了内联函数的行输出信息导致,你可以使用以下几种方法解决:

  1. 使用 5.1 版本 proguard

  2. 将内联函数的优化关掉;

  3. 自己对 mapping 文件去除内联函数的行信息。

如果使用 4.X 版本的 Proguard 强烈建议升级到 5.1 版本。可以先下载 5.1 的 Proguard, 然后通过以下方式指定:

 classpath files('proguard-5.1.jar')

若使用 gradle 编译,与 multiDexKeepProguard 不同,我们无需将生成的 tinker_proguard.pro 拷贝到自己的配置中。另外一个方面,若 applymapping 过程出现冲突,我们可以采取以下几个方法:

  1. 添加 ignoreWarning;需要注意的是如果某些类的确需要采用新的 mapping,这样补丁后 App 会出问题,一般并不建议采用这种方式;

  2. 修改基准包的 mapping 文件;我们需要根据新的 mapping 文件,修正基准包的 mapping 文件。例如将 warning 项删掉或者将新 mappingkeep 的项复写到基准的 mapping 中。可以参考脚本 proguard_warning.pymerge_mapping.py

注意,如果想通过直接删除旧 mapping 文件的冲突项,需要注意删除类的内部类是否存在混淆冲突。

十八、TinkerPatch 补丁管理后台与 Tinker 的关系?

TinkerPatch 平台是第三方开发基于 CDN 分发的补丁管理后台。它提供了补丁后台托管,版本管理,一键傻瓜式接入等功能,让我们可以无需修改任何代码即可轻松接入Tinker。

我们可以根据自己的需要选择接入,它是独立于 Tinker 项目之外。

十九、Tinker 的最佳实践?

为了使补丁的成功率更高,官方在 Sample 中还做了以下工作:

  1. 由于合成进程可能被各种原因杀死,使用 UpgradePatchRetry.java 来做重试功能,提高成功率;

  2. 防止补丁后程序无法启动,使用 SampleUncaughtExceptionHandler.javacrash 启动保护。这里更推荐的是进入安全模式,使用配置的方式强制清理或者升级补丁;

  3. 为了防止 BuildConfig 的改变导致大量类的变更,使用 BuildInfo.java 非 final 的变量来中转。

  4. 为了加快补丁应用同时保持用户体验,SampleResultService.java 在应用退入后台或手机灭屏时,才杀掉进程。你也可以在杀掉进程前,直接通过发送 broadcast 或 service intent 的方式尽快的重启进程。

  5. jumboMode 打开,防止由于字符串增多导致 force-jumbol,导致更多的变更。

  6. 使用 zip comment 方式生成渠道包。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容