在app上集成tinker,根据 tinker上的wiki 的指示操作即可。
具体步骤如下:
gradle接入
gradle是推荐的接入方式,在gradle插件tinker-patch-gradle-plugin
中我们帮你完成proguard、multiDex以及Manifest处理等工作。
添加gradle依赖
在项目的build.gradle中,添加tinker-patch-gradle-plugin
的依赖
buildscript {
dependencies {
classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1')
}
}
然后在app的gradle文件app/build.gradle,我们需要添加tinker的库依赖以及apply tinker的gradle插件.
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'
tinkerPatch task详解
直接使用task:tinkerPatchVariantName(例如tinkerPatchDebug、tinkerPatchRelease)即可自动根据Variant选择相应的编译类型,同时它还贴心的为我们完成以下几个操作:
将TINKER_ID自动插入AndroidManifest的meta项,输出路径为build/intermediates/tinker_intermediates/AndroidManifest.xml;
如果minifyEnabled为true,将自动将Tinker的proguard规则添加到proguardFiles中,输出路径为build/intermediates/tinker_intermediates/tinker_proguard.pro,这里你不需要将它们拷贝到自己的proguard配置文件中;
如果multiDexEnabled为true,将自动生成Tinker需要放在主dex的keep规则。在tinker 1.7.6版本之前,你需要手动将生成规则拷贝到自己的multiDexKeepProguard文件中。例如Sample中的multiDexKeepProguard file("keep_in_main_dex.txt")。在1.7.6版本之后,这里会通过脚本自动处理,无须手动填写。
把dexOptions的jumboMode打开。
dexOptions {
jumboMode = true
}
输出路径为:build/intermediates/tinker_intermediates/tinker_multidexkeep.pro。 后你可以在build/outputs/tinkerPatch中找到输出的文件。
多Flavor打包
有的时候我们希望通过flavor方式打包,在sample中提供了简单的用法事例:
通过flavor编译,这个时候我们可以看到bakApk路径是一个按照flavor名称区分的目录;
将编译目录路径填写到sample中tinkerBuildFlavorDirectory,其他的几个字段不需要填写,这里会自动根据路径拼接;
ext {
tinkerBuildFlavorDirectory = "${bakPath}/app-1014-13-35-12"
}
- 运行tinkerPatchAllFlavorDebug或者tinkerPatchAllFlavorRelease即可得到所有flavor的补丁包。
注:
对于渠道包,如果不是需要使用热修复,那么怎么生成渠道包都可以的。
对于flavor编译渠道包,会导致不同的渠道包由于BuildConfig变化导致classes.dex差异,这种方案是不可取的。
将渠道信息写在apk文件的zip comment中,是非常推荐的,例如可以使用项目 packer-ng-plugin 或者可使用V2 Scheme的 walle , 也包括最新出来的多渠道打包神器 ApkChannelPackage ,说一下区别,如果要使用热修复的话,对于不需要加固的app,那么生成渠道包,这三种方案都可以采用;对于要加固的app,只能采用ApkChannelPackage这种方案中的根据已有APK生成渠道包。 这篇文章 对多渠道打包工具对比做了详细的区分。目前采用的也是ApkChannelPackage方案。
Sample的使用方法
Demo请参考tinker-sample-android, 它的使用方法如下:
-
调用
assembleDebug
编译,我们会将编译过的包保存在build/bakApk中。然后我们将它安装到手机,点击SHOW INFO
按钮,可以看到补丁并没有加载.
-
修改代码,例如将MainActivity中
I am on patch onCreate
的Log打开。然后我们需要修改build.gradle中的参数,将步骤一编译保存的安装包路径拷贝到tinkerPatch
中的oldApk
参数中。
-
调用
tinkerPatchDebug
, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/
。然后我们将patch_signed_7zip.apk
推送到手机的sdcard中。adb push ./app/build/outputs/tinkerPatch/debug/patch_signed_7zip.apk /storage/sdcard0/
-
点击
LOAD PATCH
按钮, 如果看到patch success, please restart process
的toast,即可锁屏或者点击KILL SELF
按钮
-
我们可以看到的确出现了
I am on patch onCreate
日志,同时点击SHOW INFO
按钮,显示补丁包的确已经加载成功了。
Release的使用方法
Tinker的使用方式如下,以gradle接入的release包为例:
- 每次编译或发包将安装包与mapping文件备份;
- 若有补丁包的需要,按自身需要修改你的代码、库文件等;
- 将备份的基准安装包与mapping文件输入到tinkerPatch的配置中;
- 运行tinkerPatchRelease,即可自动编译最新的安装包,并与输入基准包作差异,得到最终的补丁包。
调试源码
tinker调试源码非常简单,大家需要在tinker的主工程运行tinker group中buildAndPublishTinkerToLocalMaven
任务即可。
此外由于localmaven无法传递依赖,需要在使用的地方再显式引用以下库:
compile("com.tencent.tinker:tinker-android-loader:${TINKER_VERSION}") { changing = true }
compile("com.tencent.tinker:aosp-dexutils:${TINKER_VERSION}") { changing = true }
compile("com.tencent.tinker:bsdiff-util:${TINKER_VERSION}") { changing = true }
compile("com.tencent.tinker:tinker-commons:${TINKER_VERSION}") { changing = true }
github/Tinker的默认分支为master分支,几个含义的含义分别是:
- master分支;最近一次release的稳定代码,我们在master分支打tag;
- dev分支;开发分支,这里会包含下一个版本的代码,我们只能给dev分支提pr以及验证部分已经修复的issue;
- hotfix分支;为了修复tinker紧急bug的分支。
关于tinker分支管理、issue以及pr规范,请阅读Tinker Contributing Guide。
为了将tinker单独分块更清晰,可以将tinker相关的代码整理到 tinkerpatch.gradle 中
加固支持
从1.7.8开始,tinker又支持加固了,只需要修改tinkerpatch.gradle中的这部分
buildConfig {
applyMapping = getApplyMappingPath()
applyResourceMapping = getApplyResourceMappingPath()
tinkerId = getTinkerIdValue()
keepDexApply = false
isProtectedApp = true //开启加固
...
}
补丁管理后台:
https://github.com/baidao/tinker-manager
一、集成Tinker app/build.gradle 配置,参考官方 sample,也可以参考SDK里的 sample
二、集成SDK
- app/build.gradle
repositories {
jcenter()
}
dependencies {
...
compile 'com.dx168.patchsdk:patchsdk:1.2.6'
}
- ApplicationLike
@SuppressWarnings("unused")
@DefaultLifeCycle(application = "com.dx168.patchsdk.sample.MyApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class MyApplicationLike extends TinkerApplicationLike {
private OriginalApplication originalApplication;
public MyApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
originalApplication = new OriginalApplication();
}
@Override
public void onCreate() {
super.onCreate();
String appId = "20170112162040035-6936";
String appSecret = "d978d00c0c1344959afa9d0a39d7dab3";
PatchManager.getInstance().init(getApplication(), "http://xxx.xxx.xxx/hotfix-apis/", appId, appSecret, new ActualPatchManager() {
@Override
public void cleanPatch(Context context) {
TinkerInstaller.cleanPatch(context);
}
@Override
public void patch(Context context, String patchPath) {
TinkerInstaller.onReceiveUpgradePatch(context, patchPath);
}
});
PatchManager.getInstance().register(new Listener() {
...
});
PatchManager.getInstance().setTag("your tag");
PatchManager.getInstance().setChannel("");
PatchManager.getInstance().queryAndPatch();
originalApplication.onCreate();
}
}
- 通知结果
LoadReporter
@Override
public void onLoadResult(File patchDirectory, int loadCode, long cost) {
super.onLoadResult(patchDirectory, loadCode, cost);
switch (loadCode) {
case ShareConstants.ERROR_LOAD_OK:
PatchManager.getInstance().onLoadSuccess();
...
break;
default:
PatchManager.getInstance().onLoadFailure();
break;
}
...
}
TinkerResultService
@Override
public void onPatchResult(final PatchResult result) {
...
if (result.isSuccess) {
PatchManager.getInstance().onPatchSuccess(result.rawPatchFilePath);
} else {
PatchManager.getInstance().onPatchFailure(result.rawPatchFilePath);
}
...
}
- 记得注册TinkerResultService
三、补丁调试工具(patchtool) 测试未发布的补丁,可以扫描补丁二维码,下载补丁、立即修复、重启应用
注:
参考 Readme 搭建补丁后台,不过这个后台没有push下发补丁的功能,只有后台下发后,客户端pull拉取补丁,进行补丁修复操作
如果你的app用的是bugly来作为异常上报和运营统计,那么可以直接使用bugly提供的后台,具体操作请参考 这里 , 小巫同学录制了一系列相关的视频,参考视频教程即可学会,地址在 这里 ,bugly支持push下发,比自己搭建后台更加有优势,同样,也可以使用 TinkerPatch补丁管理后台 ,不过是收费的。
参考文档:
- Tinker热修复集成总结
- Tinker 接入指南
- tinker官方文档
- tinker补丁管理后台