Android热更新Tinker集成配置【详细】

关于热更新目前的各种方案原理看我之前这篇文章的介绍热补丁动态修复技术调研
这篇文章将选择Trinker集成体验一下热更新。

1.什么是Tinker?


Tinker 是一个开源项目(Github链接),它是微信官方的 Android 热补丁解决方案,它支持动态下发代码、So 库以及资源,让应用能够在不需要重新安装的情况下实现更新。但需要注意的是需要重启Activity或重启Application才能达到更新效果。

2.对比其他以及选择Tinker的理由?


将Tinker与其他原理的热补丁方案做对比:阿里的 AndFix、美团的 Robust 以及 QZone 的超级补丁方案,如果不了解这三类原理,就看这篇文章的介绍热补丁动态修复技术调研

总的来说:

  • AndFix作为native解决方案,首先面临的是稳定性与兼容性问题,更重要的是它无法实现类替换,它是需要大量额外的开发成本的;
  • Robust兼容性与成功率较高,但是它与AndFix一样,无法新增变量与类只能用做的bugFix方案;
  • Qzone方案可以做到发布产品功能,但是它主要问题是插桩带来Dalvik的性能问题,以及为了解决Art下内存地址问题而导致补丁包急速增大的。

Tinker热补丁方案不仅支持类、So 以及资源的替换,它还是2.X-7.X的全平台支持。利用Tinker我们不仅可以用做 bugfix,甚至可以替代功能的发布。Tinker 已运行在微信的数亿 Android 设备上,那么为什么你不使用 Tinker 呢?

3.接入TinkerPatch 平台


  • Tinker 需要使用者有一个后台可以下发和管理补丁包,并且需要处理传输安全等部署工作,TinkerPatch 平台帮你做了这些工作,提供了补丁后台托管,版本管理,保证传输安全等功能,让你无需搭建一个后台,无需关心部署操作,只需引入一个 SDK 即可立即使用 Tinker。
  • 接入平台,无需理解复杂的热修复原理,一行代码即可接入热修复。实现了自动反射 Appliction 与 Library,使用者无需对自己的项目做任何的改动;
  • 当然如果我们要更好的理解tinker,需要研究其原理,源码,若不使用TinkerPatch平台,也可以根据需求自己搭建后台,根据开源的tinker自行接入。

4.【通过接入TinkerPatch 平台,开启Tinker之旅】


第一步:注册 TinkerPatch 平台账号点击前往注册
第二步 添加 APP

名字只是一个标识,没有其他要求

第三步 记录 AppKey

添加完APP,会生成一个AppKey,这个将在后续代码中使用

第四步 客户端SDK接入 【重点】
1.添加 gradle 插件依赖 :位于【工程】的build.gradle下,gradle 远程仓库依赖 jcenter。
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        // 添加TinkerPatch 插件
        //无需再单独引用tinker的其他库 
       classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.2.2"
    }
}

