01
简述
随着时代的发展,生活节奏的加快,人们对于互联网的需求日益加深,再加上IT技术的日新月异,导致应用的更新频率越来越快。传统的安装包覆盖安装模式似乎不太能跟得上节奏了,因此热更新应运而生。
热更新,是一种快速、低成本修复产品软件版本缺陷的方式。相较于“冷”更新而言,减少了例如应用平台审核、下载安装包、覆盖安装等很多繁杂而耗时的步骤,拥有“发布即生效“的优势。
02
背景
APP发版从来不是一个简单的事情,打包 、测试、提交应用市场、提示升级、用户下载、覆盖安装等。除了忙碌以外,重点是还会有原来的版本遗留,无论怎么提示都有人不同意,不愿意升级,发强制升级体验又太糟糕,甚至面临卸载的风险。尤其对于一个不得不处理的线上bug,来这么一套发版流程从各个角度来说,是极其痛苦的。
此时就迫切希望有一个类似于前端网⻚更新的机制,能够动态的、迅速的、甚至无感地改变移动端代码,即热更新。
但当团队想要使用现有热更新框架的时候,又面临了接入难、操作烦、收费高、不可控等问题,尤其是当业务线很多的时候。综上,我们考虑探索、实现自己的热更新系统。
03
目标
简易:降低接入成本,使开发者能够迅速接入。降低学习成本,使框架所有角色轻松上手。
便捷:使用方便,热更新流程中测试、发版操作方便。
快速:突出热更新的优势,使得更新版本发出后,尽量快反应到用户侧。
安全:提供灰度下发和撤回机制,避免线上运营造成不必要的问题。
稳定:补丁到达率、安装成功率保持高水平,框架稳定可维护。
04
技术选型
市面上成熟的热更新技术很多,腾讯、阿里、美团等诸多厂商也在热更新技术上作出了一些实践。在综合了功能完备性、简易性、性能、稳定性多方面因素后,最终选择了Tinker作为热更新系统的底层框架。
支撑/方案 | Tinker | QZone | AndFix | Robust |
即时生效 | No | No | Yes | Yes |
类替换 | Yes | Yes | No | No |
So替换 | Yes | No | No | No |
资源替换 | Yes | Yes | No | No |
复杂度 | 较低 | 较低 | 复杂 | 复杂 |
性能损耗 | 较小 | 较大 | 较小 | 较小 |
补丁包大小 | 较小 | 较大 | 一般 | 一般 |
成功率 | 较高 | 较高 | 一般 | 最高 |
以下是对主流热修复技术的具体分析。
4.1 阿里AndFix
原理
-
直接在native层进行方法的结构体信息对换,从而实现方法的新旧替换。
优点
-
补丁实时生效,不需要重新启动。
缺点
-
存在稳定及兼容性问题。ArtMethod的结构基本参考Google开源的代码,各大厂商的ROM都可能有所改动,可能导致结构不一致,修复失败。
-
无法增加变量及类,只能修复方法级别的Bug,无法做到新功能的发布。
4.2 美团Robust
原理
-
打基础包时插桩,在每个方法前插入一段类型为 ChangeQuickRedirect 静态变量的逻辑。加载补丁时,从补丁包中读取要替换的类及具体替换的方法实现,新建ClassLoader加载补丁dex。当changeQuickRedirect不为null时,替换掉之前老的逻辑,达到fix的目的。
优点
-
补丁实时生效,不需要重新启动
-
高稳定性,修复成功率高达99.9%
-
支持增加方法和类
缺点
-
不支持so和资源的替换
-
会增大apk的体积,平均一个函数会比原来增加17.47个字节。
4.3 Qzone超级补丁
原理
-
class加载原理:dex文件转换成dexFile对象,存入Element[]数组,findclass顺序遍历Element数组获取DexFile,然后执行DexFile的findclass。
-
Hook了ClassLoader.pathList.dexElements[],将补丁的dex插入到数组的最前端,所以会优先查找到修复的类,从而达到修复的效果。
优点
-
代码是非侵入式的,对apk体积影响不大。
缺点
-
需要下次启动才修复。
-
性能损耗大,为了避免类被加上CLASS_ISPREVERIFIED,使用插桩,单独放一个帮助类在独立的dex中让其他类调用。
4.4 微信Tinker
原理
-
采用dex替换的方式,避免了dex插桩带来的性能损耗。原理是提供dex差量包,整体替换dex的方案。差量的方式给出patch.dex,然后将patch.dex与应用的classes.dex合并成一个完整的dex,完整dex加载得到dexFile对象作为参数构建一个Element对象然后整体替换掉旧的dex-Elements数组。
-
Tinker自研了DexDiff/DexMerge算法,对于dex文件的处理经验老道。Tinker还支持资源和So包的更新,So补丁包使用BsDiff来生成,资源补丁包直接使用文件md5对比来生成,针对资源比较大的(默认大于100KB属于大文件)会使用BsDiff来对文件生成差量补丁。
优点
-
兼容性高、补丁小。
-
开发透明,代码非侵入式。
-
支持so文件、资源文件、类的增加和删除。
缺点
-
需要下次启动才修复。
05
调研
内核为Tinker的热更新系统,市面上已经存在,那就是Bugly(原TinkerPatch)。但经过调研后发现其存在集成难、操作难、维护难等许多硬伤,并不适合进行大规模使用;再加上Tinker代码完全开源,我们完全具备自建热更新系统的动机、条件和能力。
06
方案
-
采用以Tinker为核心,搭建以Grove、Jenkins等现有打包、发版工具为基础的一体化系统级方案——HotFix热更新系统。
-
类似于APP发版,系统分为四大模块,对应了配置、打包、发布、生效。
6.1 HotFixPatch——补丁生成插件
-
实现tinker生成补丁包功能。
-
根据实际情况固定配置,并使tinkerId自增。
-
打包自动替换application,接入无需改造application。
6.2 Jenkins——项目构建工具
-
从grove获取线上包并放入约定位置。
-
调用Task-HotFixPatch。
-
使补丁产出用户清晰可见。
6.3 Grove——版本管理平台
-
新增补丁列表及管理。
-
支持补丁上传并可拆包识别版本等信息,确保补丁正确。
-
提供补丁下发、灰度机制。
-
提供jenkins获取基准包。
6.4 HotFixLoader——补丁合成库
-
实现tinker合并补丁功能。
-
完成可配置的补丁请求策略。
-
实现反射application生命周期各方法,避免改造成本。
-
实现重启策略:强、弱、静。
07
使用流程
7.1 接入热更新插件
-
classpath 'com.sfic.plugin:HotfixPlugin:1.0.2-SNAPSHOT'。
-
apply plugin: 'com.sfic.plugin.hotfix'。
-
gradle.properties中配置 PLATFORM。
7.2 接入或升级upgrader
-
implementation 'com.sfic.upgrader:upgrader:0.1.14-SNAPSHOT'。
-
SfUpgrader.init() 中开关 hotFixEnabled = true。
-
根据需要设置灰度id,默认为cuId。SfUpgrader.updateHotFixFlag(String)。
-
配置混淆 -keep class com.sfic.lib.supportx.hotfix.*{;} 。
7.3 配置jenkins
-
jenkins文件夹下放入hot_fix.sh。
-
jenkins中加入buildType,格式 tinkerPatch+flavor+variants,例如 tinkerPatchYizhanRelease 。
-
加入shell构建
7.4 使用操作
-
Jenkins打包 —— 使用TinkerPatch相关task生成补丁包,使用完整包测试功能。
-
上传Grove —— 功能测试结束后,上传补丁包至grove,并进行相关配置(可配置灰度测试)。
-
版本管理 —— 灰度测试结束后,在补丁管理页面进行全量发布及其他补丁管理。
7.5 后期维护
-
数据监听 —— 上线后一段时间内监听补丁下发数、到达数。在不同下发策略(灰度)、生效策略(强弱静)下,相对应地计算或监听补丁下发率和到达率,以更好地维护项目及热更新策略。
-
异常处理 —— 热修复完成后,如仍留有或新增异常,应采取相应紧急策略:新增补丁包覆盖升级热修复版本、撤回补丁包至基础版本等。
08
结语
HotFix热更新系统,对于NA团队来说,解决了Android端版本紧急修复和小版本迭代的痛点。使得问题被迅速解决,功能被迅速生效。其特点可以使用在任何需要满足急切、强制或无感的更新场景下,具体可体现在bug修复及小版本更新等。