距离2018年结束还有不到最后一周时间了(准备发一顿牢骚~),也有好长时间没写博客了,平时需要遇到技术点生疏时有着通过别人分享的经验之谈去了解巩固,一篇不行看另一篇的习惯,觉得能get到自己需要的那点就够了,但几次之后就会发现自己掌握的技术栈总有些空洞,就像电磁波一样,时而存在,时而消失,通过浏览别人的博客文章得到的心得技术那毕竟那不是我们自己的,当然也就不存在能体会到通过官方文档从一脸懵逼到了解关键点、解决途中种种坑道,到最后应用自己项目中那份欣慰成就感。
好了,不扯那么多了,今天来讲一下如何在自己项目中接入HotFix(热修复)这门优雅为Android项目下发热补丁紧急修复异常bug的解决方案,这对于项目刚上线遇到要命的问题解决提供了极其便利的方式,这里我们选择了腾讯团队推出的Tinker作为本篇的主咖。
目录
写在前面:
本篇博客主要帮大家按步骤将tinker热修复通过gradle方式接入到自己项目中,过程中的gradle代码与每一步的效果图片都会一一贴出,不用担心看不懂。对于专业性知识扩展延伸如:tinker的loader部分源码以及dex diff/patch 、Tinker Gradle Plugin算法相关解析等 本篇不作为分支讲解点。
1.为什么选择tinker?
市场上修复方案有腾讯团队的tinker、阿里的AndFix、美团的Robust以及QZone的超级补丁方案优势特点比较如下:
Tinker | QZone | AndFix | Robust | |
---|---|---|---|---|
类替换 | yes | yes | no | no |
So替换 | yes | no | no | no |
资源替换 | yes | yes | no | no |
全平台支持 | yes | yes | yes | yes |
即时生效 | no | no | yes | yes |
性能损耗 | 较小 | 较大 | 较小 | 较小 |
补丁包大小 | 较小 | 较大 | 一般 | 一般 |
开发透明 | yes | yes | no | no |
复杂度 | 较低 | 较低 | 复杂 | 复杂 |
gradle支持 | yes | no | no | no |
Rom体积 | 较大 | 较小 | 较小 | 较小 |
成功率 | 较高 | 较高 | 一般 | 最高 |
相比来说,还是看重了Tinker热补丁方案支持类、So以及资源的替换,而且对于市场上Android版本系统2.X-8.X(1.9.0以上支持8.X)全平台支持的兼容强大性。况且Tinker已运行在微信的数亿Android设备上,光这点足以膜拜!
1.1 [前戏] 了解以tinker热补丁作为热修复方案的常见三种渠道
-
第一种是自定义集成Tinker,需要后台的支持,这种也是大家了解tinker后并运用最普遍的大众主流方式 ,不过有点小复杂(本篇的接入方式,迎难而上!)
-
第二种是集成了tinker的TinkerPatch 后台补丁平台 ,称无需理解复杂的热修复原理,一行代码即可接入热修复,一键傻瓜式接入平台。(听起来还蛮诱人的耶~ 有免费版,超出提供修复的补丁的标准则需要收费,对于一些企业的需求还可以定制,收费应该是不会低的。但直接接入对自己学习帮助意义不大,建议通过自主自定义接入后再去尝试)
-
第三种是Bugly Android 热更新,没错还是腾讯出品的,集成他们提供的SDK就可以实现自动下载补丁包、合成、并应用补丁的功能,而且提供了热更新管理后台让开发者对每个版本补丁进行管理。但bugly不单单拥有修复补丁的功能,Bugly Android 热更新是它作为应用升级的SDK,它强大的幕后者— Bugly质量监控平台更是全面的应用质量跟踪监控修复平台,据了解目前腾讯的所有产品都已经接入了这个Bugly质量监控平台,可以说是非常强大了,看看百科如何介绍它的:
从2014年10月起,腾讯 Bugly 开始对外开放给更多的开发者使用,这套平台可以帮助开发人员更准确高效的定位解决问题,堪称业界良心典范。 -
其它:主要的就是上述三种方式,其实还有一些个人开发者维护的,如开源在GitHub上的:「tinker-manager」等,稳定性不太敢保证,不过人家能开源出自己的个人方案也值得佩服。
本篇以第一种渠道,也是tinker官方推荐入手的方式,Tinker的自主接入渠道有两种接入方法,一种是Tinker推荐使用的gradle接入,一种是命令行的接入,这里以gradle接入作为讲解。
2.先了解tinker目前的一些限制
3.接入步骤一 — gradle 配置
3.1添加gradle依赖
1>在根目录下的gradle.properties中添加tinker的版本值,方便修改
TINKER_VERSION = 1.9.1
2>项目的gradle中的dependencies
在根目录下的gradle.buildscript.properties中添加具体的tinker版本值
classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"
3>moudle的gradle中:
//apply tinker插件
apply plugin: 'com.tencent.tinker.patch'
android{
dependencies{
.....
//可选,用于生成application类
provided "com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}"
//tinker的核心库 compile编译并打包
compile "com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}"
//解决因过时的Api报错问题
annotationProcessor("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") {
changing = true
}
}
}
然后sync now,通常这时你会遇到集成tinker时遇到的第一个问题
tinkerId is not set!!
先不着急解决,花几分钟时间了解一下在自定义集成tinker时必须明白的一些gradle参数,否则遇到集成失败则无从下手。
3.2需要知道的一些gradle参数
参数 | 默认值 | 描述 |
---|---|---|
tinkerPatch | 全局信息相关的配置项 | |
tinkerEnable | true | 是否打开tinker的功能。 |
oldApk | null | 基准apk包的路径,必须输入,否则会报错。 |
newApk | null | 选填,用于编译补丁apk路径。如果路径合法,即不再编译新的安装包,使用oldApk与newApk直接编译。 |
… | … | … |
useSign | true | 在运行过程中,我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名。 |
tinkerId | null | 在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId。这个是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git版本号、versionName等等。 |
… | … | … |
上面摘选了一些gradle参数详解,关于tinker接入在gradle里涉及的全部参数说明,可以花几分钟时间了解一下官方文档介绍gradle参数详解 , 这里就不一一全部罗列出来了,毕竟介绍说明还是要以官方文档为主。
在看完这些陌生的gradle参数 是不是有点困惑不明白,不过不要紧,这里先贴出我成功接入tinker的gradle配置,通过我的参数注释慢慢了解后,可以再去根据官方提供的gradle去优化自己的配置,从而去体验到Tinker的最佳实践。
3.3可直接拷贝的gradle配置
在build.gradle中的android.defaultConfig{}中添加
buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\""
buildConfigField "String", "PLATFORM", "\"all\""
在build.gradle中的最外层,即android{}和dependencies{}外添加下边的配置
//官方文档默认是将git提交的记录作为tinkerId记录下来
// 用来获取TinkerId(这里以当前的版本号设为tinkerId)
def gitSha() {
return android.defaultConfig.versionName
}
def javaVersion = JavaVersion.VERSION_1_7
def bakPath = file("${buildDir}/bakApk/")
ext {
/**
* 出于某种原因,你可能想要忽略修改构建,例如即时运行调试构建
* for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
*/
tinkerEnabled = true
/** 对于正常的构建
* for normal build
*/
/**
* 老的apk去构建补丁的apk
* old apk file to build patch apk
*/
tinkerOldApkPath = "${bakPath}/app-release-1223-11-57-16.apk"
/**
* 构建补丁apk的混淆映射文件
* proguard mapping file to build patch apk
*/
tinkerApplyMappingPath = "${bakPath}/app-release-1223-11-57-16-mapping.txt"
/**
* 构建补丁apk时,R.txt必须输入资源文件是否改变
* resource R.txt to build patch apk, must input if there is resource changed
*/
tinkerApplyResourcePath = "${bakPath}/app-release-1223-11-57-16-R.txt"
/**
* 只是用于构建所有的特点,如果不,可以忽略这一领域,这里忽略
* only use for build all flavor, if not, just ignore this field
*/
/*tinkerBuildFlavorDirectory = "${bakPath}/"*/
}
def getOldApkPath() {
return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
}
def getApplyMappingPath() {
return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
}
def getApplyResourceMappingPath() {
return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
}
def getTinkerIdValue() {
return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
}
def buildWithTinker() {
return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
}
def getTinkerBuildFlavorDirectory() {
return ext.tinkerBuildFlavorDirectory
}
if (buildWithTinker()) {
apply plugin: 'com.tencent.tinker.patch'
tinkerPatch {
/**
* 必