这类文章已经很多了,写这个是为了记录下自己的使用过程和在使用中遇到过的一些问题。
看过一句话,无论学习什么技术,以官方文档为主,教程文章为辅。所以认真看文档。
Tinker
介绍下Tinker,其实这些官方文档都有,写在这里也就是为了自己再看的时候方便。
Tinker是什么
Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。也可以使用Tinker来更新插件。
主要包括以下几个部分:
- gradle编译插件:tinker-patch-gradle-plugin
- 核心sdk库:tinker-android-lib
- 非gradle编译用户的命令行版本:tinker-patch-cli.jar
与其他方案比较
阿里的AndFix、美团的Robust以及QZone的超级补丁方案
Tinker | QZone | AndFix | Robust | |
---|---|---|---|---|
类替换 | yes | yes | no | no |
SO替换 | yes | no | no | no |
资源替换 | yes | yes | no | no |
全平台支持 | yes | yes | yes | yes |
即时生效 | no | no | yes | yes |
性能消耗 | 较小 | 较大 | 较小 | 较小 |
补丁包大小 | 较小 | 较大 | 一般 | 一般 |
开发透明 | yes | yes | no | no |
复杂度 | 较低 | 较低 | 复杂 | 复杂 |
gradle支持 | yes | no | no | no |
ROM体积 | 较大 | 较小 | 较小 | 较小 |
成功率 | 较高 | 较高 | 一般 | 最高 |
- AndFix作为native解决方案,首先面临的是稳定性与兼容性问题,更重要的是它无法实现类替换,它是需要大量额外的开发成本的;
- Robust兼容性与成功率较高,但是它与AndFix一样,无法新增变量与类只能用做的bugFix方案;
- Qzone方案可以做到发布产品功能,但是它主要问题是插桩带来Dalvik的性能问题,以及为了解决Art下内存地址问题而导致补丁包急速增大的。
特别是在Android N之后,由于混合编译的inline策略修改,对于市面上的各种方案都不太容易解决。而Tinker热补丁方案不仅支持类、So以及资源的替换,它还是2.X-8.X(1.9.0以上支持8.X)的全平台支持。利用Tinker我们不仅可以用做bugfix,甚至可以替代功能的发布。
不过这些是Tinker文档的内容,想具体了解其他方案还是到每个的官方文档进行了解AndFix、Robust。
已知问题
- Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件(1.9.0支持新增非export的Activity);
- 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
- 在Android N上,补丁对应用启动时间有轻微的影响;
- 不支持部分三星android-21机型,加载补丁时会主动抛出"TinkerRuntimeException:checkDexInstall failed";
- 对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标
开始接入
gradle接入
在项目的build.gradle文件中,添加tinker-patch-gradle-plugin的依赖
dependencies{
classpath "com.tencent.tinker:tinker-patch-gradle-plugin:x.x.x"
}
在app的build.gradle文件中,添加tinker的库依赖以及apply tinker的gradle插件.
dependencies{
//可选,用于生成application类
provided('com.tencent.tinker:tinker-android-anno:x.x.x')
//tinker的核心库
compile('com.tencent.tinker:tinker-android-lib:x.x.x')
}
...
...
//apply tinker插件
apply plugin: 'com.tencent.tinker.patch'
这三个依赖在添加的时候版本号必须保持一致。
配置gradle中的参数
我自己在build.gradle中参数的配置基本是按照demo中build.gradle配置的,或者也可以查看文档中的参数说明,弄清楚每个参数代表什么以后,根据自己的需求进行配置。
修改Application类
这里有两种方法进行修改,一种是将自己的Application类继承TinkerApplication.Java;第二种是使用Annotation来自动生成Application类。官方推荐是使用第二种方式,Annotation自动生成Application类,既然已经用了Tinker方案了,就按官方推荐的接入。文档中自定义Application类。
要用Annotation来自动生成Application类的原因是:程序启动时会加载默认的Application类,这导致补丁包无法对其做修改。
具体实现:
1、新建一个自己的ApplicationLike类,让其继承DefaultApplicationLike,并添加相关注释
@DefaultLifeCycle(
application = ".MyApplication", //application类名
flag = ShareConstants.TINKER_ENABLE_ALL, //tinkerFlag
loaderClass = "com.tencent.tinker.loader.TinkerLoader", //loaderClassName, 我们这里使用默认即可!
loadVerifyFlag = false
)
public class MyApplicationLike extends DefaultApplicationLike{
...
}
2、将原先自己的Application类以及他的继承类的所有代码拷贝到新建的自己的ApplicationLike中。
3、原先Application的attachBaseContext方法实现要单独移到onBaseContextAttached中。
4、在现在自己的ApplicationLike中,所有引用到application的地方改成getApplication()。
5、对其他引用原先Application或者它的静态对象和方法的地方,改成引用现在自己的ApplicationLike的静态对象和方法。
通过Annotation方式生成Application类,需要将原来自己的Application类删除,其他任何类都不能再引用原来自己的Application类。
修改好Application以后,在AndroidManifest.xml中,修改application的name属性,与MyApplicationLike注释中写的保持一致。之后在自己的ApplicationLike中的onBaseContextAttached()方法中,通过TinkerInstaller.install()来初始化Tinker。到此为止,Tinker的接入已初步完成,接下来可以实现补丁功能。
实现补丁功能
将基准包安装到手机,修改需要更新的内容后,将基准包和相应文件的路径填写在build.gradle中的对应位置上,调用tinkerPatch Task进行编译,生成补丁包,将补丁包放在手机的sdcard中,用基准包安装在手机上的应用加载补丁,加载成功后重启,补丁生效,更新成功。
官方demo使用方法
生成补丁包
在成功接入tinker并且build完成以后,在Android Studio的工程目录中会生成bakApk文件夹,路径对应自己在gradle中的设置。
在bakApk文件夹下的内容,就是每次在生成补丁之前,在gradle中进行配置时所需要的XXX.apk(基准包)、XXX_mapping.txt(打开混淆以后才会产生)、XXX_R.txt文件。
同时,会在Android Studio的Gradle工具栏中会生成tinkerPatch Task文件,直接使用task:tinkerPatchVariantName(例如tinkerPatchDebug、tinkerPatchRelease)即可自动根据Variant选择相应的编译类型。
在编译过程中,还帮助我们完成了以下操作:
将TINKER_ID自动插入AndroidManifest的meta项,输出路径为build/intermediates/tinker_intermediates/AndroidManifest.xml;
如果minifyEnabled为true即打开混淆,将自动将Tinker的proguard规则添加到proguardFiles中,输出路径为build/intermediates/tinker_intermediates/tinker_proguard.pro,这里不需要将它们拷贝到自己的proguard配置文件中;
如果multiDexEnabled为true,将自动生成Tinker需要放在主dex的keep规则。在tinker 1.7.6版本之前,你需要手动将生成规则拷贝到自己的multiDexKeepProguard文件中。例如Sample中的multiDexKeepProguard file("keep_in_main_dex.txt")。在1.7.6版本之后,这里会通过脚本自动处理,无须手动填写。
把dexOptions的jumboMode打开。
在进行tinkerPatch Task编译之前,需要将bakApk文件夹下每个文件路径填写在对应的位置上。多flavor打包。
//demo中的配置将相关属性写在一起,方便每次更改
ext {
//demo中的这个属性表示是否打开tinkerBuild
tinkerEnabled = true
//基准包apk的位置
tinkerOldApkPath = "${bakPath}/"
//打开混淆生成的mapping.txt的路径
tinkerApplyMappingPath = "${bakPath}/"
//R.txt文件的路径
tinkerApplyResourcePath = "${bakPath}/"
//用了flavor以后在这里填写编译后的目录路径,没用flavor可以不填
tinkerBuildFlavorDirectory = "${bakPath}/"
}
填写完成以后直接使用tinkerPatch Task中相应的操作,即自动编译最新的安装包,并与输入基准包作差异,得到最终的补丁包。,编译完成后,会在tinkerPatch输出的目录build/outputs/tinkerPatch中产生我们需要的文件。
文件名 | 描述 |
---|---|
patch_unsigned.apk | 没有签名的补丁包 |
patch_signed.apk | 签名后的补丁包 |
patch_signed_7zip.apk | 签名后并使用7zip压缩的补丁包,也是我们通常使用的补丁包。但正式发布的时候,最好不要以.apk结尾,防止被运营商挟持。 |
log.txt | 在编译补丁包过程的控制台日志 |
dex_log.txt | 在编译补丁包过程关于dex的日志 |
so_log.txt | 在编译补丁包过程关于lib的日志 |
tinker_result | 最终在补丁包的内容,包括diff的dex、lib以及assets下面的meta文件 |
resources_out.zip | 最终在手机上合成的全量资源apk,你可以在这里查看是否有文件遗漏 |
tempPatchedDexes | 在Dalvik与Art平台,最终在手机上合成的完整Dex,我们可以在这里查看dex合成的产物。 |
官方提示:每次编译结束,我们都应该查看相关日志,清楚最终在补丁包中的文件。尤其是dex的补丁文件,即使是1k的dex补丁文件,也会带来合成时的时间损耗以及合成完整dex文件ROM空间体积这两部分影响!
加载补丁包
将基准包安装到手机,再将生成好的patch_signed_7zip.apk补丁包放到手机的sdcard中,正式使用时从服务器下载补丁包。然后在需要加载补丁包的位置调用TinkerInstaller.onReceiveUpgradePatch加载对应路径补丁包。
需要注意:在Tinker的更新使用中,默认在DefaultTinkerResultService会杀掉:patch进程,假设当前是补丁升级并且成功了,我们会杀掉当前进程,让补丁包更快的生效(这就是为什么有的人接好Tinker,实现了更新功能,但是每次加载完补丁会Crash的原因)。若是修复类型的补丁包并且失败了,我们会卸载补丁包。
扩展
Tinker中还有很多可选自定义类。可以通过继承默认的类,实现自己感兴趣的事件回调,比如在上面提到的,tinker在DefaultTinkerResultService中默认在加载补丁成功后杀死当前进程,这在正式的使用中会很降低用户体验,所以我们需要继承这个类,实现自己的回调。具体实现可以参考官方说明配合demo。