文章目录

  • 1. 介绍
  • 2. 使用
    • 2.1 工程build.gradle
    • 2.2 模块build.gradle
    • 2.3 增加tinker.gradle
    • 2.4 增加相关java代码
    • 2.5 修改AndroidManifest.xml
    • 2.6 修改MainActivity
  • 3. 打包
  • 4. 打补丁包
  • 5. 代码
  • 6. 参考资料

1. 介绍

Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。

详细介绍查看:https://github.com/Tencent/tinker/wiki
Tinker的github:https://github.com/Tencent/tinker

2. 使用

2.1 工程build.gradle

添加:

classpath "com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1"

2.2 模块build.gradle

1) apply from: 'tinker.gradle' ,后面会新增 tinker.gradle
2)引入 tinker库

implementation 'com.tencent.tinker:tinker-android-lib:1.9.14.19'


3) minSdkVersion必须是小于或等于20

2.3 增加tinker.gradle

tinker.gradle


def bakPath = file("${buildDir}/bakApk/")
def oldApkName = "app-debug-0421-15-07-54"ext {// for some reason, you may want to ignore tinkerBuild, such as instant run debug build?tinkerEnabled = true// 基准apk路径tinkerOldApkPath = "${bakPath}/${oldApkName}.apk"// 未开启混淆,则不需要填写tinkerApplyMappingPath = "${bakPath}/${oldApkName}-mapping.txt"// 基准apk中的R文件路径tinkerApplyResourcePath = "${bakPath}/${oldApkName}-R.txt"// flavortinkerBuildFlavorDirectory = "${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 buildWithTinker() {return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
}def getTinkerIdValue() {//return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()return hasProperty("TINKER_ID") ? TINKER_ID : (new Date().format("yyyyMMddHHmm").toLong())
}def getTinkerBuildFlavorDirectory() {return ext.tinkerBuildFlavorDirectory
}if (buildWithTinker()) {apply plugin: 'com.tencent.tinker.patch'tinkerPatch {/*** 默认为null* 将旧的apk和新的apk建立关联* 从build / bakApk添加apk*/oldApk = getOldApkPath()/*** 可选,默认'false'*有些情况下我们可能会收到一些警告*如果ignoreWarning为true,我们只是断言补丁过程* case 1:minSdkVersion低于14,但是你使用dexMode与raw。* case 2:在AndroidManifest.xml中新添加Android组件,* case 3:装载器类在dex.loader {}不保留在主要的dex,*          它必须让tinker不工作。* case 4:在dex.loader {}中的loader类改变,*          加载器类是加载补丁dex。改变它们是没有用的。*          它不会崩溃,但这些更改不会影响。你可以忽略它* case 5:resources.arsc已经改变,但是我们不使用applyResourceMapping来构建*/ignoreWarning = true/*** 可选,默认为“true”* 是否签名补丁文件* 如果没有,你必须自己做。否则在补丁加载过程中无法检查成功* 我们将使用sign配置与您的构建类型*/useSign = true/*** 可选,默认为“true”* 是否使用tinker构建*/tinkerEnable = buildWithTinker()/*** 警告,applyMapping会影响正常的android build!*/buildConfig {/*** 可选,默认为'null'* 如果我们使用tinkerPatch构建补丁apk,你最好应用旧的* apk映射文件如果minifyEnabled是启用!* 警告:你必须小心,它会影响正常的组装构建!*/applyMapping = getApplyMappingPath()/*** 可选,默认为'null'* 最好保留R.txt文件中的资源id,以减少java更改*/applyResourceMapping = getApplyResourceMappingPath()/*** 必需,默认'null'* 因为我们不想检查基地apk与md5在运行时(它是慢)* tinkerId用于在试图应用补丁时标识唯一的基本apk。* 我们可以使用git rev,svn rev或者简单的versionCode。* 我们将在您的清单中自动生成tinkerId*/tinkerId = getTinkerIdValue()/*** 如果keepDexApply为true,则表示dex指向旧apk的类。* 打开这可以减少dex diff文件大小。*/keepDexApply = false/*** optional, default 'false'* Whether tinker should treat the base apk as the one being protected by app* protection tools.* If this attribute is true, the generated patch package will contain a* dex including all changed classes instead of any dexdiff patch-info files.*/isProtectedApp = false/*** optional, default 'false'* Whether tinker should support component hotplug (add new component dynamically).* If this attribute is true, the component added in new apk will be available after* patch is successfully loaded. Otherwise an error would be announced when generating patch* on compile-time.** <b>Notice that currently this feature is incubating and only support NON-EXPORTED Activity</b>*/supportHotplugComponent = false}dex {/*** 可选,默认'jar'* 只能是'raw'或'jar'。对于原始,我们将保持其原始格式* 对于jar,我们将使用zip格式重新包装dexes。* 如果你想支持下面14,你必须使用jar* 或者你想保存rom或检查更快,你也可以使用原始模式*/dexMode = "jar"/*** 必需,默认'[]'* apk中的dexes应该处理tinkerPatch* 它支持*或?模式。*/pattern = ["classes*.dex","assets/secondary-dex-?.jar"]/*** 必需,默认'[]'* 警告,这是非常非常重要的,加载类不能随补丁改变。* 因此,它们将从补丁程序中删除。* 你必须把下面的类放到主要的dex。* 简单地说,你应该添加自己的应用程序{@code tinker.sample.android.SampleApplication}* 自己的tinkerLoader,和你使用的类*/loader = [//use sample, let BaseBuildInfo unchangeable with tinker"tinker.sample.android.app.BaseBuildInfo"]}lib {/*** 可选,默认'[]'* apk中的图书馆应该处理tinkerPatch* 它支持*或?模式。* 对于资源库,我们只是在补丁目录中恢复它们* 你可以得到他们在TinkerLoadResult与Tinker*/pattern = ["lib/*/*.so"]}res {/*** 可选,默认'[]'* apk中的什么资源应该处理tinkerPatch* 它支持*或?模式。* 你必须包括你在这里的所有资源,* 否则,他们不会重新包装在新的apk资源。*/pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]/***  可选,默认'[]'*  资源文件排除模式,忽略添加,删除或修改资源更改* *它支持*或?模式。* *警告,我们只能使用文件没有relative与resources.arsc*/ignoreChange = ["assets/sample_meta.txt"]/***  默认100kb* *对于修改资源,如果它大于'largeModSize'* *我们想使用bsdiff算法来减少补丁文件的大小*/largeModSize = 100}packageConfig {/***可选,默认'TINKER_ID,TINKER_ID_VALUE','NEW_TINKER_ID,NEW_TINKER_ID_VALUE'* 包元文件gen。路径是修补程序文件中的assets / package_meta.txt* 你可以在您自己的PackageCheck方法中使用securityCheck.getPackageProperties()* 或TinkerLoadResult.getPackageConfigByName* 我们将从旧的apk清单为您自动获取TINKER_ID,* 其他配置文件(如下面的patchMessage)不是必需的*/configField("patchMessage", "tinker is sample to use")/***只是一个例子,你可以使用如sdkVersion,品牌,渠道...* 你可以在SamplePatchListener中解析它。* 然后你可以使用补丁条件!*/configField("platform", "all")/*** 补丁版本通过packageConfig*/configField("patchVersion", "1.0.2")}//或者您可以添加外部的配置文件,或从旧apk获取元值//project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test"))//project.tinkerPatch.packageConfig.configField("test2", "sample")/*** 如果你不使用zipArtifact或者path,我们只是使用7za来试试*/sevenZip {/*** 可选,默认'7za'* 7zip工件路径,它将使用正确的7za与您的平台*/zipArtifact = "com.tencent.mm:SevenZip:1.1.10"/*** 可选,默认'7za'* 你可以自己指定7za路径,它将覆盖zipArtifact值*/
//        path = "/usr/local/bin/7za"}}List<String> flavors = new ArrayList<>();project.android.productFlavors.each { flavor ->flavors.add(flavor.name)}boolean hasFlavors = flavors.size() > 0def date = new Date().format("yyyyMMddHHmm")/*** bak apk and mapping*/android.applicationVariants.all { variant ->/*** task type, you want to bak*/def taskName = variant.nametasks.all {if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {it.doLast {copy {def fileNamePrefix = "${project.name}-${variant.baseName}"def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPathif (variant.metaClass.hasProperty(variant, 'packageApplicationProvider')) {def packageAndroidArtifact = variant.packageApplicationProvider.get()if (packageAndroidArtifact != null) {try {from new File(packageAndroidArtifact.outputDirectory.getAsFile().get(), variant.outputs.first().apkData.outputFileName)} catch (Exception e) {from new File(packageAndroidArtifact.outputDirectory, variant.outputs.first().apkData.outputFileName)}} else {from variant.outputs.first().mainOutputFile.outputFile}} else {from variant.outputs.first().outputFile}into destPathrename { String fileName ->fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")}from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"into destPathrename { String fileName ->fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")}from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"from "${buildDir}/intermediates/symbol_list/${variant.dirName}/R.txt"from "${buildDir}/intermediates/runtime_symbol_list/${variant.dirName}/R.txt"into destPathrename { String fileName ->fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")}}}}}}project.afterEvaluate {//sample use for build all flavor for one timeif (hasFlavors) {task(tinkerPatchAllFlavorRelease) {group = 'tinker'def originOldPath = getTinkerBuildFlavorDirectory()for (String flavor : flavors) {def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")dependsOn tinkerTaskdef preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")preAssembleTask.doFirst {String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"}}}task(tinkerPatchAllFlavorDebug) {group = 'tinker'def originOldPath = getTinkerBuildFlavorDirectory()for (String flavor : flavors) {def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")dependsOn tinkerTaskdef preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")preAssembleTask.doFirst {String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"}}}}}
}

2022/07/25修改,注意:升级包与旧版本的tinkerId必须一样,否则校验不成功,不执行升级。

2.4 增加相关java代码


具体代码查看:https://gitee.com/jie-xio/android_samples/tree/master/tinker/TestTinker

2.5 修改AndroidManifest.xml

2.6 修改MainActivity

public class MainActivity extends AppCompatActivity{private String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";private TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);textView = findViewById(R.id.tv_text);textView.setText("补丁信息xcxcxcxcx");//请求文件权限RxPermissions.request(this, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE).subscribe(granted -> {if (granted) {//权限允许} else {//权限拒绝}});TinkerLoadLibrary.installNavitveLibraryABI(this.getApplicationContext(), "armeabi-v7a");System.loadLibrary("testtinker");}/*** 加载热补丁插件*/public void loadPatch(View v) {TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path);}/*** 查看补丁信息*/public void showInfo(View v) {// add more Build Infofinal StringBuilder sb = new StringBuilder();Tinker tinker = Tinker.with(getApplicationContext());if (tinker.isTinkerLoaded()) {sb.append(String.format("[补丁已加载] \n"));sb.append(String.format("[基准包版本号] %s \n", BuildConfig.TINKER_ID));sb.append(String.format("[补丁号] %s \n", tinker.getTinkerLoadResultIfPresent().getPackageConfigByName(ShareConstants.TINKER_ID)));sb.append(String.format("[补丁版本] %s \n", tinker.getTinkerLoadResultIfPresent().getPackageConfigByName("patchVersion")));sb.append(String.format("[补丁占用空间] %s k \n", tinker.getTinkerRomSpace()));sb.append(String.format("[其他] %s \n", stringFromCpp()));sb.append(String.format("[其他] %s \n", stringOther()));} else {sb.append(String.format("[补丁未加载] \n"));sb.append(String.format("[基准包版本号] %s \n", BuildConfig.TINKER_ID));sb.append(String.format("[TINKER_ID] %s \n", ShareTinkerInternals.getManifestTinkerID(getApplicationContext())));sb.append(String.format("[其他] %s \n", stringFromCpp()));}textView.setText(sb);}/*** 清除补包*/public void cleanPatch(View v){Tinker.with(getApplicationContext()).cleanPatch();}public native String stringFromCpp();public native String stringOther();}

如果需要load C++库文件的话,需要这样load

TinkerLoadLibrary.installNavitveLibraryABI(this.getApplication(), "armeabi-v7a"); 这行代码最后放到MainActivityonCreate中,或者干脆放到SampleApplicationLike类的onCreate中:

详细说明参考:Tinker-API概览#library库的加载

3. 打包


注意,如果Gradle窗口打开,没有Tasks,则需要进入设置

build完成后, 会在这个目录生成apk

4. 打补丁包

修改tinker.gradle中的基础包

然后执行tinkerPatchDebug或者tinkerPatchRelease,打debug或者release的补丁包
例如,打debug版本的补丁包,生成的apk在如下目录

5. 代码

https://gitee.com/jie-xio/android_samples/tree/master/tinker/TestTinker

6. 参考资料

【1】 Tinker-接入指南
【2】 Tinker-API概览
【3】 Bugly热更新 so 文件
【4】使用腾讯bugly集成热更新使用踩坑记录

微信Android热补丁方案Tinker相关推荐

  1. Tinker -- 微信Android热补丁方案 接入指南

    这两年来热修复对与移动开发是比较热门的话题,  HotFix  能做什么?他可以在用户无感知的情况下,后台修复出现的 bug,不需要通过升级发版新App,对用户体验来说是很大的提升,因为频繁发版的话, ...

  2. Tinker -- 微信Android热补丁方案 常见问题

    Tinker 常见问题 Issue/提问须知 在提交issue之前,我们应该先查询是否已经有相关的issue.提交issue时,我们需要写明issue的原因,以及编译或运行过程的日志(加载进程以及Pa ...

  3. Android 热修复方案Tinker(五) SO补丁加载

    基于Tinker V1.7.5 Android 热修复方案Tinker(一) Application改造 Android 热修复方案Tinker(二) 补丁加载流程 Android 热修复方案Tink ...

  4. 【腾讯bugly干货分享】微信Android热补丁实践演进之路

    为什么80%的码农都做不了架构师?>>>    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://bugly.qq.com/bbs/forum.ph ...

  5. Android 热修复方案Tinker(三) Dex补丁加载

    转载来源:http://blog.csdn.net/l2show/article/details/53307523 之前有说到Tinker的修复原理是跟Qzone类似,这里就详细分析一下为什么这样做可 ...

  6. Android 热修复方案分析

    绝大部分的APP项目其实都需要一个动态化方案,来应对线上紧急bug修复发新版本的高成本.之前有利用加壳,分拆两个dex结合DexClassLoader实现了一套全量更新的热更方案.实现原理在Andro ...

  7. (4.2.32)各大热补丁方案分析和比较

    选自: [腾讯bugly干货分享]微信Android热补丁实践演进之路 各大热补丁方案分析和比较 继插件化后,热补丁技术在2015年开始爆发,目前已经是非常热门的Android开发技术.其中比较著名的 ...

  8. android热更新框架nuwa,Android热更新技术——Tinker、nuwa、AndFix、Dexposed

    一.热修复技术作用 线上app BUG紧急修复,不重新发版,不重新安装,在线远程修复问题 二.局限性与适用场景 补丁只能针对单一客户端版本,随着版本差异变大补丁体积也会增大: 补丁不能支持所有的修改, ...

  9. Android 热补丁技术——资源的热修复

    前言 今年真是热补丁框架的洪荒之力爆发的一年,短短几个月内,已经出现了好几个热修复的框架了,基本上都是大同小异,这里我就不过多的去评论这些框架.只有自己真正的去经历过,你才会发现其中的 大写的坑 事实 ...

  10. 微信Android热更新Tinker使用详解(by 星空武哥)

    转载请标注原创地址:http://blog.csdn.net/lsyz0021/article/details/54694260 Tinker是什么 Tinker是微信官方的Android热补丁解决方 ...

最新文章

  1. python脚本实例手机端-用Python实现自动化操作Android手机
  2. OVS vswitchd启动(三十七)
  3. 织梦php调用字段,织梦dedecms搜索页调用自定义字段的方法
  4. Linux进程的创建和父子进程同步,操作系统实验报告_Linux进程创建与通信.doc
  5. 【网络】浏览器输入URL到展示页面全过程(含互联网协议及HTTPS简介)
  6. 揭秘赚钱的技巧,学会让钱自己进来
  7. php遍历文件夹下文件内容_PHP递归遍历指定文件夹内的文件实现方法
  8. Python3 hex() 函数
  9. 高阶多项式合并同类项程序c语言,c语言实现两多项式相乘并排序合并同类项.doc...
  10. 创建SSIS包—建立端到端的package
  11. visio程序流程图绘制教程
  12. 第三方支付系统简易版支付系统部署
  13. Docker--docker ps 命令与结果解析
  14. 联想服务器怎么接显示器,联想笔记本连接显示器 联想笔记本外接显示器怎么设置...
  15. python测试request代理IP是否替换
  16. QT 黑色风格+白色风格+淡蓝色风格样式表。
  17. 红米5 Android 8.0,红米 5 获得 Android 8.0 稳定版更新:修复大量问题
  18. VB创建写字板小程序
  19. 俄罗斯封锁了刻赤海峡,这是乌克兰自讨苦吃吗?
  20. 养成精通英语的三十个好习惯

热门文章

  1. 受贿千万,字节前餐饮主管二审被判6年
  2. css3新单位vw、vh、vmin、vmax的使用详解
  3. Android 调起微信扫一扫
  4. 有关SoftICE的详细操作指导教程
  5. 如何在html中加入导航栏,网页制作时如何添加导航栏
  6. iPhone中国移动收不到彩信,设置方法?
  7. 字节跳动测开发实习面试
  8. 精益创业实践|企业创新如何克服9大关键挑战
  9. Java 语言被很多人抱怨语法繁琐、开发效率低、体系繁杂而笨重,为什么还有这么强的生命力,尤其是在企_-Chaz-_新浪博客
  10. Blender 建模