Android开发 热修复Tinker框架接入、发布流程及问题解决

您所在的位置:网站首页 自动驾驶路径预测 Android开发 热修复Tinker框架接入、发布流程及问题解决

Android开发 热修复Tinker框架接入、发布流程及问题解决

2023-07-15 13:05| 来源: 网络整理| 查看: 265

Tinker的作用

作为移动端开发者,时常会遇到因为一些小bug而需要重新发版的问题,这还属于小问题,如遇到一些大问题的话,若是通过用户更新来修复那效率就太慢了,亦或是遇到不更新的用户,那么该bug将一直存在用户手机上,这是非常危险的。Tinker是为了解决这种问题而生的,修改少量的代码,生成差分包,然后用户无感下载非常小的更新包,就可以解决问题。它是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。

当然,你也可以使用Tinker来更新你的插件。虽然Tinker可以替代更新,但是为了适应单一职责,因此我还是建议如果需要更新功能的话还是直接发版本,如果是修复一些小bug,那么就可以使用Tinker。

Tinker源码Github地址

接入(亲历心路历程) 找文档

首先打开Github地址,找到底部接入指南。OK,无中文版咋整?(后来发现有中文的文档,只是在他的github主页没有附上) 通过其他大牛和博主了解到,要接入Tinker且使用它,必须要还要接入Bugly。(WTF?) 看看其他热更新框架算了。 结果发现几个主流的全都停止更新了,只有Tinker还在持续更新中。为了可持续发展,没办法,只能接入Tinker了。 但是Tinker的接入文档跟没有一样,那就先看看Bugly如何接把。打开Bugly官网后发现了新大陆,原来Tinker的接入流程全在Bugly的官方文档里面。 这给我激动的差点哭了。

Bugly官网地址

Bugly热更新文档地址 接入Tinker时请拿本文与官方文档配合使用

开始接入 1、在Bugly官网新建我的产品并记录好AppId

QQ图片20211028143120.png

image.png

2、添加插件依赖

工程根目录下“build.gradle”文件中添加:

buildscript { repositories { jcenter() mavenCentral() } dependencies { classpath "com.tencent.bugly:tinker-support:1.1.5" classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.14.6') } } 复制代码

在app module的“build.gradle”文件中添加(注意signingConfigs的注释):

apply plugin: 'com.android.application' // tinker依赖的插件脚本 apply from: 'tinker-support.gradle' android { //必须配置这个,否则打补丁包的时候会因为没有签名而报错 signingConfigs { release { storeFile file('D:\XXX.jks') storePassword 'XXX' keyAlias 'XXX' keyPassword 'XXX' } debug { storeFile file('D:\XXX.jks') storePassword 'XXX' keyAlias 'XXX' keyPassword 'XXX' } } defaultConfig { multiDexEnabled true ndk { abiFilters "arm64-v8a", "armeabi-v7a" } } // recommend dexOptions { jumboMode = true } buildTypes { release { // 混淆 minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' buildConfigField("boolean", "LOG_ERROR", "false") // 注意此处必填 signingConfig signingConfigs.release } debug { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' buildConfigField("boolean", "LOG_ERROR", "true") // 注意此处必填 signingConfig signingConfigs.debug } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { // tinker implementation "com.android.support:multidex:1.0.1" // 多dex配置 implementation 'com.tencent.bugly:crashreport_upgrade:1.3.6' implementation 'com.tencent.tinker:tinker-android-lib:1.9.14.6' } 复制代码

如果此时同步会报错,因为tinker-support.gradle文件不存在,这个文件才是我们打包的主要文件,当我们项目接入Tinker后,如果临时不需要使用Tinker的话,注释apply from: 'tinker-support.gradle' 这段代码就行了。

3、新建tinker-support.gradle

image.png

apply plugin: 'com.tencent.bugly.tinker-support' def bakPath = file("${buildDir}/bakApk/")//这里指向的就是app/bulid/bakApk目录 def baseApkDir = "app-release-folder" //此处填写每次构建生成的基准包目录 //def myTinkerId = "base-" + rootProject.versionName // 用于生成基准包(不用修改) def myTinkerId = "patch-" + rootProject.versionName + ".0" // 用于生成补丁包(每次生成补丁包都要修改一次,最好是 patch-${versionName}.x.x) tinkerSupport { // 开启tinker-support插件,默认值true enable = true // 指定归档目录,默认值当前module的子目录tinker autoBackupApkDir = "${bakPath}" // 是否启用覆盖tinkerPatch配置功能,默认值false // 开启后tinkerPatch配置不生效,即无需添加tinkerPatch overrideTinkerPatchConfiguration = true // 编译补丁包时,必需指定基线版本的apk,默认值为空 // 如果为空,则表示不是进行补丁包的编译 // @{link tinkerPatch.oldApk } baseApk = "${bakPath}/${baseApkDir}/app-release.apk" // 对应tinker插件applyMapping baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt" // 对应tinker插件applyResourceMapping baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt" // 是否启用加固模式,默认为false.(tinker-spport 1.0.7起支持) // isProtectedApp = false // 是否开启反射Application模式 enableProxyApplication = true // 是否支持新增非export的Activity(注意:设置为true才能修改AndroidManifest文件) supportHotplugComponent = true // 构建基准包和补丁包都要指定不同的tinkerId,并且必须保证唯一性 tinkerId = "${myTinkerId}" // 构建多渠道补丁时使用 // buildAllFlavorsDir = "${bakPath}/${baseApkDir}" } tinkerPatch { tinkerEnable = true 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" } buildConfig { keepDexApply = false } } 复制代码

这里有几个点需要注意一下:

这个文件必须在app里面,与app的build.gradle同级 只有开启了enableProxyApplication = true,那么注释apply from: 'tinker-support.gradle'就会让Tinker停用,如果是按照官方的文档重写application的话,那么enableProxyApplication要等于false,那么想停用Tinker的话还需要去处理application。 bakPath、baseApkDir、myTinkerId三个参数的值在打包的时候有重要作用,这里注意一下就行,后面会有打包发布的操作流程。 4、初始化Bugly+Tinker 方式一:反射MyApplication public class MyApplication extends Application { private static MyApplication appContext; @Override public void onCreate() { super.onCreate(); appContext = this; configTinker(); } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // you must install multiDex whatever tinker is installed! MultiDex.install(getApplication()); // 安装tinker 此接口仅用于反射Application方式接入。 Beta.installTinker(); } public static synchronized MyApplication getApplication() { return appContext; } /** * 初始化Tinker */ private void configTinker(){ //是否开启热更新能力 Beta.enableHotfix = true; //是否开启自动下载补丁 Beta.canAutoDownloadPatch = true; //是否自动合成补丁 Beta.canAutoPatch = true; //是否提示用户重启 Beta.canNotifyUserRestart = false; //补丁回调接口 Beta.betaPatchListener = new BetaPatchListener() { @Override public void onPatchReceived(String s) { LogUtil.e(TAG, "补丁下载地址:" + s); } @Override public void onDownloadReceived(long l, long l1) { LogUtil.e(TAG, String.format(Locale.getDefault(), "%s %d%%", Beta.strNotificationDownloading, (int) (l1 == 0 ? 0 : l * 100 / l1))); } @Override public void onDownloadSuccess(String s) { LogUtil.e(TAG, "补丁下载成功"); } @Override public void onDownloadFailure(String s) { LogUtil.e(TAG, "补丁下载失败"); } @Override public void onApplySuccess(String s) { LogUtil.e(TAG, "补丁应用成功"); } @Override public void onApplyFailure(String s) { LogUtil.e(TAG, "补丁应用失败"); } @Override public void onPatchRollback() { } }; //第二个参数true表示是开发设备,在Bugly的后台发布补丁时,可以选择全部用户还是开发设备 Bugly.setIsDevelopmentDevice(getApplication(), true); // 多渠道需求塞入 // String channel = WalleChannelReader.getChannel(getApplication()); // Bugly.setAppChannel(getApplication(), channel); // 这里实现SDK初始化,appId替换成你的在平台申请的appId isDebug是否是调试模式 Bugly.init(getApplication(), appId, isDebug); } } 复制代码 方式二:不反射,官方建议的方式,该方式会增加接入成本,但有更好的兼容性

1、“tinker-support.gradle”文件中设置“enableProxyApplication = false”;

2、自定义Application,注意,继承TinkerApplication

public class MyApplication extends TinkerApplication { // 此文件只写这段代码,其他代码统一全部放到MyApplicationLike里面去 public MyApplication() { super(ShareConstants.TINKER_ENABLE_ALL, "xx.xx.xx.MyApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false); } } 复制代码

3、自定义ApplicationLike

public class MyApplicationLike extends DefaultApplicationLike { public MyApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) { super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); } @Override public void onCreate() { super.onCreate(); configTinker(); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @Override public void onBaseContextAttached(Context base) { super.onBaseContextAttached(base); // you must install multiDex whatever tinker is installed! MultiDex.install(base); // 安装tinker Beta.installTinker(this); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) { getApplication().registerActivityLifecycleCallbacks(callbacks); } /** * 初始化Tinker */ private void configTinker(){ //是否开启热更新能力 Beta.enableHotfix = true; //是否开启自动下载补丁 Beta.canAutoDownloadPatch = true; //是否自动合成补丁 Beta.canAutoPatch = true; //是否提示用户重启 Beta.canNotifyUserRestart = false; //补丁回调接口 Beta.betaPatchListener = new BetaPatchListener() { @Override public void onPatchReceived(String s) { LogTools.e(TAG, "补丁下载地址:" + s); } @Override public void onDownloadReceived(long l, long l1) { LogTools.e(TAG, String.format(Locale.getDefault(), "%s %d%%", Beta.strNotificationDownloading, (int) (l1 == 0 ? 0 : l * 100 / l1))); } @Override public void onDownloadSuccess(String s) { LogTools.e(TAG, "补丁下载成功"); } @Override public void onDownloadFailure(String s) { LogTools.e(TAG, "补丁下载失败"); } @Override public void onApplySuccess(String s) { LogTools.e(TAG, "补丁应用成功"); } @Override public void onApplyFailure(String s) { LogTools.e(TAG, "补丁应用失败"); } @Override public void onPatchRollback() { } }; //第二个参数true表示是开发设备,在Bugly的后台发布补丁时,可以选择全部用户还是开发设备 Bugly.setIsDevelopmentDevice(getApplication(), true); // 多渠道需求塞入 // String channel = WalleChannelReader.getChannel(getApplication()); // Bugly.setAppChannel(getApplication(), channel); // 这里实现SDK初始化,isDebug:是否是调试模式 Bugly.init(getApplication(), "APP ID", false); } } 复制代码 AndroidManifest文件只管权限就行了,1.3.1版本以上已经兼容了FileProvider,混淆参考官方文档,这里不做赘述。至此,Tinker接入完毕。 发布流程 首先我们需要了解两个名词 基准包:我们发布到线上的apk,用户正常使用的包 补丁包:基于基准包所产生的差异包,修复了基准包里面的bug,使用基准包的用户可以下载补丁包通过打补丁的方式来修复bug 实行热修复

如果发现线上问题,经过评估应该利用热修复(而不是app升级)来解决的,在我们修复bug文件之后,按照如下流程:

1. 将生产包apk(生成包apk和mapping文件必须在打包的时候备份好)放在工程内的下面位置,这个目录地址必须是tinker-support.gradle文件里面的变量bakPath+baseApkDier的目录。

这个目录需要自己新建而不是Tinker自动生成,这里需要注意一下。我一开始以为是Tinker会自动生成,导致我打补丁包的时候一直报错。

image.png

image.png 这是基准包apk和mapping文件存放的完整路径,我们基准包必须放在这个目录下且名字必须是app-release.apk,当然你如果想用别的名字也可以修改baseApk变量的值。mapping同理。

image.png

2. 修改tinkerId作为补丁包的版本号,通常在"patch-" + rootProject.versionName + ".0" 的基础上+1,最终写成: "patch-" + rootProject.versionName + ".1", 如果打了多次基准包,就在后面加数字区分: "patch-" + rootProject.versionName + ".2" 3. 双击下面的命令,执行生成补丁的过程

image.png

4. 如果正常生成补丁,到如下位置去寻找补丁包,名字为:patch_signed_7zip.apk

image.png

5. 上传补丁包到bugly后台(目标版本是versionName+versionCode组合,是Bugly自己给我们的基准包定义的版本,与TinkerId无关):

image.png

多渠道打包 1、采用在Gradle添加productFlavors的方式

具体接入可以直接参考Tinker的文档 但是该方式对打基准包和补丁包,都需要消耗大量的时间

2、采用第三方框架Walle集成多渠道打包的方式(本人采用)

Walle源码地址,里面附接入流程 接入完成之后,我们通过执行命令gradlew clean assembleReleaseChannels进行多渠道打包 打包成功的话会在命令控制台自动输出BUILD SUCCESSFUL,并且在该图片对应目录生成对应的渠道包

image.png 并且会在build/bakApk目录下面生成app-release.apk,这个包就可以作为我们的基准包,我们需要备份好,日后需要打补丁包的时候用的上,利用这个多渠道打包就可以只生成一个补丁包然后对应所有的渠道,使用时比方式一要方便许多。只是增加了接入成本。

疑难杂症解决办法 1、高版本Gradle兼容问题: 我目前用的是3.6.4版本,4.0以上的版本好像有兼容问题,不知道现在修复没,所以说,如果工程有别的工程对Gradle有要求且和Tinker冲突,那就GG了。 复制代码 2、清单文件标签的适配: Android11需要配置配置该标签用于跳转到第三方应用,我们知道gradle版本4.1+是可以适用的,如果低于4.1的话,那么gradle选择的版本应该是4.0.1/3.6.4/3.5.4/3.4.3/3.3.3 复制代码 3、基准包使用和打包补丁包时可能出现的问题: 1、使用的基准包必须是自己打包的release包,而不是tinker在build/bakApk目录下自动生成的包 2、每次打包发布apk得时候,apk和mapping必须备份好,因为不同的版本可能会出现不同的bug, 那么我们修复时用到的基准包也会不一样 3、在打补丁包时候,基准包的路径必须正确,否则会找不到基准包而打包失败 4、必须在app的build.gradle里面配置签名文件,否则打包时会因为找不到签名文件而失败 5、每次修改补丁包必须修改tinkerId,否则会导致打补丁出错 6、同一个基准包下,发布了很多补丁包,建议把老补丁包撤销一下,否则用户可能会出现补丁混乱的问题 复制代码 4、Tinker与viewbinding冲突的问题: 需要将Tinker升级到v1.9.14.6版本且在工程的build.gradle里面加入: classpath('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.14.6') 复制代码 5、兼容google play 参考https://github.com/Tencent/tinker/issues/1314 复制代码 6、打补丁包错误:loader classes are found in old secondary dex.Found classes:XXX 在tinker-support.gradle的tinkerSupport里面加入tinkerEnable = true,ignoreWarning = true 也可以尝试tinkerpatch.gradle配置文件中 在tinkerPatch下面添加 allowLoaderInAnyDex = true removeLoaderForAllDex = true 若还是无法处理,可以参考tinker官网该问题的处理(https://github.com/tencent/tinker/issues/104) 复制代码 7、无法修改AndroidManifest.xml文件 目前Tinker的版本,Android系统10以上不能新增四大组件,低于10的系统可以新增activity。 复制代码


【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3