Android热修复——Tinker的集成
前言
做前端开发的都知道,当我们项目做完了以后,都会把应用上传到应用市场上供用户下载使用,比如上传到应用宝啊,应用汇啊,360啊,小米,华为,魅族啊,等等
但是,有时候我们会经常遇到一些很扯淡的事情,刚刚熬夜加班将项目发到应用市场上,第二天,又发现一个严重bug,难道是开会研究看看是否能在下一版解决?还是将bug解决了,
在给测试进行测试,然后再发版?如果从新发版,这个成本太高了,那怎么办呢,今天我们就来谈一谈热修复——Tinker!
现在市场上的热修复工具或者框架有很多很多,比较出名的有阿里的 AndFix、美团的 Robust 以及 QZone 的超级补丁方案。这个方案各有利弊,但是今天我们不说这几个方案,我们来聊一聊另一个补丁方案——Tinker。首先我们上一张图:

Tinker热补丁方案·不仅支持类、So以及资源的替换,它还是2.X-7.X的全平台支持。
在官网上,介绍这个Tinker,用了一句话:Tinker 已运行在微信的数亿 Android 设备上,那么为什么你不使用 Tinker 呢? ps:别人几亿的用户量在用Tinker,都不担心出现什么问题,
你们几个用户量的应用,还整天瞎担心。
这篇文章,主要是和大家聊一下怎么集成这个Tinker,并不会设计很多原理的东西,如果需要了解更深入的原理性的东西,可以到官网看看。点击官网http://tinkerpatch.com/Docs/intro
集成SDK
第一步 添加 gradle 插件依赖
在项目的Gradle添加远程依赖
1
buildscript {
2
repositories {
3
jcenter()
4
}
5
dependencies {
6 classpath 'com.android.tools.build:gradle:2.3.2'
7
//
TinkerPatch 插件
8 classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.1.8"
9
//
NOTE: Do not place your application dependencies here; they belong
10
//
in the individual module build.gradle files
11
}
12 }
第二步 集成 TinkerPatch SDK
在app中的gradle添加denpendencies 依赖,注意:这两个gradle不是同一个gradle,上面的那个build gradle 是整个项目的,下面这个build gradle是在app里面的,注意区分
1
dependencies {
2
//
若使用annotation需要单独引用,对于tinker的其他库都无需再引用
3 provided("com.tinkerpatch.tinker:tinker-android-anno:1.8.0"
)
4 compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.1.8"
)
5 }
注意,若使用 annotation 自动生成 Application, 需要单独引入 Tinker 的 tinker-android-anno 库。除此之外,我们无需再单独引入 tinker 的其他库。
为了简单方便,我们将 TinkerPatch 相关的配置都放于 tinkerpatch.gradle 中, 我们需要将其引入:
1 apply from: 'tinkerpatch.gradle'
第三步 配置 tinkerpatchSupport 参数
好了,接下来我们就在app目录下创建这个tinkerpatch.gradle,如图所示的目录

