【Android】热修复——Tinker(入门)
前言
不知你是否遇到这样的情况?千辛万苦上开发了一个版本,好不容易上线了,突然发现了一个严重bug需要进行紧急修复,怎么办?难道又要重新打包App、测试,发布新个版本?就为了修改一两行的代码?
莫慌,这种问题其实可以分分钟解决。如果你学会了这项黑科技——热修复。
在用户使用App的时候,不知不觉,这个Bug就被修复了。
莫慌
热修复:热修复(也称热补丁、热修复补丁,英语:hotfix)是一种包含信息的独立的累积更新包,通常表现为一个或多个文件。这被用来解决软件产品的问题(例如一个程序错误)。——维基百科
本文介绍了Tinker的接入方式,更加详细的内容可以查阅官方文档
介绍
Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。
Tinker所支持的功能如下
来自官方Github
Tinker热补丁方案·不仅支持类、So以及资源的替换,它还是2.X-7.X的全平台支持。
接入
进入正题吧
在项目的
build.gradle
中
buildscript {
repositories {
jcenter()
}
dependencies {
// TinkerPatch 插件
classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.1.4"
}
}
然后在app
的gradle
文件app/build.gradle
中
dependencies {
// 若使用annotation需要单独引用,对于tinker的其他库都无需再引用
provided("com.tencent.tinker:tinker-android-anno:1.7.7")
compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.1.4")
}
在app目录下,创建tinkerpatch.gradle
(可以去后面的链接下载源码,把这个文件拷进去)
将 TinkerPatch 相关的配置都放于
tinkerpatch.gradle
中,然后在app的gradle
文件app/build.gradle
中还添加
apply from: 'tinkerpatch.gradle'
完整的app/buidl.gradle
:
apply plugin: 'com.android.application'
apply from: 'tinkerpatch.gradle'
android {
...
}
dependencies {
.....
// 若使用annotation需要单独引用,对于tinker的其他库都无需再引用
provided("com.tencent.tinker:tinker-android-anno:1.7.7")
compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.1.4")
}
配置参数
打开之前创建的tinkerpatch.gradle
添加
apply plugin: 'tinkerpatch-support'
/**
* TODO: 请按自己的需求修改为适应自己工程的参数
*/
def bakPath = file("${buildDir}/bakApk/")
def baseInfo = "app-1.0.0-0330-20-37-31"
def variantName = "release"
/**
* 对于插件各参数的详细解析请参考
* http://tinkerpatch.com/Docs/SDK
*/
tinkerpatchSupport {
/** 可以在debug的时候关闭 tinkerPatch **/
tinkerEnable = true
reflectApplication = true
autoBackupApkPath = "${bakPath}"
appKey = "你的appkey"// 注意!!! 需要修改成你的appkey
/** 注意: 若发布新的全量包, 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
}
}
- 参数说明
- bakPath:基包路径
- baseInfo:基包文件夹名(打补丁包的时候,需要修改)
- appKey:进入官网注册一个账号,新增APP,得到对应的appKey。
官方参数说明
其中
appKeyappKey
在新增的APP中可以看到
使用
创建FetchPatchHandler
用于检测是否更新(刚打开时会检测一次)
public class FetchPatchHandler extends Handler {
public static final long HOUR_INTERVAL = 3600 * 1000;
private long checkInterval;
/**
* 通过handler, 达到按照时间间隔轮训的效果
* @param hour
*/
public void fetchPatchWithInterval(int hour) {
//设置TinkerPatch的时间间隔
TinkerPatch.with().setFetchPatchIntervalByHours(hour);
checkInterval = hour * HOUR_INTERVAL;
//立刻尝试去访问,检查是否有更新
sendEmptyMessage(0);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//这里使用false即可
TinkerPatch.with().fetchPatchUpdate(false);
//每隔一段时间都去访问后台, 增加10分钟的buffer时间
sendEmptyMessageDelayed(0, checkInterval + 10 * 60 * 1000);
}
}
创建MyApplication
初始化(reflectApplication = true 时)
public class MyApplication extends Application{
private ApplicationLike tinkerApplicationLike;
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.TINKER_ENABLE) {
// 我们可以从这里获得Tinker加载过程的信息
tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike();
// 初始化TinkerPatch SDK, 更多配置可参照API章节中的,初始化SDK
TinkerPatch.init(tinkerApplicationLike)
.reflectPatchLibrary()
.setPatchRollbackOnScreenOff(true)
.setPatchRestartOnSrceenOff(true);
// 每隔3个小时去访问后台时候有更新,通过handler实现轮训的效果
new FetchPatchHandler().fetchPatchWithInterval(3);
}
}
}
最后在记得在Manifest.xml
中配置Application
,还要给SD卡读写权限
<manifest >
.....
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
...
android:name=".MyApplication">
</manifest>
注意:初始化的代码建议紧跟 super.onCreate(),并且所有进程都需要初始化,已达到所有进程都可以被 patch 的目的
如果你确定只想在主进程中初始化 tinkerPatch,那也请至少在 :patch 进程中初始化,否则会有严重的 crash 问题
打生产包
注意:打包前记得配置签名。可参考源码
每次开发完成后,开始打包。
打开Studio
右侧的Gradle
,选择assemableRelease
打正式包
完成后可以在文件夹build
中找到生成的文件(这里称为基包)
打开build
-> bakApk
-> app-1.0.0-0330-21-40-52
(根据时间命名)
release
文件夹中会出现我们刚打完的包。一个apk
,对应一个txt文件。
将apk安装到手机上,这是一个刚创建的项目,里面只有Hello World
将
app-1.0.0-0330-21-40-52
备份,打补丁包的时候需要用到。
多渠道打包时会根据渠道名分包,目录结构相似。
生成补丁包
这里模拟一个修补bug的场景,发一个热补丁,弹一个Toast。
注意:在打生产包的代码上做修改
Toast.makeText(this, "这是个补丁", Toast.LENGTH_SHORT).show();
-
将之前的备份好的基包复制到
修改baseInfobuild/bakApk
下,打开tinkerpatch.gradle
修改baseInfo
成对应的文件名
-
修改
appVersiontinkerpatch.gradle
中的tinkerpatchSupport
->appVersion
-
完成后打开
GradleGradle
,如下选择tinkerPatchRelease
补丁包将位于 build/outputs/tinkerPatch 中,这里只需要用到patch_signed_7zip.apk
补丁包
发布
最后,只需要将刚生成的补丁包发布,然后静静等待即可。
- 进入官方网站,新增
APP
添加APP
- 选择对应的app,添加APP版本
添加APP版本
添加版本
版本号对应tinkerpatch.gradle
中的appVersion - 选择
patch_signed_7zip.apk
文件,提交即可(更多下发选项,参考官方文档)
发布补丁 -
提交后,查看补丁的下载数量以及成功应用数
补丁详情
这时候重新打开app,等待补丁下载。下载完成后关闭app,再次打开,查看结果。就这样,整个热修复的流程就完成了。
注意:一定要关闭后打开,热修复才会生效。
关于兼容多渠道包
关于渠道包的问题,若使用flavor
编译渠道包,会导致不同的渠道包由于BuildConfig
变化导致classes.dex
差异。这里建议的方式有:
- 将渠道信息写在
AndroidManifest.xml
或文件中,例如channel.ini
; - 将渠道信息写在
ap
k文件的zip comment
中,这种是建议方式,例如可以使用项目packer-ng-plugin或者可使用V2 Scheme的walle; - 若不同渠道存在功能上的差异,建议将差异部分放于单独的dex或采用相同代码不同配置方式实现;
已通过Walle实现:【Android】Walle多渠道打包&Tinker热修复
Tinker已知的问题:
- Tinker不支持修改
AndroidManifest.xml
,Tinker不支持新增四大组件; - 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
- 在
Android N
上,补丁对应用启动时间有轻微的影响; - 不支持部分三星android-21机型,加载补丁时会主动抛出
TinkerRuntimeException:checkDexInstall failed
; - 由于各个厂商的加固实现并不一致,在1.7.6以及之后的版本,tinker不再支持加固的动态更新;
(在官方——对加固的支持看到相关的内容,说是支持多种加固,不过我没试过 2017.7.3) - 对于资源替换,不支持修改
remoteView
。例如transition
动画,notification icon
以及桌面图标。
Tips:
-
dubug
模式下,tinkerpatch.gradle
—>tinkerpatchSupport
—>tinkerEnable
需要改为false
- 添加
SD
卡权限, - 下载补丁后,杀掉进程重新打开,补丁才会生效
- 补丁非即时生效,需要等一会儿
源码地址
参考
以上有错误之处,感谢指出