概述
经过上一轮《Android热修复方案(一)理论篇》关于热修复的理论介绍,这篇重点实践一下该理论。这是基于Tinker的热修复。
具体实现
新建项目,添加依赖
创建一个全新的项目TinkerTest。先在Project的gradle中引用库。在app的gradle添加依赖:
project:1
2
3
4
5dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
//无需再单独引用tinker的其他库
classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:${TINKERPATCH_VERSION}"
}
app:1
2
3
4
5
6
7
8
9
10
11
12
13dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.android.support:appcompat-v7:26.0.1"
implementation "com.android.support:multidex:1.0.2"
//若使用annotation需要单独引用,对于tinker的其他库都无需再引用
annotationProcessor("com.tinkerpatch.tinker:tinker-android-anno:${TINKER_VERSION}") {
changing = true
}
compileOnly("com.tinkerpatch.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
implementation("com.tinkerpatch.sdk:tinkerpatch-android-sdk:${TINKERPATCH_VERSION}") {
changing = true
}
}
properties:1
2TINKER_VERSION=1.9.2
TINKERPATCH_VERSION=1.2.2
配置信息
可参照官方给demo来进行配置。把必要的信息修改为本项目的,具体见注释1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93/**
* TODO: 请按自己的需求修改为适应自己工程的参数
*/
def bakPath = file("${buildDir}/bakApk/")
def baseInfo = "app-1.0.0-0510-16-22-11"//对应着有Bug的apk来生成补丁
def variantName = "debug"
/**
* 对于插件各参数的详细解析请参考
* http://tinkerpatch.com/Docs/SDK
*/
tinkerpatchSupport {
/** 可以在debug的时候关闭 tinkerPatch **/
/** 当disable tinker的时候需要添加multiDexKeepProguard和proguardFiles,
这些配置文件本身由tinkerPatch的插件自动添加,当你disable后需要手动添加
你可以copy本示例中的proguardRules.pro和tinkerMultidexKeep.pro,
需要你手动修改'tinker.sample.android.app'本示例的包名为你自己的包名, com.xxx前缀的包名不用修改
**/
tinkerEnable = true
reflectApplication = false
/**
* 是否开启加固模式,只能在APK将要进行加固时使用,否则会patch失败。
* 如果只在某个渠道使用了加固,可使用多flavors配置
**/
protectedApp = false
/**
* 实验功能
* 补丁是否支持新增 Activity (新增Activity的exported属性必须为false)
**/
supportComponent = true
autoBackupApkPath = "${bakPath}"
appKey = "你的key"//可不配置
/** 注意: 若发布新的全量包, appVersion一定要更新 **/
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"
/**
* 若有编译多flavors需求, 可以参照: https://github.com/TinkerPatch/tinkerpatch-flavors-sample
* 注意: 除非你不同的flavor代码是不一样的,不然建议采用zip comment或者文件方式生成渠道信息(相关工具:walle 或者 packer-ng)
**/
}
/**
* 用于用户在代码中判断tinkerPatch是否被使能
*/
android {
defaultConfig {
buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}"
}
}
/**
* 一般来说,我们无需对下面的参数做任何的修改
* 对于各参数的详细介绍请参考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
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"
// path = "/usr/local/bin/7za"
}
buildConfig {
keepDexApply = false
}
}
编写有Bug的代码
虚拟一个线上的Bug
activity_main.xml:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
android:gravity="center"
tools:context=".MainActivity">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是bug"
/>
<Button
android:id="@+id/fixBugbBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="修复bug"
/>
</LinearLayout>
1 | private void TestTinker() { |
业务非常简单,textView上出现了Bug,待修复。
安装有Bug的apk
把有问题的apk安装进设备,生成的apk的时候同时也会在bakapk下生成对应的Tag版本。后续打补丁的时候也要对应该版本
修复Bug
更改TextView上的文本,隐藏加载补丁的按钮1
2textView.setText("我已修复");//去掉注释代表修复了该Bug
fixBugBtn.setVisibility(View.INVISIBLE);//去掉注释
生成补丁
生成补丁之前一定要设置自己的工程参数:
修改完代码之后,如果是debug就点击”tinkerPacthDeBug“,如果是Release就点击”tinkerPacthRelease“
最终的补丁会放在output下,以patch_signed_7zip.apk命名。
加载补丁
需要将补丁放到目标目录下,例如放入根目录,可以用USB先拷贝,也可以用adb指令的方式。
最终实现效果
可以看出加载补丁后按钮消失,文本由”我是bug“变成”我已修复“
总结
总的来说使用Tinker并不难,官网还提供了傻瓜式的一键接入的方式,用起来十分方便。但是还是要理解他的原理,这很重要。最后再赘述一次:
Tinker是用了类加载的形式进行热修复,主要将旧apk(有bug)和新apk(无bug)进行比对生成新的补丁。新补丁包含了目标dex文件,tinker将其放到Element集合的第一位优先加载。根据双亲委派模型就不会加载后面有bug的类了。最终达到了类的替换目的。