打开tinkerpatch.gradle,将 TinkerPatch 相关的配置都放于tinkerpatch.gradle中。
1 apply plugin: 'tinkerpatch-support'
2
3
/**
4
* TODO: 请按自己的需求修改为适应自己工程的参数
5
*/
6
7
//
基包路径
8 def bakPath = file("${buildDir}/bakApk/"
)
9
//
基包文件夹名(打补丁包的时候,需要修改)
10 def baseInfo = "app-1.0.1-1018-17-29-31"
11
//
版本名称
12 def variantName = "debug"
13
14
/**
15
* 对于插件各参数的详细解析请参考
16
*
17
*/
18
tinkerpatchSupport {
19
//
可以在debug的时候关闭 tinkerPatch
20 tinkerEnable =
true
21
//
是否使用一键接入功能 默认为false 是否反射 Application 实现一键接入;
22
//
一般来说,接入 Tinker 我们需要改造我们的 Application, 若这里为 true, 即我们无需对应用做任何改造即可接入。
23 reflectApplication =
true
24
//
将每次编译产生的 apk/mapping.txt/R.txt 归档存储的位置
25 autoBackupApkPath = "${bakPath}"
26 appKey = "这里的appKey修改成你自己的appkey"
//
注意!!! 需要修改成你的appkey
27
28
/**
注意: 若发布新的全量包, appVersion一定要更新 *
*/
29 appVersion = "1.0.1"
30
31 def pathPrefix = "${bakPath}/${baseInfo}/${variantName}/"
32 def name = "${project.name}-${variantName}"
33
/**
34
* 基准包的文件路径, 对应 tinker 插件中的 oldApk 参数;编译补丁包时,
35
* 必需指定基准版本的 apk,默认值为空,则表示不是进行补丁包的编译
36
*/
37 baseApkFile = "${pathPrefix}/${name}.apk"
38
39
/**
40
* 基准包的 Proguard mapping.txt 文件路径, 对应 tinker 插件 applyMapping 参数;在编译新的 apk 时候,
41
* 我们希望通过保持基准 apk 的 proguard 混淆方式,
42
* 从而减少补丁包的大小。这是强烈推荐的,编译补丁包时,我们推荐输入基准 apk 生成的 mapping.txt 文件。
43
*/
44 baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
45
/**
46
* 基准包的资源 R.txt 文件路径, 对应 tinker 插件 applyResourceMapping 参数;在编译新的apk时候,
47
* 我们希望通基准 apk 的 R.txt 文件来保持 Resource Id 的分配,这样不仅可以减少补丁包的大小,
48
* 同时也避免由于 Resource Id 改变导致 remote view 异常
49
*/
50 baseResourceRFile = "${pathPrefix}/${name}-R.txt"
51
/**
52
* 若有编译多flavors需求, 可以参照:
https://github.com/TinkerPatch/tinkerpatch-flavors-sample
53
* 注意: 除非你不同的flavor代码是不一样的,不然建议采用zip comment或者文件方式生成渠道信息(相关工具:walle 或者 packer-ng)
54
*
*/
55
}
56
57
/**
58
* 用于用户在代码中判断tinkerPatch是否被使能
59
*/
60
android {
61
defaultConfig {
62 buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}"
63
}
64
}
65
/**
66
* 一般来说,我们无需对下面的参数做任何的修改
67
* 对于各参数的详细介绍请参考:
68
*
https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
69
*/
70
tinkerPatch {
71 ignoreWarning =
false
72 useSign =
true
//
是否需要签名,打正式包如果这里是true,则要配置签名,否则会编译不过去
73
dex {
74 dexMode = "jar"
75 pattern = ["classes*.dex"
]
76 loader =
[]
77
}
78
lib {
79 pattern = ["lib/*/*.so"
]
80
}
81
82
res {
83 pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"
]
84 ignoreChange =
[]
85 largeModSize = 100
86
}
87
packageConfig {
88
}
89
sevenZip {
90 zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
91
//
path = "/usr/local/bin/7za"
92
}
93
buildConfig {
94 keepDexApply =
false
95
}
96 }
简单的说一下这里的参数配置:如图

这里还需要注意一个地方,就是appKey,在我们登录tinker的官网,并且添加一个app版本以后,都会生成一个appkey,我们要把自己的appkey填到上面的配置中,附上一张图

