前言
此文章属于Tinker初中级集成详解,暂时 对Tinker原理不做过多详解,若想深入了解其原理请自行查阅相关文档。
2016年微信推出了自己的热修复框架
Tinker
,从原理上来说,Tinker
的实现方式和QQ空间的一脉相承,随着时间的推移,Tinker
逐渐的成熟并推出了一键集成的SDK,感觉应该比较是靠谱了,所以就动手集成一下吧。集成比较方便,甚至推出了方便集成的SDK版本,不过需要money,所以我们还是从github上入手吧,首先来看下大家有些熟悉的热修复框架对比,看过之后相信大家还是会优先选择Tinker框架:
开始集成SDK(每一步都完美的帮你避过了我踩过的各种坑):
注:我的gradle版本为2.3.3。 3.0.0有好多坑,我自己是没在3.0成功过,所以我暂时放弃了...(能力有限,高手勿喷)
1、添加 gradle 插件依赖(在项目的Gradle添加远程依赖)
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
// TinkerPatch 插件
classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.1.8"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
2、集成 TinkerPatch SDK(app下的gradle)
注:下面两条依赖的版本大家最好按照我目前发的为基准,其他低版本多少都会有些bug,如anno1.8.0 + tinpatch sdk 1.1.8巨坑bug(不报错,只是打补丁失败)
dependencies {
...
// 若使用annotation需要单独引用,对于tinker的其他库都无需再引用
provided("com.tinkerpatch.tinker:tinker-android-anno:1.9.8")
compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.2.8")
}
3、为了简单方便,我们将TinkerPatch相关的配置放到单另的tinkerpatch.gradle里,然后我们在app.gradle里将其引入:(放到app.gradle里最后面就可以)
apply from: 'tinkerpatch.gradle'
4、在app目录下创建file,取名为tinkerpatch.gradle,之后配置tinkerpatch-support相关参数:
注:这里的所有参数请全部粘贴到创建的文件下:
1)baseInfo和variantName参数先按照这个放着暂时不管
2)appKey请自行登录tinker官网登录并创建应用获取即可 To: http://www.tinkerpatch.com/
3)appVersion版本号一般对应你的versionName就行了(versionName改的话 这里就改,这里的appVersion对应tinker官网上传patch时的版本,切记!)
剩余参数暂时不必理会,如想深入了解 还请自行查阅.. :
apply plugin: 'tinkerpatch-support'
/**
* TODO: 请按自己的需求修改为适应自己工程的参数
*/
//基包路径
def bakPath = file("${buildDir}/bakApk/")
//基包文件夹名(打补丁包的时候,需要修改)
def baseInfo = "app-1.0.0-0705-10-01-29"
//版本名称
def variantName = "debug"
/**
* 对于插件各参数的详细解析请参考
*
*/
tinkerpatchSupport {
//可以在debug的时候关闭 tinkerPatch
tinkerEnable = true
//是否使用一键接入功能 默认为false 是否反射 Application 实现一键接入;
// 一般来说,接入 Tinker 我们需要改造我们的 Application, 若这里为 true, 即我们无需对应用做任何改造即可接入。
reflectApplication = true
//将每次编译产生的 apk/mapping.txt/R.txt 归档存储的位置
autoBackupApkPath = "${bakPath}"
appKey = "需要修改成你的 你的!"// 注意!!! 需要修改成你的appkey
/** 注意: 若发布新的全量包, appVersion一定要更新 **/
appVersion = "1.0.0"
def pathPrefix = "${bakPath}/${baseInfo}/${variantName}/"
def name = "${project.name}-${variantName}"
/**
* 基准包的文件路径, 对应 tinker 插件中的 oldApk 参数;编译补丁包时,
* 必需指定基准版本的 apk,默认值为空,则表示不是进行补丁包的编译
*/
baseApkFile = "${pathPrefix}/${name}.apk"
/**
* 基准包的 Proguard mapping.txt 文件路径, 对应 tinker 插件 applyMapping 参数;在编译新的 apk 时候,
* 我们希望通过保持基准 apk 的 proguard 混淆方式,
* 从而减少补丁包的大小。这是强烈推荐的,编译补丁包时,我们推荐输入基准 apk 生成的 mapping.txt 文件。
*/
baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
/**
* 基准包的资源 R.txt 文件路径, 对应 tinker 插件 applyResourceMapping 参数;在编译新的apk时候,
* 我们希望通基准 apk 的 R.txt 文件来保持 Resource Id 的分配,这样不仅可以减少补丁包的大小,
* 同时也避免由于 Resource Id 改变导致 remote view 异常
*/
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 //是否需要签名,打正式包如果这里是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
}
}
给大家上个大致的参数图吧:
上述步骤 配置完之后 sync编译即可
5、初始化TinkerPatch SDK
官方给了我们两种方式来初始化TinkerPatch SDK,第一种是reflectApplication = true 的情况,另一种reflectApplication = false的情况,今天我们说reflectApplication = true这种情况,另一种情况,大家可以到官网中看看。简单来说一下这两种情况的区别啊,当reflectApplication = true这种情况是不需要更改我们项目的Application类,而reflectApplication = false的情况是需要改动Application这个类。
创建tinkerApplication类,进行相关的配置,代码如下:
public class tinkerApplication 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);
}
}
}
再创建一个FetchPtachHnalder类用来轮训判断
public class FetchPatchHandler extends Handler {
public static final long HOUR_INTERVAL = 3600 * 1000;
private long checkInterval;
/**
* 通过handler, 达到按照时间间隔轮训的效果
*/
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);
}
}
最后将AndroidManifest.xml中的添加上相应的网络和SD的权限,还要在application中加上 android:name=".tinkerApplication",附上代码:
到现在tinker基本上已经集成完了,接下来我们在Main的xml里随便写个view以便区分新旧包。
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="#2135"
android:gravity="center_horizontal"
android:padding="30dp"
android:text="old version view" />
写完之后在AS面板里找到Gradle点击build下的assembleDebug进行编译,完成之后在工程左侧的build查看(它会根据编译时间生成一个时间命名的文件夹且内含一个debug包,请忽略我的时间..昨天做的 今天发帖):
生成之后,我们将此debug包安装到手机上,运行效果为我们刚才在xml里写的旧布局:
接下来,我们打补丁包(模拟修复bug)..
首先到tinkerpatch.gradle里更改咱们先前介绍的两个参数:
baseInfo:修改为上面生成的对应文件夹名(请修改为自己的)
variantName:因为打的debug包,所以传入debug即可
/**
* TODO: 请按自己的需求修改为适应自己工程的参数
*/
//基包路径
def bakPath = file("${buildDir}/bakApk/")
//基包文件夹名(打补丁包的时候,需要修改)
def baseInfo = "app-1.0.0-0705-10-01-29"
//版本名称
def variantName = "debug"
最后,我们在xml里做些更改,之后打出差异包补丁,继续找到Gradle下的tinker目录:
编译完成之后,请到工程目录下app->outputs->查看生成的文件夹 tinkerPatch:
接下来,我们将图中选中的patch_signed_7zip.apk上传至tinker官网(发布补丁):
最后,我们拿起手机,后台结束应用之后,重新打开即可看到补丁融合成功:
到tinker官网,我们可以查看补丁下发进度及当前成功率,附图:
注:这个成功率感觉也是靠运气..为什么这么说呢,因为我前天上传了3个补丁,流程完全一样,却失败了3次.....
最后再附上一张失败后错误码对应的msg:
错误码 | 描述 |
---|---|
-1 | 下载补丁时异常 |
-2 | 合成补丁时异常 |
-3 | 加载补丁时异常 |
-100 | 补丁加载出现异常 |
-101 | 补丁加载进入安全模式 |
-102 | 补丁加载Dex时出现异常 |
-103 | 补丁加载Dex时检测失败 |
-104 | 补丁加载资源时出现异常 |
-105 | 补丁加载资源时检测失败 |
-106 | 补丁解释执行时获取instruction set出现异常 |
-107 | 补丁解释执行时命令行出错 |
-200 | 补丁合成出现异常 |
-201 | 补丁合成dexopt文件出现异常 |
-202 | 补丁合成patch.info文件损坏 |
-203 | 补丁合成dexopt文件不存在 |
-204 | 补丁合成dexopt文件格式异常 |
-205 | 补丁合成不支持JIT模式 |
-210 | 补丁合成签名校验失败 |
-211 | 补丁合成dex meta文件损坏 |
-212 | 补丁合成library meta文件损坏 |
-213 | 补丁合成无法从安装包找到tinkerId |
-214 | 补丁合成无法从补丁包找到tinkerId |
-215 | 补丁合成package meta文件损坏 |
-216 | 补丁合成tinkerId不相等 |
-217 | 补丁合成res meta文件损坏 |
-218 | 补丁合成存在不支持的类型 |
-230 | 补丁合成版本校验失败 |
-240 | 补丁合成补丁包失败 |
-241 | 补丁合成dex文件失败 |
-242 | 补丁合成library文件失败 |
-243 | 补丁合成资源文件失败 |
结尾
教程到这里就结束了,如有疑问,请留言,文章中若有误导之处请指出
亲爱的读者,如果此贴对您有帮助,请您动下发财的贵手帮忙右上角【点赞】支持下,非常感谢!