一、基础
这里使用TinkerPatch也是由于具有完善的后台管理和简洁性,作为小白是个不错的热更新入门sdk。
我们知道在打包后的apk修改为zip文件后可以看到内部的结构是这样的:
java代码会在编译期间转为.class文件,有使用dex工具将.class文件转为.dex文件,是一个可执行文件,也是主要的运行文件。在运行app后便会对其优化出现odex的缓存文件(Davlik)或oat文件(Art),Android安装的Apk文件实质就是zip结尾的压缩文件。在遍历dex时便会将所有的方法存放在一个数组里面,他的长度时65535,这也就是在大项目上打包常会出现的一个问题,方法数超过数组上限后无法打包。
Tinkder则是通过提供Dex差量包,整体替换Dex的方法。在打包时,都会根据基础包上进行打包,通过Dex查分算法生成差异包patch.dex,将差异包分发致客户端后,客户端会根据旧Dex与新patch.dex合并为新的dex,之后将这个新的dex文件插入数组前面使其最先执行该文件,从而修复了原有的bug。
二、代码
注意这里在集成sdk过程中最好紧跟官方文档:
http://tinkerpatch.com/Docs/SDK
1.添加 gradle 插件依赖
在项目根目录build.gradle中添加插件依赖buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.2'
// TinkerPatch 插件
classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.2.8"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
}
2.集成TinkerPatch SDK
在主Module下build.gradle中添加依赖apply plugin: 'tinkerpatch-support'
android {
defaultConfig {
...
multiDexEnabled true
}
...
dexOptions {
javaMaxHeapSize "2g"
preDexLibraries = false
jumboMode = true
}
}
dependencies {
...
// TinkerPatch依赖
provided("com.tinkerpatch.tinker:tinker-android-anno:1.9.8")
compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.2.8")
// 包混合依赖
compile("com.android.support:multidex:1.0.1")
}
在官方文档中推荐独立编写tinker.gradle来管理tinkerpatch依赖管理,这里关系复杂就放在了build.gradle中
Project
|->app
| \—>build.gradle(tinker配置)
|
|->build.gradle(项目常数及基本配置)
->config.gradle
3.配置 tinkerpatchSupport 参数
|
在官方文档中有更全面的描述及使用
这里需要注意的几个:
reflectApplication:是否反射 Application 实现一键接入;一般来说,接入 Tinker 我们需要改造我们的 Application, 若这里为 true, 即我们无需对应用做任何改造即可接入。
appKey:在平台上注册app的key
appVersion:tinkerpatch便是以此code来判定是否更新的依据,以为具有与VersionName同样的性质,所以一般情况下我们会直接使用同一个数值来管理VersionName和Tinker的appVersion,但是在多渠道打包时就需要进行额外处理,以区分多渠道版本
baseApkFile:基准包的文件路径, 对应 tinker 插件中的 oldApk 参数,也是我们在打包时出现的位置。上述的路径可以经测试指到’project/build/bakApk’
这里由于使用多渠道,所以名称和单一渠道的文件名不同,不过基本相似。
4.初始化功能
这里由于我的reflectApplication=false,所以也是继承自己的Applicationpublic class SampleApplication extends Application {
...
@Override
public void onCreate() {
super.onCreate();
// 我们可以从这里获得Tinker加载过程的信息
tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike();
// 初始化TinkerPatch SDK, 更多配置可参照API章节中的,初始化SDK
TinkerPatch.init(tinkerApplicationLike)
.reflectPatchLibrary()
.setPatchRollbackOnScreenOff(true)
.setPatchRestartOnSrceenOff(true);
TinkerPatch.with().fetchPatchUpdateAndPollWithInterval();
}
是的,只需要复制代码,即可完成傻瓜式的热更新功能,当然文档中也有详细描述API功能。
注意:初始化的代码建议紧跟 super.onCreate(),并且所有进程都需要初始化,已达到所有进程都可以被 patch 的目的
如果你确定只想在主进程中初始化 tinkerPatch,那也请至少在 :patch 进程中初始化,否则会有造成 :patch 进程crash,无法使补丁生效
5.使用步骤
是的,代码就上述这么多,完成好后就是后续的打包和生成差异包的工作了。
这幅图可以看到我们需要使用的工具,上面的方框用来打Apk包,下面的方框用来生成差异包。这里由于多渠道的关系,生成了许多不想干的功能,不需要在意。
首先我们双击assembleRelease构建最新的Apk
当然,为了测试看清出,clear-build是打包前的准备工作。此时就会在build文件夹中生成相应的apk
在文件夹中就会出现对应的apk了,不需要在意后续文件夹名称,这是我们打得基准包,就是说后续的差异包都需要在他,需要保存好。
修改文件夹名称
可以看到上述生成的app-1.0.7-1019-09-01-16,他的规则不难看出是app+tinkerVersion+时间,此时因为需要根据这个里面的app来生成差异包,所以在上面的baseInfo文件夹名称中修改他指向为该app的路径即可。
同步一下就可以看到是否指向成功,如果失败可以println打印日志看一下出错的位置。
最后一定要将自己的tinker中的appVersion变高,毕竟是需要提高版本来升级的。
生成差异包
上述准备成功后,我们就可以生成我们的差异包了。首先运行之前讲的下面方框中的tinkerPatchRelease生成差异包,稍等片刻后就好了。
可以清楚看到output中生成了我们需要的tinker文件夹,其中我们需要的就是patch_signed_7zip.apk,我们将它取出来,去除.apk的后缀名称,因为部分手机可能不会下载带.apk的文件,从而发生无法更新的问题。
上传差异包
首先创建版本,需要注意填写版本号一定是基础包的版本号,而不是这个差异包的版本号。
最后只需要上传文件夹和写相应的描述就可以上传啦。
1.在测试的时候首先清除安装在手机中基准包app的进程,
2.在重新进入app,刷新我们的版本管理工具就会看到app正在下载、合并,
3.合并成功后,再清除进程重新进入,就是一次完整的热更新了。
后续我们再更新的时候,
1.只需要将tinker的appVersion提高,
2.生成app差异包,
3.上传差异包就好了。
三、多渠道
上述是单需求的app,问题并不大。但是在多渠道打包时,就显得麻烦。
我们知道在热更新时首先判断是否有更高版本,再确认是否下载最新的差异包。但是在面对多渠道过程时,我们首先需要确定的是这个版本是否相同,其次才是确定是否有更高版本的差异包。
1.准备多渠道功能
在使用tinker多渠道前,我们需要为自己的项目增加多渠道功能。android {
...
flavorDimensions "base","zone"
}
productFlavors {
main {
dimension "base"
}
x {
dimension "zone"
buildConfigField("String", "type", "\"x\"")
}
y {
dimension "zone"
buildConfigField("String", "type", "\"y\"")
}
}
这里我们使用多维度的方式生成两个渠道:mainx和mainy。同步后就会发现在gradle中有给不同包的命令。这里在打包时只需要记住assembleRelease就可以了。
2.准备tinker的多渠道适配
在文件中增加以下代码tinkerpatchSupport {
...
productFlavors {
flavor {
flavorName = "mainx"
appVersion = "A${tinkerpatchSupport.appVersion}"
println("mainX====appVersion:"+appVersion)
pathPrefix = "${bakPath}/${baseInfo}/${flavorName}-${vative}"
name = "${project.name}-${flavorName}-${vative}"
baseApkFile = "${pathPrefix}/${name}.apk"
println("mainX=====baseApkFile:" + baseApkFile)
baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
println("mainX=====baseProguardMappingFile:" + baseProguardMappingFile)
baseResourceRFile = "${pathPrefix}/${name}-R.txt"
println("mainX=====baseResourceRFile:" + baseResourceRFile)
}
flavor {
flavorName = "mainy"
appVersion = "B${tinkerpatchSupport.appVersion}"
println("mainY====appVersion:"+appVersion)
pathPrefix = "${bakPath}/${baseInfo}/${flavorName}-${vative}"
name = "${project.name}-${flavorName}-${vative}"
baseApkFile = "${pathPrefix}/${name}.apk"
println("mainY=====baseApkFile:" + baseApkFile)
baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
println("mainY=====baseProguardMappingFile:" + baseProguardMappingFile)
baseResourceRFile = "${pathPrefix}/${name}-R.txt"
println("mainY=====baseResourceRFile:" + baseResourceRFile)
}
}
}
flavorName:渠道名称
appVersion:覆盖tinkerid版本号
可以看到这里的appVersion是A1.x.x,A是我们给这个版本的一个特殊标志,后面的则是我们之前写好的版本号。
这里增加了一个setAppChannel是用来指明渠道名称的,这里由于在gradle中增加了type,所以设置的字符串是 mainx 和 mainy。TinkerPatch.init(TestApplication.tinkerApplicationLike)
.reflectPatchLibrary()
.setPatchRollbackOnScreenOff(true)
.setPatchRestartOnSrceenOff(true)
.setAppChannel("main"+BuildConfig.type);
3.打包生成Apk
clear-build后,我们双击assembleRelease
可以看到我们在app-1.0.7-1019-10-02-35的文件夹中生成了两个渠道的app文件。
之后就可以修改baseInfo的路径名称,修改tinkerId的版本号了。做好这些准备并同步后,开始生成差异包。
在生成差异包的时候需要注意,我们必须选择给哪一个渠道单独生成差异包。
这里我们选择tinkerPatchMainYRelease。
完成后就可以看到这里的patch包已经生成好了。
4.上传差异包
这里在输入版本号的时候需要注意,我们之前写的是1.0.7,但是在这里我们需要写B1.0.7,因为为了区别另一个渠道mainx这里的tinkerId版本号就用此来区别。
剩下的都和上述一样。