Tinker是微信前段时间开源的Android热补丁方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。今天我们就来介绍下Tinker,希望大家能用到自己的项目中,我自己的项目已经使用了,而且在线上已经稳定运行了大半年了,是个人目前用下来最好的android hot fix方案,在这里感谢微信团队🙏🙏🙏,Tinker的github地址为https://github.com/Tencent/tinker。
1 常见android热更新方案
目前常用的方案有Native Hook方案,通过在native代码进行方法hook,这种方式最大的问题在于部分手机需要root后才能hook进程,而且功能太弱,是比较早期的热更新方案,现在应该基本没什么人用了;Classloader方案是用的比较多的一种,其实思路和Multidex是一个思路,就是在加载dex的时候优先加载patch.dex用新类覆盖有bug的类;今天我们要介绍的是通过instance Run替换dex的方案,这种方案最大的好处就是兼容性比较好,而且可以实现类的替换也可以实现资源文件的替换。
原理 | 方案 |
---|---|
native Hook方案 | AndFix |
ClassLoader替换类方案 | Nuwa、HotFix |
Instance Run冷插拔dex替换 | Tinker |
测试模块 | nativehook | Classloader方案 | InstanceRun方案 |
---|---|---|---|
类替换 | no | yes | yes |
资源替换 | no | no | yes |
是否需要重启 | no | yes | yes |
兼容稳定性 | 不稳定 | 最好 | 稳定 |
这里再贴下tinker官网对各个主流hot fix方案的对比,对比下来还是很不错的:
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体积 | 较大 | 较小 | 较小 | 较小 |
成功率 | 较高 | 较高 | 一般 | 最高 |
2 Tinker方案设计思想
Tinker的方案对比其他方案为什么会有那么多好处?主要是因为Tinker采用的是全量Dex替换策略,这样不但可以避免ART地址错乱问题,也可以解决在Dalvik上需要插桩的麻烦,tinkerPatch直接使用基准apk包与新编译出来的apk包做差异,得到最终的补丁包。在运行的时候,将差异的patch.dex重新跟原始安装包里的Dex组合为新Dex。在启动的时候ClassLoader会加载最新的Dex,这样就做到了热修复,除了可以用来修复bug,甚至一些new feature也可以使用tinker来发布。为了保证patch.dex比较小,微信团队自研了DexDiff算法可以做到非常小的差量包。为了保证dex合并的性能,Tinker会在后台启动一个patch Process来进行dex合并,让用户感知不到,在下次启动的时候,新的dex就会生效。
这里盗用一张Tinker的原理图,让大家秒懂大概的原理。

3 实施
3.1 引入Tinker
在项目的build.gradle中添加tinker-patch-gradle-plugin依赖
buildscript {
dependencies {
classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1')
}
}
dependencies {
//可选,用于生成application类
provided('com.tencent.tinker:tinker-android-anno:1.9.1')
//tinker的核心库
compile('com.tencent.tinker:tinker-android-lib:1.9.1')
}
//apply tinker插件
apply plugin: 'com.tencent.tinker.patch'
3.2 初始化
首先需要定义一个继承自DefaultApplicationLike的类,这个类是com.tencent.tinker.loader.app包下的一个类,可以把这个类当作Application去用,目前我的项目里就是把这个类当作Application去用了。在类的头部需要加上@DefaultLifeCycle注解,用来在编译的时候动态生成SimpleTinkerInApplication这个类,然后需要在app的androidMainfest.xml里设置Application name为注解里设置的动态生成的这个Application类。
@DefaultLifeCycle(application = ".SimpleTinkerInApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class SimpleTinkerInApplicationLike extends ApplicationLike {
public SimpleTinkerInApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
}
@Override
public void onCreate() {
super.onCreate();
TinkerInstaller.install(this);
}
}
3.3 调用API
在patch下发后,我们可以在一些隐蔽的地方调用Tinker加载patch的方法,我的项目里是放在用户进入app后进行版本检测的时候如果发现服务端有新patch需要更新,则异步下载,在完成下载后调用该方法loadpatch,load完成后Tinker是需要下次启动才会生效的,用户下次再进入app的时候看到的就是打过patch的版本了。
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),
Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed.apk");
3.4 生成patch包
tinker提供了patch生成的工具,源码见:tinker-patch-cli,打成一个jar就可以使用,并且提供了命令行相关的参数以及文件。
执行下面的jar包就可以在output目录生成patch差量包。
java -jar tinker-patch-cli.jar -old old.apk -new new.apk -config tinker_config.xml -out output
在build/output目录下会生成下面这些文件,其中patch_signed_7zip.apk是我们最后发布补丁要用的apk差量文件:
文件名 | 描述 |
---|---|
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合成的产物。 |
3.5 patch分发管理
patch分发涉及到主版本和补丁版本的依赖关系,所以需要在服务端进行管理哪些手机需要推送补丁包,出问题后如何回滚等等问题,补丁包管理可以和自己产品的版本管理结合起来自研,也可以使用Tinker团队开发的patch发布管理平台。
Tinker团队还同步开发了一个patch发布管理的三方平台,对于规模较小的团队或者个人开发者完全可以使用TinkerPatch这个免费的平台进行补丁包的发放和管理,日请求量<1w,赠送的每月cdn流量包是10g,所以一般规模的小app足够使用了。下面是TinkerPatch的地址 http://www.tinkerpatch.com
4 实施注意点
- tinker需要在
AndroidManifest.xml
中指定TINKER_ID;
<application>
<meta-data
android:name="TINKER_ID"
android:value="tinker_id_1234567" />
//...
</application>
- 添加SDCard权限,因为patch会下载到sdcard所以需要sdcard权限,如果你是6.0以上的系统,需要添加上授权代码,或者手动在设置页面打开SDCard读写权限;
- 对于项目中自定义了Application的APP需要注意下,建议将原先Application里app初始化的内容都搬迁到新建的ApplicationLike类中,替换掉原先的Application;
- 建议直接将tinker github上sample目录中的tinker相关的java代码和gradle和自己的工程内容合并,这样可以很快集成Tinker,避免出现一些问题出现,起码我的项目一开始还不熟的时候是这样实施的。。。后期熟悉Tinker后可以随意灵活使用;
- 使用Proguard做混淆的同学还需要注意设置下;
- Tinker的常见问题可以参考官网的QA,在使用的过程中遇到的问题基本都涵盖了 官网QA
5 总结
Tinker在我的项目实施的过程中暂时没有遇到不能解决的问题,目前Tinker在项目中主要还是用来修复bug使用,还没有作为new feature发布使用,不过这样的热更效率和稳定性,作为new feature使用也是可以的,自从使用了Tinker后目前还没有遇到以前使用Andfix遇到的一些crash的问题,上了Tinker以后最大的好处就是每次发版后再也不用担心出问题后需要赶急忙慌的修bug去市场上架,再期待着用户点击更新了。。
网友评论