第四步 初始化 TinkerPatch SDK
官方给了我们两种方式来初始化TinkerPatch SDK,第一种是reflectApplication = true 的情况,另一种reflectApplication = false的情况,今天我们说reflectApplication = true这种情况,另一种情况,大家可以到官网中看看
简单来说一下这两种情况的区别啊,当reflectApplication = true这种情况是不需要更改我们项目的Application类,而reflectApplication = false的情况是需要改动Application这个类。
初始化TinkerPatch SDK
1
package
com.example.administrator.tinkerdemotest;
2
3
import
android.app.Application;
4
5
import
com.tencent.tinker.loader.app.ApplicationLike;
6
import
com.tinkerpatch.sdk.TinkerPatch;
7
import
com.tinkerpatch.sdk.loader.TinkerPatchApplicationLike;
8
9
/**
10
* Created by Administrator on 2017/10/18 0018.
11
*
12
* reflectApplication = true 时
13
*/
14
15
public
class tinkerApplication
extends
Application {
16
17
private
ApplicationLike tinkerApplicationLike;
18
@Override
19
public
void
onCreate() {
20
super
.onCreate();
21
22
if
(BuildConfig.TINKER_ENABLE) {
23
//
我们可以从这里获得Tinker加载过程的信息
24 tinkerApplicationLike =
TinkerPatchApplicationLike.getTinkerPatchApplicationLike();
25
26
//
初始化TinkerPatch SDK, 更多配置可参照API章节中的,初始化SDK
27
TinkerPatch.init(tinkerApplicationLike)
28
.reflectPatchLibrary()
29 .setPatchRollbackOnScreenOff(
true
)
30 .setPatchRestartOnSrceenOff(
true
);
31
32
//
每隔3个小时去访问后台时候有更新,通过handler实现轮训的效果
33
new FetchPatchHandler().fetchPatchWithInterval(3
);
34
}
35
}
36 }
最后在加上FetchPatchHandler这个类
1
package
com.example.administrator.tinkerdemotest;
2
3
4
import
android.os.Handler;
5
import
android.os.Message;
6
7
import
com.tinkerpatch.sdk.TinkerPatch;
8
9
/**
10
* Created by Administrator on 2017/10/18 0018.
11
*/
12
13
public
class FetchPatchHandler
extends
Handler {
14
15
public
static
final
long HOUR_INTERVAL = 3600 * 1000
;
16
private
long
checkInterval;
17
18
19
/**
20
* 通过handler, 达到按照时间间隔轮训的效果
21
*/
22
public
void fetchPatchWithInterval(
int
hour) {
23
//
设置TinkerPatch的时间间隔
24
TinkerPatch.with().setFetchPatchIntervalByHours(hour);
25 checkInterval = hour *
HOUR_INTERVAL;
26
//
立刻尝试去访问,检查是否有更新
27 sendEmptyMessage(0
);
28
}
29
30
31
@Override
32
public
void
handleMessage(Message msg) {
33
super
.handleMessage(msg);
34
//
这里使用false即可
35 TinkerPatch.with().fetchPatchUpdate(
false
);
36
//
每隔一段时间都去访问后台, 增加10分钟的buffer时间
37 sendEmptyMessageDelayed(0, checkInterval + 10 * 60 * 1000
);
38
}
39 }
最后将AndroidManifest.xml中的添加上相应的网络和SD的权限,还要在application中加上 android:name=".tinkerApplication",附上代码:
1 <?xml version="1.0" encoding="utf-8"?>
2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
package="com.example.administrator.tinkerdemotest">
4
5 <uses-permission android:name="android.permission.INTERNET"/>
6 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
7 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
8
9
10 <
application
11 android:allowBackup="true"
12 android:icon="@mipmap/ic_launcher"
13 android:label="@string/app_name"
14 android:roundIcon="@mipmap/ic_launcher_round"
15 android:name=".tinkerApplication"
16 android:supportsRtl="true"
17 android:theme="@style/AppTheme">
18 <activity android:name=".MainActivity">
19 <intent-filter>
20 <action android:name="android.intent.action.MAIN" />
21
22 <category android:name="android.intent.category.LAUNCHER" />
23 </intent-filter>
24 </activity>
25 </application>
26
27 </manifest>
好了,到这里,集成Tinker的代码基本上已经完成了,现在运行看看如图

双击assembleDebug就可以运行项目了,结果如图:

然后在app-->build下有个app-debug.apk的包,我们将这个包安装到手机上,就相当于是我们本地的应用,在里面我加了一句话,如图

接着我们打有补丁的包,打补丁的包之前,我们的改两个地方,在tinkerpatch.gradle中找到如图中的代码,我们需要将baseInfo,和variantName改成和自己的一直如图:
改完以后,我们就可以打补丁的包了,如图

将baseInfo的值改为 右图中1所对应的值,variantName的值改为右图2对应的值。改完以后,我们就可以打补丁的包了,如图

然后在build --> outputs --> tinkerPatch 中就可以找到我们的补丁的包了,如图

最后将我们的补丁包上传到tinker官网,就可以使用了,如图

最后附上一条动态的效果图。注意:一定要把app进程杀死,才会有效果。

源码链接:https://github.com/343661629/Tinker