allprojects {
    repositories {
        jcenter()
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}
2.集成 TinkerPatch SDK, 位于【app】的build.gradle下
dependencies {
...
    compile 'com.android.support:multidex:1.0.1'
    // 若使用annotation需要单独引用,对于tinker的其他库都无需再引用
    provided("com.tinkerpatch.tinker:tinker-android-anno:1.9.2")
    compile "com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.2.2"
}
对于有的使用的as3.0,引入语法有变化,如下配置
dependencies {
...
    implementation 'com.android.support:multidex:1.0.1'
    // 若使用annotation需要单独引用,对于tinker的其他库都无需再引用
    compileOnly("com.tinkerpatch.tinker:tinker-android-anno:1.9.2")
    implementation "com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.2.2"
}

注意,若使用 annotation 自动生成 Application, 需要单独引入 Tinker 的 tinker-android-anno 库。除此之外,我们无需再单独引入 tinker 的其他库。
同时在【app】目录下需要将 TinkerPatch 相关的配置都放于 tinkerpatch.gradle 中,所以新建一个file,命名为tinkerpatch.gradle

所以我们需要在app下的build.gradle引入
dependencies {
...
}
//添加
apply from: 'tinkerpatch.gradle'
3.配置 tinkerpatchSupport 参数 ,在tinkerpatch.gradle下
apply plugin: 'tinkerpatch-support'
//这里需要说明bakPath定义了基准包的输出位置
def bakPath = file("${buildDir}/bakApk/")
//baseInfo和variantName 对于基准包我们可以忽略不填,
//因为tinker也不会对这个做处理
//它主要的作用是在打补丁包是,我们要根据基准包输出的路径下的信息做修改。主要是给补丁包配置
//这里我们先默认为"",等到基准包完成后再修改。
def baseInfo = "xxx"
def variantName = "xxx"

tinkerpatchSupport {
    tinkerEnable = true
    reflectApplication = true
    protectedApp = true
    supportComponent = false
    autoBackupApkPath = "${bakPath}"
    appKey = "d8de698f2ac404aa"
    appVersion = "1.0.0"
    def pathPrefix = "${bakPath}/${baseInfo}/${variantName}"
    def name = "${project.name}-${variantName}"
    baseApkFile = "${pathPrefix}/${name}.apk"
    baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
    baseResourceRFile = "${pathPrefix}/${name}-R.txt"
}
android {
    defaultConfig {
        buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}"
    }
}
tinkerPatch {
    ignoreWarning = false
    useSign = true
    dex {
        dexMode = "jar"
        pattern = ["classes*.dex"]
        loader = []
    }
    lib {
        pattern = ["lib/*/*.so"]
    }

    res {
        pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
        ignoreChange = []
        largeModSize = 100
    }

    packageConfig {
    }
    sevenZip {
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
    }
    buildConfig {
        keepDexApply = false
    }
}

重要参数描述

我们将原apk包称为基准apk包,tinkerPatch直接使用基准apk包与新编译出来的apk包做差异,得到最终的补丁包。

参数 默认值 描述 其他说明
tinkerEnable true 是否开启 tinkerpatchSupport 插件功能。 在开启时将不能instant run,所以若想调试程序,在debug的时候关闭false
reflectApplication false 是否反射 Application 实现一键接入 一般来说,接入 Tinker 我们需要改造我们的 Application, 若这里为 true, 即我们无需对应用做任何改造即可接入。
protectedApp false 是否开启支持加固 若我们的程序需要加固,则开启此选项
supportComponent false 是否开启支持在补丁包中动态增加Activity 注意:新增Activity的Exported属性必须为false�
autoBackupApkPath "" 指定每次编译产生的 apk/mapping.txt/R.txt 归档存储的位置
appKey "" 在 TinkerPatch 平台 申请的 appkey 就是我们在上面提到的appkey
appVersion "" 和 TinkerPatch 平台 的版本号需要对应,若这里我们输入1.0.0,之后在TinkerPatch平台上我们也需要输入1.0.0 注意,我们使用 appVersion 作为 TinkerId, 我们需要保证每个发布出去的基础安装包的 appVersion 都不一样。
baseApkFile "" 基准包的文件路径,对应 tinker 插件中的 oldApk 参数 编译补丁包时,必需指定基准版本的 apk(路径以及名字,如果不对应,将不到找到对应的基准包进行打补丁操作),默认值为空,则表示不是进行补丁包的编译。所以这个参数很重要。
baseProguardMappingFile "" 基准包的 Proguard mapping.txt 文件路径, 对应 tinker 插件 applyMapping 参数 在编译新的 apk 时候,我们希望通过保持基准 apk 的 proguard 混淆方式,从而减少补丁包的大小。这是强烈推荐的,编译补丁包时,我们推荐输入基准 apk 生成的 mapping.txt 文件。
baseResourceRFile "" 基准包的资源 R.txt 文件路径, 对应 tinker 插件 applyResourceMapping 参数 在编译新的apk时候,我们希望通基准 apk 的 R.txt 文件来保持 Resource Id 的分配,这样不仅可以减少补丁包的大小,同时也避免由于 Resource Id 改变导致 remote view 异常。
tinkerPatch 全局信息相关的配置项 一般来说,我们无需对其中的参数做任何的修改
outputFolder 设置编译输出路径,也就是补丁包生成的位置,默认在build/outputs/tinkerPatch中 一般我们不做指定,也不用配置,只需要知道补丁生成的位置在哪里
ignoreWarning false 如果出现如右侧的五种情况,并且ignoreWarning为false,程序将中断编译。因为这些情况可能会导致编译出来的patch包带来风险 1. minSdkVersion小于14,但是dexMode的值为"raw";2. 新编译的安装包出现新增的四大组件(Activity, BroadcastReceiver...);3. 定义在dex.loader用于加载补丁的类不在main dex中;4. 定义在dex.loader用于加载补丁的类出现修改;5. resources.arsc改变,但没有使用applyResourceMapping编译。
useSign ture 在运行过程中,我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名。 做好不要做修改
4.初始化 TinkerPatch SDK
最后在我们的代码中,只需简单的初始化 TinkerPatch 的 SDK 即可,我们无需考虑 Tinker 是如何下载/合成/应用补丁包, 也无需引入各种各样 Tinker 的相关类。
对于初始化操作我们分为两种情况:
  • 第一种是我们的 reflectApplication = true,即一键接入,此时我们无需为接入 Tinker 而改造我们的 Application 类。
