Android架构之路--热更新Tinker
当前市面的热补丁方案有很多,其中比较出名的有阿里的 AndFix、美团的 Robust 以及 QZone 的超级补丁方案。但它们都存在无法解决的问题,这也是正是最后使用 Tinker 的原因。先看一张图对比:
1-1:热更新对比图
Tinker热补丁方案不仅支持类、So 以及资源的替换,它还是2.X-7.X的全平台支持。利用Tinker我们不仅可以用做 bugfix,甚至可以替代功能的发布。Tinker已运行在微信的数亿Android设备上。
TinkerPatch 平台在 Github 为大家提供了各种各样的 Sample,大家可点击前往 [TinkerPatch Github].
Tinker原理图
1-2:原理图
Tinker流程图
1-3:Tinker 流程图
二、Tinker相关网站
微信Tinker Patch官网:Tinker Patch
Github地址:tinker
三、接入Tinker步骤
基础步骤
- 注册Tinker账户、添加APP、记录AppKey,添加 APP 版本、 发布补丁。详细步骤请移步Tinker平台使用文档
主要来说下配置Gradle和代码
1. 配置Tinker版本信息
我们使用配置文件去配置Tinker版本信息,易于统一版本和后面更换版本,如图:
2-1 gradle.properties文件
代码如下:
TINKER_VERSION=1.9.6
TINKERPATCH_VERSION=1.2.6
2. 使用Tinker插件
在根目录下的build.gradle文件下配置,如图:
2-2 添加Tinker插件
代码如下:
classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:${TINKERPATCH_VERSION}"
3. 配置Tinker的gradle脚本
在项目app目录下新建tinkerparch.gradle文件,如图:
2-3 tinkerpatch.gradle
代码如下:
apply plugin: 'tinkerpatch-support'
/**
* TODO: 请按自己的需求修改为适应自己工程的参数
*/
def bakPath = file("${buildDir}/bakApk/")
def baseInfo = "app-1.0.0-0529-14-38-02"
def variantName = "release"
/**
* 对于插件各参数的详细解析请参考
* 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 = true
/**
* 是否开启加固模式,只能在APK将要进行加固时使用,否则会patch失败。
* 如果只在某个渠道使用了加固,可使用多flavors配置
**/
protectedApp = false
/**
* 实验功能
* 补丁是否支持新增 Activity (新增Activity的exported属性必须为false)
**/
supportComponent = true
autoBackupApkPath = "${bakPath}"
/** 注意:换成自己在Tinker平台上申请的appKey**/
appKey = "521db2518e0ca16d"
/** 注意: 若发布新的全量包, 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
}
}
注意
- AppKey:换成自己在Tinker平台上申请的。
- baseInfo:基准包名称,使用Tinker脚本编译在模块的build/bakApk生成编译副本。
- variantName: 这个一般对应buildTypes里面你基准包生成的类型,release、debug或其他
- appVersion:配置和Tinker后台新建补丁包的一致。
其他地方可以暂时不用改
4. 配置模块下的build.gradle
配置签名
如果有不会的同学可以看这篇 Android Studio的两种模式及签名配置
2-4:配置签名
在配置混淆代码的时候,想要提醒下大家,当设置 minifyEnabled 为false时代表不混淆代码,shrinkResources也应设置为false ,它们通常是彼此关联。
要是你设置minifyEnabled 为false,shrinkResources为true,将会报异常,信息如下:
Error:A problem was found with the configuration of task':watch:packageOfficialDebug'.
File '...\build\intermediates\res\resources-official-debug-stripped.ap_' specified for property 'resourceFile' does not exist.
2-4-1:混淆配置
配置依赖
2-5:配置Tinker依赖
使用插件
2-6:使用Tinker插件
具体代码如下:
apply plugin: 'com.android.application'
apply from: 'tinkerpatch.gradle'
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "qqt.com.tinkerdemo"
minSdkVersion 17
targetSdkVersion 25
versionCode 1
versionName "1.0.0"
multiDexEnabled true
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release {
storeFile file("./jks/tinker.jks")
storePassword "123456"
keyAlias "tinker"
keyPassword "123456"
}
debug {
storeFile file("./jks/debug.keystore")
}
}
buildTypes {
release {
minifyEnabled false
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
signingConfig signingConfigs.debug
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
annotationProcessor("com.tinkerpatch.tinker:tinker-android-anno:${TINKER_VERSION}") {
changing = true
}
provided("com.tinkerpatch.tinker:tinker-android-anno:${TINKER_VERSION}") {
changing = true
}
compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:${TINKERPATCH_VERSION}") {
changing = true
}
compile 'com.android.support:multidex:1.0.1'
}
5. 代码集成
最后一步,在自己的代码新建一个Application,把代码集成在App中,别忘了在AndroidManifest里面配置APP。。。
如图:
2-7:集成代码
我是继承MultiDexApplication主要是防止64k异常。有关这块知识,请看 Android 方法数超过64k、编译OOM、编译过慢解决方案
具体代码如下:
package qqt.com.tinkerdemo;
import android.support.multidex.MultiDexApplication;
import com.tencent.tinker.loader.app.ApplicationLike;
import com.tinkerpatch.sdk.TinkerPatch;
import com.tinkerpatch.sdk.loader.TinkerPatchApplicationLike;
/**
* 邮箱:ljh12520jy@163.com
*
* @author Ljh on 2018/5/28
*/
public class App extends MultiDexApplication {
private ApplicationLike mApplicationLike;
@Override
public void onCreate() {
super.onCreate();
mApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike();
// 初始化TinkerPatch SDK, 更多配置可参照API章节中的,初始化SDK
TinkerPatch.init(mApplicationLike)
.reflectPatchLibrary()
.fetchPatchUpdate(true)
// 强制更新
.setPatchRollbackOnScreenOff(true)
.setPatchRestartOnSrceenOff(true)
.setFetchPatchIntervalByHours(3);
// 每隔3个小时(通过setFetchPatchIntervalByHours设置)去访问后台时候有更新,
//通过handler实现轮训的效果
TinkerPatch.with().fetchPatchUpdateAndPollWithInterval();
}
}
四、生成基准包
在生成基准包的时候,要注意一个问题,就是关闭 instant run(当tinkerEnable = true时,false的时候,就不需要),如图:
3-1:关闭InstantRun
在Android Studio的右上角,点击Gradle,如图:
3-2:准备生成基准包
双击assembleRelease生成成功后安装模块/build/outputs/apk/release/app-release.apk就OK了,这时候进去模块/build/bakApk里面记录一下类似app-1.0.0-0530-18-01-59的文件名称,只生成一次基准包,那么就会生成一个。这里需要注意一下,如果点太多生成太多的话确定不了刚刚生成的是哪个,那么就选最新那个或者删掉重新生成基准包。
生成后的基准包如图:
3-3:生成基准包
五、修改bug
在自己的代码中随便修改点代码(Tinker1.9.6 里面支持新增Activity代码)
六、生成补丁包
在生成补丁包前,我们需要去tinkerpatch.gradle文件下修改一些信息。
- baseInfo :把前面app-1.0.0-0529-14-38-02换成我们刚生成记录下的基准包(app-1.0.0-0530-18-01-59)就可以。
- variantName : 因为刚刚我们使用assembleRelease生成的补丁,所以我们只需要使用release
双击TinkerPatchRelease生成差分包,patch_signed_7zip.apk就是补丁包
生成的补丁包如图:
3-4:生成补丁包
3-5:tinkerPatch下的一些文件说明
七、发布补丁包
回到Tinker后台,选中我们开始新建的项目,补丁下发->添加APP版本。然后上传刚刚的patch_signed_7zip.apk。
APP开启强制更新的话那么重启应用就会更新,否则会通过轮询去更新。应用重启才生效。
3-6:发布补丁包
注:在Tinker后台发布的差分包(补丁包)是根据app-1.0.0-0530-18-01-59为基准包下,修复bug生成的补丁包,只对于app-1.0.0-0530-18-01-59版本的apk生效。
3-7:差分包
一、多渠道打包
tinker官方文档推荐用walle或者packer-ng-plugin来辅助打渠道包。估计有不少同学用过,今天我想推荐另外一款多渠道打包的插件ApkMultiChannelPlugin,它作为Android Studio插件进行多渠道打包。
安装步骤:打开 Android Studio: 打开 Setting/Preferences -> Plugins -> Browse repositories 然后搜索 ApkMultiChannel 安装重启。
有不了解的同学,可以直接看它的文档。
我是采用add channel file to META-INF方式进行多渠道打包,在这里提供一个读取渠道的工具类ChannelHelper。
二、多渠道打包步骤
1. 选择一个基准包
选择基准包的一个apk,然后右键,点击Build MultiChannel
1-1:选择基准包
2. 配置
配置签名信息,打包方式和渠道等。
1-2:配置多渠道
配置说明:
Key Store Path: 签名文件的路径
Key Store Password: 签名文件的密码
Key Alias: 密钥别名
Key Password: 密钥密码
Zipalign Path: zipalign 文件的路径(用于优化 apk;zipalign 可以确保所有未压缩的数据均是以相对于文件开始部分的特定字节对齐开始,这样可减少应用消耗的 RAM 量。)
Signer Version: 选择签名版本:apksigner 和 jarsigner
Build Type: 打包方式
Channels: 渠道列表,每行一个,最前面可加 > 或不加(保存信息的时候,程序会自行加上)
我们刚才刚才配置的东西会保存在根目录的 channels.properties里
1-3:channel配置文件
3. 开始打包
配置完成后,选择基准包的一个apk,然后右键,点击Build MultiChannel,就会开始进行多渠道打包,文件会输出在选中的apk的当前目录下的channels是目录下,如图:
1-4:多渠道打包
4. 发布APK
将刚才打包完成的包,分别发布到对应的应用市场。
5. 修改bug
随便修改部分代码
6. 生成补丁包
在生成补丁包前,我们需要去tinkerpatch.gradle文件下修改一些信息。
- baseInfo :改成我们刚才选择基准包的目录app-1.0.1-0601-14-30-42就可以。
双击TinkerPatchRelease生成差分包,patch_signed_7zip.apk就是补丁包 - variantName : 因为刚刚我们使用assembleRelease生成的补丁,所以我们只需要使用release
双击TinkerPatchRelease生成差分包,patch_signed_7zip.apk就是补丁包
1-5:生成补丁包
7. 发布补丁包
回到Tinker后台,选中我们开始新建的项目,补丁下发->添加APP版本。然后上传刚刚的patch_signed_7zip.apk。
APP开启强制更新的话那么重启应用就会更新,否则会通过轮询去更新。应用重启才生效。
1-6: 发布补丁包.png
注:
- 这个补丁包对于以app-1.0.1-0601-14-30-42为基准宝,进行多渠道打包的apk都能生效(亲测成功),如果你把该渠道包进行360加固(protectedApp = true),也生效。
- 当我们在正式环境需要混淆代码:设置 minifyEnabled true,添加混淆:
-keep public class * implements com.tencent.tinker.loader.app.ApplicationLike
如图:
1-7: 混淆代码