Tinker-让android热更新更靠谱

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
测试模块nativehookClassloader方案InstanceRun方案
类替换noyesyes
资源替换nonoyes
是否需要重启noyesyes
兼容稳定性不稳定最好稳定

这里再贴下tinker官网对各个主流hot fix方案的对比,对比下来还是很不错的:

TinkerQZoneAndFixRobust
类替换yesyesnono
So替换yesnonono
资源替换yesyesnono
全平台支持yesyesyesyes
即时生效nonoyesyes
性能损耗较小较大较小较小
补丁包大小较小较大一般一般
开发透明yesyesnono
复杂度较低较低复杂复杂
gradle支持yesnonono
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的原理图,让大家秒懂大概的原理。

《Tinker-让android热更新更靠谱》 https://raw.githubusercontent.com/WeMobileDev/article/master/assets/tinker/wechat.png

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 实施注意点

  1. tinker需要在AndroidManifest.xml中指定TINKER_ID;
<application>
  <meta-data
            android:name="TINKER_ID"
            android:value="tinker_id_1234567" />
    //...
</application>
  1. 添加SDCard权限,因为patch会下载到sdcard所以需要sdcard权限,如果你是6.0以上的系统,需要添加上授权代码,或者手动在设置页面打开SDCard读写权限;
  2. 对于项目中自定义了Application的APP需要注意下,建议将原先Application里app初始化的内容都搬迁到新建的ApplicationLike类中,替换掉原先的Application;
  3. 建议直接将tinker github上sample目录中的tinker相关的java代码和gradle和自己的工程内容合并,这样可以很快集成Tinker,避免出现一些问题出现,起码我的项目一开始还不熟的时候是这样实施的。。。后期熟悉Tinker后可以随意灵活使用;
  4. 使用Proguard做混淆的同学还需要注意设置下;
  5. Tinker的常见问题可以参考官网的QA,在使用的过程中遇到的问题基本都涵盖了 官网QA

5 总结

Tinker在我的项目实施的过程中暂时没有遇到不能解决的问题,目前Tinker在项目中主要还是用来修复bug使用,还没有作为new feature发布使用,不过这样的热更效率和稳定性,作为new feature使用也是可以的,自从使用了Tinker后目前还没有遇到以前使用Andfix遇到的一些crash的问题,上了Tinker以后最大的好处就是每次发版后再也不用担心出问题后需要赶急忙慌的修bug去市场上架,再期待着用户点击更新了。。

    原文作者:monkey01
    原文地址: https://www.jianshu.com/p/dd0da6f17692
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