You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{
"react-native/business-a.unbundle": "MD5 for business-a.unbundle",
"react-native/business-b.unbundle": "MD5 for business-b.unbundle",
"react-native/ReactNativeResource.bundle/common/modify.png": "MD5 for modify.png"
}
背景
小鹏汽车 App ReactNative (以下简称RN) 热更新的实践方式,可能与其他 App 不大一样。当前虽然已经做了 RN 业务的拆包处理,但实际 RN 在 Jenkins 的构建产物是一个压缩文件,该文件在构建 App 的时候通过脚本下载和解压,最终打进 App 里面,作为当前版本的原始基准包。当时我们在设计热更新体系的时候,没有对 RN 构建产物做过多修改,最终每次热更活动下发的,都是这个完整的压缩文件。在热更完毕之后,RN Bundle 管理类将不再从 Asset 中加载拆分后的 Bundle 文件,而是加载沙盒中热更目录的文件,来达到最终的热更效果。
现状
与进入 RN 页面时动态下发 Bundle 的方式不同,当前 App 只会在启动的时候触发检查更新,更新完毕后在下次启动时生效。当用户进入某个 RN 页面时,将直接执行沙盒中对应的业务 Bundle 文件。这种下发和加载方式,资源版本管理会比较简单,但也会有以下问题:
RN 业务独立打包以及相应版本管理,后续将由精卫平台来承载。慢慢增长的资源包大小,会是目前重点关注的问题。减小资源包的大小,对降低用户流量使用、公司带宽费用都会有明显的提升效果;另外如果大幅降低下载耗时,新活动也将更快地触达用户,产生相应的业务价值。
预研
CodePush 本身支持对图片资源的增量更新,但不适用于 Bundle 文件。如果想要实现增量更新,且是单 Bundle 的 RN 工程,可以直接使用 RN 中文网的 react-native-pushy,后端服务也是在国内,可以不用担心网络问题。
由于我们的 App 是多 Bundle 的应用场景,以上两个库不大适用,因此需要定制一套符合自身情况的增量更新方案。
其实在社区可以搜到不少增量更新相关的方案,Bsdp 就是其中之一,在参考 Shopee 和 携程 分享的技术方案,并进行一番预研和本地验证后,我们选择了 Bsdp 算法,并按实际情况做了一些调整(方案来自 Shopee 团队,下文也会引述一些他们博文的内容,在此表示感谢)。
Bsdp(BSDiff & BSPatch)算法
引自 Shopee 技术团队博文:
流程示意图:
关于算法更多内容,可以参考原文。
尝试
ZIP 的差分
由于当前 RN 构建产物是一个压缩文件,因此针对新旧压缩包进行差分,是最容易想到的方法。基于这个想法,我们在本地终端工具进行相应验证。以线上某次热更活动为例,下载了
4.8.0
和4.8.0-patch1
两个包(当前 RN 资源包的大小已经达到了30+m
,实际里面大部分是图片资源,这也是我们后续优化的重点方向,此次主要讨论增量更新方案),最终 Diff 出来的文件是1.5m
左右,这已经远小于原包大小。对我们来说,这算是已经迈进了一大步,有信心认为自己正在往正确的方向前进。基准包获取
要进行差分,必将需要拿到新旧包,由于 App 构建时,已经解压了 RN 资源包,图片资源和各个业务 Bundle 都已经独立打包进 App。因此,如果要获取旧包(以下称基准包),按现在情况有两种方式:
内置新的压缩包会增加 App Size,评估之后不在考虑范围之内;而拷贝 Asset 目标文件,再压缩成基准包,貌似是可行的方案。
但是很遗憾,我们也遇到了与 Shopee 团队一样的问题,就是:ZIP 文件在不同端不兼容。
在构建 RN 产物的时候,cli 工具在压缩时使用了 jszip,而在 iOS 端验证的时候,使用的是 SSZipArchive,验证下来发现两者压缩后的基准包是不一样的,MD5值已经发生了变更,这样就无法用来进行差分。在遇到这个问题的时候,一开始考虑在 App 引入 NodeJS 的运行环境(nodejs-mobile),再用相同的压缩库进行压缩。看似可行,但考虑到引入的包大小、功能冗余、适配实现等问题,后来也放弃了这个想法。
FolderPatch
在遇到压缩文件不兼容问题后,又重新梳理了 Shopee 和携程两个团队分享的方案,并在两篇博文中,提取到了两个比较关键的信息:
以上文对
4.8.0
和4.8.0-patch1
的测试为例,原来针对压缩文件的差分,产物大小1.5m
左右;最新测试是先生成新包每个文件的差分文件,再一并压缩,最终不到100k
!这个提升效果是再次令人惊喜的,在一番新的梳理之后,我们决定采用与 Shopee 团队一致的技术方案,并稍作调整,定制出适合自己的 FolderPatch。方案
引述自 Shopee 团队博文:
设计
在原下发逻辑中,App 侧下载拿到的签名包如下所示:
为了能记录目录和文件的新增、删除等操作,拟定引入 FolderDiff.json;为了能校验新旧文件的 Patch 结果是否正确,引入 ManifestHash.json 文件,记录新文件的 MD5 值。
其中 FolderDiff.json 的示例内容如下:
由于
.codepushrelease
文件每次都会变更,并且比较小,因此我们选择直接忽略,当做新增文件直接拷贝。再看看 ManifestHash.json 的示例内容(只记录变更文件的 MD5):
而原来的
react-native
目录,将只会保留新增文件和变更文件的差分产物,如下所示:基准包获取
在设计以上规则之后,依然需要面对基准包获取问题,主要是涉及下文所述两个方面。
Asset 基准包
在首次热更活动下发时,目标基准包将会是 Asset 版本。在 Patch 结束后,沙盒中的热更目录应该包含完整的 RN 资源包内容,但下发的差分包中只会包含新增文件和变更文件,其他文件该如何获取呢?
最终还是从 ManifestHash.json 中入手。在后端进行 Diff 的时候,可以知道当前基准包中的所有文件,因此可以在这个过程中,把必要的文件记录到 ManifestHash.json 中。考虑到图片目录 ReactNativeResource.bundle 必定存在,App 侧可以做兜底拷贝处理,因此重点是记录其他业务 Bundle 文件集合。在完成所有文件的记录后,如果当前文件没有变更,则对应的 MD5 置为 0;否则记录变更后的 MD5,App 侧按需进行 Patch。
调整后的 ManifestHash.json 内容示例:
这样 App 就可以根据文件内容,从 Asset 中拷贝目标文件到沙盒目录中,并按需进行 Patch。
Jenkins 基准包
RN 的构建产物,最终都会上传到 OSS 服务,在获取非首次热更活动的基准包问题上,首先考虑的是后端按规则拼接包下载链接,然后下载作为基准包进行 Diff。方案最开始阶段是这么设计的,后来与测试同学沟通后,发现不一定适用。主要是当前的集成阶段,在 Jenkins 上构建时,一般会勾选一个选项,用时间戳为当前包打上 Tag。这样的话,后端是无法简单的通过字符串拼接,来获得基准包下载链接的,因为时间戳不固定。
带 Tag 的集成包,方便我们在集成阶段进行问题的定位,因此有必要进行保留。为了兼容这种情况,构造基准包下载链接的任务,就应该由 App 提交给后端。但 App 侧应该如何获取这个链接呢?
解铃还须系铃人
,生成 OSS 保存链接和打 Tag 的操作都来自 Jenkins,可以在这个过程中,把按需带时间戳的 OSS 下载链接,按一定路径,保存到 RN 构建产物中,然后 App 在发起检查更新请求时,携带该链接作为参数,给到后端作为基准包的下载链接,达到最终目的。调整后的构建产物将包含
DownloadUrlFromJenkins
文件,其记录当前 RN 资源包的下载链接:为了在每次检查更新时,都能拿到当前基准包的下载链接,后端在下发差分包时,都应把最新包的
DownloadUrlFromJenkins
一起下发。考虑到该文件大小问题,将与上文提到.codepushrelease
文件一样处理逻辑,作为新增文件,最终 FolderDiff.json 内容示例:下发的差分包中,也将包含该文件:
至此,完成整个 FolderPatch 的规范设计。
Patch
App 侧的 Patch 操作,下载完差分包之后,主要按以下步骤进行:
bspatch_workspace
;如果为沙盒目录版本,则直接拷贝上次热更的目录到bspatch_workspace
FolderDiff#addFolders
,创建新目录FolderDiff#addFiles
,进行新增文件的拷贝,如有同名文件则直接覆盖FolderDiff#deleteFolders
、FolderDiff#deleteFiles
进行目录和文件的删除操作bspatch_workspace
到目标热更目录,后续流程与全量更新并无二异以上任一流程,只要抛出 Error,都将认为 Patch 失败,直接中断流程并退出。
其他
除了关键的核心功能,还有其他一些小细节:
最后
本次增量更新技术方案的设计和落地,是站在社区同行肩膀上,并结合自身情况,加以试错和不断改进的结果。本次分享,除了梳理方案从预研、设计到落地的过程,也是想传达一个观点:适合自己的,才是最好的。
希望其他团队设计自己的增量更新方案时,本篇文章能有所帮助。
参考资料
The text was updated successfully, but these errors were encountered: