TINKER 集成
Tinker是啥
Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。Tinker Github
为什么使用Tinker
当前市面的热补丁方案有很多,其中比较出名的有阿里的AndFix、美团的Robust以及QZone的超级补丁方案。但它们都存在无法解决的问题,这也是正是我们推出Tinker的原因。如下,就可以知道Tinker的强大之处。
Tinker | QZone | AndFix | Robust | |
---|---|---|---|---|
类替换 | yes | yes | no | no |
So替换 | yes | no | no | no |
资源替换 | yes | yes | no | no |
全平台支持 | yes | yes | no | yes |
即时生效 | no | no | yes | yes |
性能损耗 | 较小 | 较大 | 较小 | 较小 |
补丁包大小 | 较小 | 较大 | 一般 | 一般 |
开发透明 | yes | yes | no | no |
复杂度 | 较低 | 较低 | 复杂 | 复杂 |
Rom体积 | Dalvik较大 | 较小 | 较小 | 较小 |
成功率 | 较高 | 较高 | 一般 | 最高 |
Tinker的已知问题
由于原理与系统限制,Tinker有以下已知问题:
Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件
由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
在Android N上,补丁对应用启动时间有轻微的影响;
不支持部分三星android-21机型,加载补丁时会主动抛出”TinkerRuntimeException:checkDexInstall failed”;
对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。
Tinker集成过程(使用bugly集成)
项目工程的.gradle 中添加
classpath "com.tencent.bugly:tinker-support:1.2.0"
app的.gradle中添加
implementation 'com.tencent.bugly:crashreport_upgrade:1.4.2'
implementation 'com.tencent.tinker:tinker-android-lib:1.9.14'
应用bugly配置好的gradle文件
apply from: 'tinker-support.gradle'
tinker-support.gradle 代码
apply plugin: 'com.tencent.bugly.tinker-support'
def bakPath = file("${buildDir}/bakApk/")
/**
* 此处填写每次构建生成的基准包目录
*/
def baseApkDir = "huozhu-1104-11-36-34"
/**
* 对于插件各参数的详细解析请参考
*/
tinkerSupport {
// 开启tinker-support插件,默认值true
enable = true
// 指定归档目录,默认值当前module的子目录tinker
autoBackupApkDir = "${bakPath}"
// 是否启用覆盖tinkerPatch配置功能,默认值false
// 开启后tinkerPatch配置不生效,即无需添加tinkerPatch
overrideTinkerPatchConfiguration = true
// 编译补丁包时,必需指定基线版本的apk,默认值为空
// 如果为空,则表示不是进行补丁包的编译
// @{link tinkerPatch.oldApk }
baseApk = "${bakPath}/${baseApkDir}/huozhu-release.apk"
// 对应tinker插件applyMapping
baseApkProguardMapping = "${bakPath}/${baseApkDir}/huozhu-release-mapping.txt"
// 对应tinker插件applyResourceMapping
baseApkResourceMapping = "${bakPath}/${baseApkDir}/huozhu-release-R.txt"
// 构建基准包和补丁包都要指定不同的tinkerId,并且必须保证唯一性
tinkerId = "base_11.04.11.49"
// 构建多渠道补丁时使用
// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
// 是否启用加固模式,默认为false.(tinker-spport 1.0.7起支持)
// isProtectedApp = true
// 是否开启反射Application模式
enableProxyApplication = false
// 是否支持新增非export的Activity(注意:设置为true才能修改AndroidManifest文件)
supportHotplugComponent = true
}
/**
* 一般来说,我们无需对下面的参数做任何的修改
* 对于各参数的详细介绍请参考:
* https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
*/
tinkerPatch {
//oldApk ="${bakPath}/${appName}/app-release.apk"
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
//tinkerId = "1.0.1-base"
//applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" // 可选,设置mapping文件,建议保持旧apk的proguard混淆方式
//applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配
}
}
配置application
新建SampleApplicationLike 继承自DefaultApplicationLike ,开启反射application模式
enableProxyApplication = false //tinker-support.gradle 中的配置
在onBaseContextAttached 添加MultiDex 和Tinker
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
// you must install multiDex whatever tinker is installed!
MultiDex.install(base);
// 安装tinker
TinkerManager.installTinker(this);
}
应用的Application 继承自TinkerApplication 并重写默认构造方法
public MyApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, “xx.xx.SampleApplicationLike”,
“com.tencent.tinker.loader.TinkerLoader”, false);
}
Tinker 生成插件包(可使用bugly中的tinker-support)
先生成基准包,也就是上线后有bug的包;默认在build/bakApk 中 assemble一次就会生成一次
基准包目录结构
huozhu-release.apk 因为是组件化的目录结构 所以是huozhu-release.apk 而不是app-release.apk 而且注意 tinker-support.gradle 配置的指定文件名称是否一致;
huozhu-release-mapping.txt :开启混淆后会生成mapping文件
基准包生成后将 地址 复制到配置中
/**
* 此处填写每次构建生成的基准包目录
*/
def baseApkDir = "huozhu-1104-11-36-34"
同时指定一个唯一的tinkerId 可以理解为 每一次打补丁包都要指定一个tinkerId
修改部分代码 然后执行生成补丁
因为是bugly中的tinker 所以直接使用tinker-support中的buildTinkerPatchRelease ,在outputs根目录生的patch生成了可用补丁
如果是tinker中的tinkerPatchRelease 则在outputs/apk/tinkerPatch中生成了补丁
加载补丁包
在app首页或者任意位置去加载补丁包
这里封装了一下下载补丁的方法 直接调用即可
PatchUtils.getInstance().checkPatch(context);
核心方法是
TinkerInstaller.onReceiveUpgradePatch(context, file.getAbsolutePath());
Tinker中已经有各种判断 补丁是否正确等判断,所以直接加载就可以。
加载监听:
TinkerManager.getInstance().setTinkerListener(new TinkerManager.TinkerListener() {
@Override
public void onDownloadSuccess(String s) {
Log.d("onDownloadSuccess", s);
}
@Override
public void onDownloadFailure(String s) {
Log.d("onDownloadFailure", s);
}
@Override
public void onPatchStart() {
}
@Override
public void onApplySuccess(String s) {
Log.d("onApplySuccess", s);
Toast.makeText(context, "更新完成 即将重启", Toast.LENGTH_SHORT).show();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
PackageManager packageManager = context.getPackageManager();
Intent intent = packageManager.getLaunchIntentForPackage(context.getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
AppUtils.exitApp(context);
System.exit(0);
}
}, 2000);
}
@Override
public void onApplyFailure(String s) {
Log.d("onApplyFailure", s);
}
@Override
public void onPatchRollback() {
}
});
遇到的问题
1.当项目中含有第三方lib库时候,加载补丁会报错
java.lang.UnsatisfiedLinkError: Shared library "/data/app/***-3IhVXmnAlMEQk_hwO6_0Mg==/lib/arm/libc++_shared.so" already opened by ClassLoader
解决办法:第三方库加载前,有tinker加载
TinkerLoadLibrary.loadArmLibrary(getApplication(), "BaiduMapSDK_base_v6_0_0");
TinkerLoadLibrary.loadArmLibrary(getApplication(), "BaiduMapSDK_map_v6_0_0");
TinkerLoadLibrary.loadArmLibrary(getApplication(), "BaiduTraceSDK_v3_0_7");
2.新添加的activity 找不到声明
基础包必须设置supportComponent=true,并且新增Activity的Exported属性必须为false。
后续
如何对多渠道、加固包等做处理,以及少扣绩效