public class MyApplication extends Application {
    ...
    @Override
    public void onCreate() {
        super.onCreate();
        // 我们可以从这里获得Tinker加载过程的信息
       ApplicationLike tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike();
        // 初始化TinkerPatch SDK, 更多配置可参照API章节中的,初始化SDK
        TinkerPatch.init(tinkerApplicationLike)
            .reflectPatchLibrary()
            .setPatchRollbackOnScreenOff(true)
            .setPatchRestartOnSrceenOff(true)
            .setFetchPatchIntervalByHours(3);
        // 每隔3个小时(通过setFetchPatchIntervalByHours设置)去访问后台时候有更新,通过handler实现轮训的效果
        TinkerPatch.with().fetchPatchUpdateAndPollWithInterval();
    }
    ...
  • 第二种是我们的 reflectApplication = false,就是根据自己的需求自己改造应用的 Application。参考 tinkerpatch-sample 中的 SampleApplicationLike 类这里不做更多说明。
5.使用步骤
到此为止,我们的配置已经完成,就剩下实践操作了:
1.获得基准包
  • 确保更新tinkerpatchSupport中的appVersion
  • 运行 assembleRelease task 构建基准包。也就是我们通常通过命令./gradlew assembleRelease 出的apk包。
  • tinkerPatch会基于你填入的autoBackupApkPath自动备份基础包信息到相应的文件夹,包含:apk文件、R.txt文件和mapping.txt文件 (注:mapping.txt是proguard的产物,如果你没有开启proguard则不会有这个文件�)
  • 根据我们的配置,此时构建完成基准包的位置即为如图


    基准包
  • tinker按照时间戳给我们命名
  • 你要发布的apk就是该路径下的apk,如果需要加固也是用该apk。
2.获得补丁包
  • 构建之前:将自动保存下来的文件分别填到tinkerpatchSupport中的baseApkFile、baseProguardMappingFile和baseResourceRFile 参数中,因为我们需要根据这些参数路径对应的apk和当前工程对比出不同,然后打出补丁。
  • 修改配置文件【重点】:


  • 所以你应该理解了,每次打补丁只需要改根据基准apk修改这两处就可以了,baseApkFile、baseProguardMappingFile和baseResourceRFile 也就自动改变了
    def pathPrefix = "${bakPath}/${baseInfo}/${variantName}"
    def name = "${project.name}-${variantName}"

    baseApkFile = "${pathPrefix}/${name}.apk"
    baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
    baseResourceRFile = "${pathPrefix}/${name}-R.txt"
  • 最后运行 tinkerPatchRelease task 构建补丁包,若无渠道,可通过./gradlew tinkerPatchRelease构建,我这个有一个小米渠道,可以通过./gradlew tinkerPatchXiaomiRelease构建。或者最简单的方式如图:


  • 完成后,补丁包将位于 build/outputs/tinkerPatch下。


3.发布补丁包
  • 将刚所得的补丁包patch_signed_7zip.apk重命名,去掉.apk后缀,我将其命名为patch_signed。
  • 然后回到tinker平台上,找到我们的app,然后新建版本,我们代码中的appVersion为1.0.0


  • 上传补丁包并下发:


4.重新安装我们的基准包,然后刷新tinker后台
5.杀掉进程,重新启动。补丁加载成功

成功接入了~给个小心心鼓励一下吧

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

推荐阅读更多精彩内容