Android|Android 集成Tinker热更新问题及脱坑

因为业务的需求不得不集成热更新,小小心得,先记下:
Android|Android 集成Tinker热更新问题及脱坑
文章图片

Android|Android 集成Tinker热更新问题及脱坑
文章图片

现在开始进入正题,集成热更新-带你跨过我所遇到过的坑:
因为刚开始的时候我的Android studio版本号是3.0.1,所以在这个过程中遇到了很多的问题,在查询了大神们的文章之后才一步步解决,文尾我会贴出大佬们的 文章链接 如有不懂请注意查看,好了废话就不说了,建议studio版本降低到2.3.3再集成,高手勿扰,可以在这里修改studio版本号,在项目工程project -> build.gradle -> dependencies:直接手动修改为2.3.3,然后sync now就可以:
Android|Android 集成Tinker热更新问题及脱坑
文章图片

然后在项目工程project -> build.gradle -> dependencies中添加远程依赖:
Android|Android 集成Tinker热更新问题及脱坑
文章图片

dependencies { classpath 'com.android.tools.build:gradle:2.3.3'// NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files//热更新 // TinkerPatch 插件 classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.1.8" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }

【Android|Android 集成Tinker热更新问题及脱坑】集成Tinker的SDK-在app文件下的build.gradle:
注:下面两条依赖的版本大家最好按照我目前发的为基准,其他低版本多少都会有些bug,如anno1.8.0 + tinpatch sdk 1.1.8巨坑bug(不报错,只是打补丁失败)
dependencies { ... // 若使用annotation需要单独引用,对于tinker的其他库都无需再引用 provided("com.tinkerpatch.tinker:tinker-android-anno:1.9.8") compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:1.2.8") }

接下来创建一个配置文件,在app下和build.gradle同级下创建一个文件,用以后面添加配置文件:
Android|Android 集成Tinker热更新问题及脱坑
文章图片

然后我们在app -> build.gradle里将其引入
apply from: 'tinkerpatch.gradle'

Android|Android 集成Tinker热更新问题及脱坑
文章图片

添加配置文件:
注:这里的所有参数请全部粘贴到创建的文件下:
1)baseInfo和variantName参数先按照这个放着暂时不管
2)appKey请自行登录tinker官网登录并创建应用获取即可 To:http://www.tinkerpatch.com/
Android|Android 集成Tinker热更新问题及脱坑
文章图片

3)appVersion版本号一般对应你的versionName就行了(其实可以这么理解,appVersion和基包的版本号保持一致即可,如果你手机安装的基包版本只要修改那么这个appVersion版本号才跟着修改,其他情况这个appVersion不需要修改)
剩余参数暂时不必理会,如想深入了解 还请自行查阅..:
apply plugin: 'tinkerpatch-support' /** * TODO: 请按自己的需求修改为适应自己工程的参数 */ //基包路径 def bakPath = file("${buildDir}/bakApk/") //基包文件夹名(打补丁包的时候,需要修改) def baseInfo = "app-1.0.0-0705-10-01-29" //版本名称 def variantName = "debug" /** * 对于插件各参数的详细解析请参考 * */ tinkerpatchSupport { //可以在debug的时候关闭 tinkerPatch tinkerEnable = true //是否使用一键接入功能 默认为false是否反射 Application 实现一键接入; // 一般来说,接入 Tinker 我们需要改造我们的 Application, 若这里为 true, 即我们无需对应用做任何改造即可接入。 reflectApplication = true //将每次编译产生的 apk/mapping.txt/R.txt 归档存储的位置 autoBackupApkPath = "${bakPath}" appKey = "需要修改成你的 你的!"// 注意!!!需要修改成你的appkey /** 注意: 若发布新的全量包, appVersion一定要更新,你也可以这么理解,若发布新的基包,appVersion一定要更新 **/ appVersion = "1.0.0" def pathPrefix = "${bakPath}/${baseInfo}/${variantName}/" def name = "${project.name}-${variantName}" /** * 基准包的文件路径, 对应 tinker 插件中的 oldApk 参数; 编译补丁包时, * 必需指定基准版本的 apk,默认值为空,则表示不是进行补丁包的编译 */ baseApkFile = "${pathPrefix}/${name}.apk" /** * 基准包的 Proguard mapping.txt 文件路径, 对应 tinker 插件 applyMapping 参数;在编译新的 apk 时候, * 我们希望通过保持基准 apk 的 proguard 混淆方式, * 从而减少补丁包的大小。这是强烈推荐的,编译补丁包时,我们推荐输入基准 apk 生成的 mapping.txt 文件。 */ baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt" /** * 基准包的资源 R.txt 文件路径, 对应 tinker 插件 applyResourceMapping 参数;在编译新的apk时候, * 我们希望通基准 apk 的 R.txt 文件来保持 Resource Id 的分配,这样不仅可以减少补丁包的大小, * 同时也避免由于 Resource Id 改变导致 remote view 异常 */ 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//是否需要签名,打正式包如果这里是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 } }

给大家上个大致的参数图吧:
Android|Android 集成Tinker热更新问题及脱坑
文章图片

