前言
之前发过一篇文章——Bugly热更新SDK你需要知道的一些事,那是Bugly集成Tinker之后正式发布的第一个版本—1.2.0
,针对我们热更新能力进行的一些说明,经过之后的版本迭代当中也是不断的跟进Tinker版本
并且不断优化和简化开发者的接入,让开发者能够将热更新能力真正代入产品的使用当中。
在开发者使用热更新SDK的过程中我们很多问题的反馈,大部分是对热更新不够了解导致的,这个也正如Tinker作者所说的:
热更新不是请客吃饭。
推荐大家看下:Tinker:技术的初心与坚持
我们也希望开发者在使用我们SDK的时候应该对热更新有一个清晰的认识,那就是:
我们所采用的热更新方案能做什么和不能够做些什么?
实际开发当中我们有哪些东西可能是我们需要修改的,或者说产品上线之后会面临修复的? 我们可以列举一下:
- 类文件
- So库
- 资源
这三个东西Tinker都是完全支持的,类和资源在实际开发中最有可能需要通过热更新来修复,而So库场景可能会相对少一点。
那么Tinker不支持你修改什么?
- AndroidManifest文件
- 新增四大组件
关于Tinker的能力,大家可以移步看下Tinker Wiki。
开发者为什么这么热衷于热更新?
热更新解决了开发者的一个痛点就是程序逻辑出现bug了,导致业务逻辑混乱或者导致程序崩溃,但这些bug不会导致App完全不可用,如果通过发版来解决问题的话周期又太长,那么通过热更新下发补丁能够帮助产品及时修复问题,而不需要等待版本更新。这也是为什么国内稍微大一点的产品都有自己的热更新方案,通过避免频繁发版来保证产品的稳定性。不过热更新能力对于一些开发者来说可能更多是一种事后救火的行为,对产品质量不够信心,才想着有这么一个东西就算出现问题也不需要太大的担心,有这样想法的同学是非常危险的。
开发者最好不要依赖热更新来保证产品质量,建议在产品需要紧急修复问题的情况下才应该考虑热更新。
Bugly跟Tinker有什么不同?
有很多开发者问,Bugly跟Tinker有什么不同,它们之间有什么关系呢?这里正式把这个问题说清楚:
Tinker是一个开源框架,而Bugly热更新SDK是基于这套框架进行的能力封装,提供接口方便开发者能够快速实现热更新能力。而Tinker只是开源了客户端代码,并没有开源补丁管理后台,我们Bugly也提供了补丁管理后台,所以开发者只需要接入我们SDK就无需自己搭建补丁管理后台。所以说Bugly提供的是一整套热更新能力解决方案,而不仅仅是客户端的能力。
这里还需要提及一些技术实现,Tinker补丁的生成是通过Gradle插件来实现的,整个打补丁的过程对开发者来说是透明的,Bugly在Tinker插件基础上开发了TinkerSupport插件来生成符合我们平台规则的补丁,主要就是在TinkerPatch插件生成的补丁插入一个配置文件-YAPATCH.MF。
如何在实际开发中使用热更新能力
很多开发者在使用热更新能力的时候,测试的时候还好,但一到正式使用但时候就有点蒙圈,这也是对热更新不够理解的一种表现,下面笔者针对一些需要开发者去理解的概念。
基线版本vs补丁版本
在使用插件打补丁的时候需要我们去指定基线版本,那么我们需要怎么去理解这个概念呢,简单来说就是你已经上线版本的apk,而这个apk能打补丁的前提是已经集成过热更新SDK。
无论使用什么热更新方案,都是基于某一个版本进行打补丁,而被打补丁的版本就是基线版本。
在使用tinker-support插件进行打补丁我们需要配置以下几个内容:
- 基线版本Apk
- 基线版本的mapping文件
- 基线版本的R文件
最佳实践就是你通过持续集成系统发版前编译生成以上几个文件,然后对代码版本库进行打tag,如果线上版本出现问题就通过tag来拉取代码,将持续集成系统生成对基线版本对应的三个文件下载放到工程的某个目录,然后对插件进行参数配置,接着就是通过插件进行打补丁包,最后将补丁包上传到Bugly平台进行下发。
补丁版本就没啥好说的,就是针对基线版本修复问题到版本,它也是需要走编译打包流程的,因为基线版本需要跟补丁版本进行比较才能差分生成补丁包。
关于tinker-support打补丁配置如下图所示:
tinkerId到底有什么作用,每次打补丁都需要修改?
前面说到打补丁的时候会在补丁基础上插入一个配置文件— YAPATCH.MF,里面有两个配置参数:
From:表示你是基于某个基线版本打的补丁包
To: 表示的是你当前打补丁的版本
为什么要这么设计? 主要是因为每次编译会通过插件在AndroidManifest文件插入TINKER_ID的meta-data,用来唯一标识当前版本,所以你每次打包的时候都是需要修改tinkerId,我们在运行基线版本时会将tinkerId与当前版本号进行一个关联一起上报到我们后台,打补丁的时候插件会读取基线版本的tinkerId写到From字段,当前版本写到To字段,上传补丁到管理后台的时候前端会通过解析配置文件读取From字段,然后去后台匹配tinkerId对应的版本,这也是为什么如果你上传补丁From字段的tinkerId没有上报过联网就匹配不到具体版本。
为什么需要重启补丁才能够生效?
有很多同学对Tinker热更新方案为什么要重启两次才能够生效,这里需要大家先了解一下Tinker关于合成补丁的原理,Tinker采用的是ClassLoader方案,这种方案具有具有较高的稳定性和兼容性。我们先看下面这张图:
可以看到补丁包是基线版本和补丁版本差分的结果,但合成补丁包的时候跟Qzone方案是有区别的,tinker是采用全量合成dex来实现热更新,前者会将补丁包单独做成一个dex插入到dexElements最前面,而后者是全量合成dex插入到dexElements最前面。熟悉ClassLoader机制的同学应该知道,加载类的时候会去遍历dex文件,并且会优先加载排在前面的dex,应用启动的时候加载的就是已经修复问题的dex文件。这里有个潜规则就是:
如果已经被加载过的类,就不会重复被加载,这就解释了为什么要杀掉进程重启app才能够修复问题。
那为什么Bugly热更新有时候需要重启两次才能够生效呢,因为这里涉及到请求策略,下载补丁的过程,如果应用启动的时候没有检测到补丁存在就不会下载补丁,我们并不会实时去检测补丁的存在,所以只有重启的时候才会去检查补丁,检查到补丁就会下载并自动合成,合成补丁之后还需要再次重启才会生效,这也就是为什么需要重启两次。
渠道包怎么使用热更新?
多渠道热更新是一个坑,很多同学在接入Bugly热更新之后就会问,怎么支持到多渠道?这里有两种情况,一种是通过productFlavors
来打渠道包,这种方式没有办法做到一个补丁修复所有渠道,因为它打出来的包dex的crc值都是不一致的,所以只能一个渠道一个补丁对应修复问题,但这个对于需要打很多渠道包的app开发者来说就是一个灾难,因为需要忍受漫长的打包过程还要逐个上传补丁包。这个肯定是不符合我们的需求的,所以我们推荐通过多渠道工具来打渠道包,具体参考笔者之前的一篇文章:Bugly多渠道热更新解决方案。
这里要提一点,要基于tinker插件打出来的基准包来打渠道。
加固包怎么使用热更新?
加固可以说是一个巨坑,tinker 1.7.5版本及以前是支持回退QZone方案来支持加固,但因为市面上没有统一的加固方案会引入一些更加严重的问题,所以后面tinker官方将加固支持去掉了,但很多产品都接入了加固,如果用了加固就不能再使用热更新,所以后面Tinker重新对市面上的加固方案重新做了兼容,但需要在1.7.8版本以上才支持。
实际应用你需要做以下操作:
- 开启加固模式,将isProtectedApp设置为true
- 将打出来的基线版本上传到加固平台进行加固并重签名
- 打补丁是基于非加固的基线版本进行的
用一张图解释Bugly热更新
完整测试流程
- 打基准包安装并
上报联网
(注:填写唯一的tinkerId) - 对基准包的bug修复(可以是Java代码变更,资源的变更)
- 修改基准包路径、修改补丁包tinkerId、mapping文件路径(如果开启了混淆需要配置)、resId文件路径
- 执行buildTinkerPatchRelease打Release版本补丁包
- 选择app/build/outputs/patch目录下的补丁包并上传(注:不要选择tinkerPatch目录下的补丁包,不然上传会有问题)
- 编辑下发补丁规则,点击立即下发
- 杀死进程并重启基准包,请求补丁策略(SDK会自动下载补丁并合成)
- 再次重启基准包,检验补丁应用结果
- 查看页面,查看激活数据的变化
热更新接入教程
热更新本不是一件很容易的事情,尽管我们尽量把文档写得比较详细,开发者在接入过程中总会遇到一些问题,所以我们录制了一套关于Bugly Android热更新接入的完整教程,通过实操和讲解来帮助开发者能够减少出错的概率,如果大家对于文档不清楚的地方或者接入过程中遇到问题,可以看下视频哦,对应的课程链接如下:
最后
Bugly热更新自上线以来,花了很大部分时间去优化产品和教育用户,我们的工作已经不仅仅只是做SDK这么简单了,作为一个SDK产品可能仅仅只是及格的水平,我们还有很多需要做的事情,比如提供更方便开发者验证热更新能力的功能和实践和支持更多维度的条件下发,再或者更加极端会将tinker从SDK中进行剥离,仅提供补丁管理的能力,只提供回调接口让开发者自己去更新和集成tinker版本,给开发者更大的自由性,至于后面以什么形式给到开发者我们也会继续考量,感谢广大用户对我们Bugly的支持,也希望大家能够给予我们更多的反馈,让我们做得更好,谢谢大家。