上述步骤 配置完之后 sync编译即可
初始化TinkerPatch SDK
官方给了我们两种方式来初始化TinkerPatch SDK,第一种是reflectApplication = true 的情况,另一种reflectApplication = false的情况,今天我们说reflectApplication = true这种情况,另一种情况,大家可以到官网中看看。简单来说一下这两种情况的区别啊,当reflectApplication = true这种情况是不需要更改我们项目的Application类,而reflectApplication = false的情况是需要改动Application这个类。
创建tinkerApplication类,进行相关的配置,代码如下:
public class tinkerApplication extends Application { private ApplicationLike tinkerApplicationLike; @Override public void onCreate() { super.onCreate(); if (BuildConfig.TINKER_ENABLE) { // 我们可以从这里获得Tinker加载过程的信息 tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike(); // 初始化TinkerPatch SDK, 更多配置可参照API章节中的,初始化SDK TinkerPatch.init(tinkerApplicationLike) .reflectPatchLibrary() .setPatchRollbackOnScreenOff(true) .setPatchRestartOnSrceenOff(true); // 每隔3个小时去访问后台时候有更新,通过handler实现轮训的效果 new FetchPatchHandler().fetchPatchWithInterval(3); } } }

再创建一个FetchPtachHnalder类用来轮训判断
public class FetchPatchHandler extends Handler { public static final long HOUR_INTERVAL = 3600 * 1000; private long checkInterval; /** * 通过handler, 达到按照时间间隔轮训的效果 */ public void fetchPatchWithInterval(int hour) { //设置TinkerPatch的时间间隔 TinkerPatch.with().setFetchPatchIntervalByHours(hour); checkInterval = hour * HOUR_INTERVAL; //立刻尝试去访问,检查是否有更新 sendEmptyMessage(0); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); //这里使用false即可 TinkerPatch.with().fetchPatchUpdate(false); //每隔一段时间都去访问后台, 增加10分钟的buffer时间 sendEmptyMessageDelayed(0, checkInterval + 10 * 60 * 1000); } }

最后将AndroidManifest.xml中的添加上相应的网络和SD的权限,还要在application中加上
android:name=".utils.tinkerApplication"

Android|Android 集成Tinker热更新问题及脱坑
文章图片

到现在tinker基本上已经集成完了,接下来我们在Main的xml里随便写个view以便区分新旧包。

写完之后在AS面板里找到Gradle点击build下的assembleDebug进行编译,完成之后在工程左侧的build查看(它会根据编译时间生成一个时间命名的文件夹且内含一个debug包):
Android|Android 集成Tinker热更新问题及脱坑
文章图片
Android|Android 集成Tinker热更新问题及脱坑
文章图片

生成之后,我们将此debug包安装到手机上,运行效果为我们刚才在xml里写的旧布局:
Android|Android 集成Tinker热更新问题及脱坑
文章图片

接下来,我们打补丁包(模拟修复bug)..
首先到tinkerpatch.gradle里更改咱们先前介绍的两个参数:
baseInfo:修改为上面生成的对应文件夹名(请修改为自己的,修改为刚刚生成app -> build -> backApk下的时间目录)
variantName:因为打的debug包,所以传入debug即可
//基包路径 def bakPath = file("${buildDir}/bakApk/") //基包文件夹名(打补丁包的时候,需要修改) def baseInfo = "app-1.0.0-0228-18-00-28" //版本名称 def variantName = "debug"

最后,我们在xml里做些更改,之后打出差异包补丁,继续找到Gradle下的tinker目录:
Android|Android 集成Tinker热更新问题及脱坑
文章图片

编译完成之后,请到工程目录下app->outputs->查看生成的文件夹 tinkerPatch:
Android|Android 集成Tinker热更新问题及脱坑
文章图片

接下来,我们将图中选中的patch_signed_7zip.apk上传至tinker官网(发布补丁):
Android|Android 集成Tinker热更新问题及脱坑
文章图片

最后,我们拿起手机,后台结束应用之后,重新打开即可看到补丁融合成功:
Android|Android 集成Tinker热更新问题及脱坑
文章图片

到tinker官网,我们可以查看补丁下发进度及当前成功率,附图,因为我之前发布了很多原谅我的图显示的:
Android|Android 集成Tinker热更新问题及脱坑
文章图片

这个成功率大概能达到42%左右
Android|Android 集成Tinker热更新问题及脱坑
文章图片

可能会遇到的问题:当你打完补丁包的时候,使用开发者预览模式来发包的话,如果这个开发者预览模式选择打开,发现可以成功,当开发者模式关闭的时候发现更新失败了,应该考虑这个问题可能跟成功率有关也有可能是版本号的问题(个人之后使用正式包来测的):
Android|Android 集成Tinker热更新问题及脱坑
文章图片

依赖的版本号在1.9.0以下的是不支持Android版本号8.0以上的手机的:
最后吧大佬的账号贴这里:https://blog.csdn.net/qq_37199105/article/details/80923073
如果在集成中遇到了别的错误,或者集成完成之后想要Run你原来的项目的话发现报错了,可以参考这位仁兄的博客,这里我就不多说了:
https://blog.csdn.net/qq1221jyj/article/details/73743612
喷子请绕路,谢谢

    推荐阅读