[关闭]
@garygchai 2016-06-06T06:51:39.000000Z 字数 5633 阅读 3343

reactnative热更新方案设计

reactnative


Created with Raphaël 2.1.2繁星RN热更新流程图ISO codeISO codeAndroid codeAndroid codeRN codeRN codeBundle ToolBundle ToolBundle ServerBundle ServerCDNCDN客户端客户端集成集成版本获取版本获取发布至发版机更新版本配置文件上传新版本zip包checkUpdate下载替换

前言

我们喜欢reactnative的原因除了它可以让开发人员使用javascript来开发native程序之外,还有它的热更新机制,这样我们可以摆脱每次改动都要发布到商城让用户下载的痛苦。在reactnative开发阶段,我们可以通过http://localhost:8081/index.ios.bundle?platform=ios&dev=true&hot=true
让reactnative下载jsbundle文件实现热更新,但是线上环境我们不会这样做,不然就没有这篇文章了,原因是这样太慢了,reactnative在下载完jsbundle文件到解析为native应用显示的时候页面都是空白的,所以我们都会选择将jsbundle文件打离线包存放在本地,让native去读取,这样可以节省网络下载jsbundle文件的时间。

  1. $ react-native bundle --entry-file ./index.ios.js --bundle-output ./bundle/ios/index.ios.jsbundle --assets-dest ./bundle/ios --platform ios --dev false

通常我们通过reactnative提供的打包脚本可以生成相应的jsbundle文件和资源目录:
bundle目录
这里生成的bundle资源文件就是我们要离线到native本地的,所以我们的热更新系统就是基于这个这样的方式进行开发的。

打包

项目目录结构
执行reactnative的项目bin目录下面的bundle.js文件可以在bundle目录生成最新的jsbundle文件以及所需的静态资源文件,并且上传到升级服务器。用法如下:
node bin/bundle --platform ios --bundle-output ./bundle
--platform--bundle-output是可选参数,如果指定--platform,则只打包指定平台的文件,如果指定--bundle-output,则会将产生的bundle文件同步到指定的目录。用法举例:
bundle命令
图中的/Users/gaochanghai/fanxing.git/rnbundle.fanxing.com/bundleSrc可以是本地目录,也可以是升级服务器的远程目录。

获取app版本

执行打包命令的时候,上面node bundle程序,会根据指定平台去读取app当前的版本号,ios主要依赖info.plist文件配置的<key>CFBundleShortVersionString</key><string>2.9.5</string>,通过正则表达式:/<key>CFBundleShortVersionString<\/key>\s*<string>(.+)<\/string>/i匹配到版本号;android依赖目录下的bundle.grade文件通过versionCode配置的版本号获取到app的版本,正则表达式:/versionCode\s*=\s*(\d+)/i
获取到的版本号会写入version.txt文件,上传至升级服务器;

升级服务器目录结构

升级服务器目录结构

监听打包

升级服务器会实时监听bundleSrc目录,如果监听到变化,会执行node bin/bundlenode bin/patch命令,用于生成版本包和差异包。
实时监听

同步至CDN

监听到变化打出来的bundle全量包或者增量包我们会同步至CDN以供用户下载,否则下载包的流量会对服务器产生不小的流量压力。目前先同步至图片服务器:

  1. echo "同步 bundle 目录到 10.16.6.89,请等待..."
  2. rsync -avuc -e 'ssh -p 32200 -o StrictHostKeyChecking=no' public/bundle/ www@10.16.6.89:/data1/htdocs/imagesrc.fanxing.com/fxrnbundle/bundle/ 2>/dev/null
  1. echo "同步 patch 目录到 10.16.6.89,请等待..."
  2. rsync -avuc -e 'ssh -p 32200 -o StrictHostKeyChecking=no' public/patch/ www@10.16.6.89:/data1/htdocs/imagesrc.fanxing.com/fxrnbundle/patch/ 2>/dev/null

版本控制

升级服务器目录下会有一个update.config.json文件用于ios和android两个平台bundle文件的版本管理和维护:

  1. {
  2. "ios": [{
  3. "bundleV": "0.0.1",
  4. "appV": "2.9.5",
  5. "date": "2016-05-20"
  6. }],
  7. "android": [{
  8. "bundleV": "0.0.1",
  9. "appV": "2.9.5",
  10. "date": "2016-05-20"
  11. }]
  12. }

每一次版本改动都会更新update.config.json文件,bundleVappV分别表示当前bundle包的版本及其所依赖的最小app版本。假设有一次ios文件改动,那么此时打包之后update.config.json应该更新为:

  1. {
  2. "ios": [{
  3. "bundleV": "0.0.1",
  4. "appV": "2.9.5",
  5. "date": "2016-05-20"
  6. },{
  7. "bundleV": "0.0.2",
  8. "appV": "2.9.5",
  9. "date": "2016-05-20"
  10. }],
  11. "android": [{
  12. "bundleV": "0.0.1",
  13. "appV": "2.9.5",
  14. "date": "2016-05-20"
  15. }]
  16. }

全量包

监听器在监听到bundleSrc有变化的时候会执行node bin/bundle进行一次版本打包,产生的bundle包会存放public/bundle目录下面,可以被外部访问。

  1. bundle 存放全量bundle和全量assets的目录
  2. 0.0.1
  3. 0.0.2
  4. ios
  5. bundle.config.json 此版本的配置信息,包含要求app的最低版本等
  6. index.ios.jsbundle 全量jsbundle文件
  7. assets 全量图片目录
  8. android
  9. ios
  10. 0.0.3

bundle.config.json内容是根据update.config.json自动生成的:

  1. {
  2. "bundleV": "0.0.2",
  3. "appV": "2.9.5",
  4. "date": "2016-05-20"
  5. }

全量bundle文件
我们还会将版本对应目录生成一个压缩包,用于客户端下载。,这就是0.0.2版本的全量包,客户端只需要通过地址http://rnbundle.fanxing.com/bundle/ios/0.0.2.zip下载到版本进行解压,将本地离线包替换,就可以实现升级。

增量包

全量包的缺点就是不管每次改动量多大都是全量更新,一个纯净的index.ios.jsbundle压缩过后也有600多k,再加上图片,几乎每次升级都会下载将近1M的更新包,这对3G或者4G用户来说,绝对是可耻的。所以我们需要做增量升级。

  1. patch 存放增量补丁的目录
  2. 0.0.1
  3. 0.0.2
  4. ios
  5. 0.0.1-0.0.2.zip 增量包.zip
  6. android
  7. ios
  8. 0.0.3
  9. ios
  10. 0.0.1-0.0.2.zip 增量包.zip
  11. 0.0.1-0.0.3.zip 增量包.zip
  12. android
  13. ios

这样如果用户当前bundle版本是0.0.1,则用户需要下载的是0.0.1-0.0.3.zip的差异包。
http://rnbundle.fanxing.com/patch/ios/0.0.1-0.0.3.zip

增量包计算

首先,计算增量包:新版本(v10) - 旧版本(v1到v9) = 增量包 (会有9个包,v1~v10.zip,v2~v10.zip,,,,,v9-v10.zip)
然后,app根据自己的当前版本(比如V6),下载对应的增量包(V6-V10.zip)。
最后,app中通过旧版本(v6) + 增量包(v6~v10.zip) = 新版本(v10) ,计算出了新版本的全量包。

注意,服务器并不是无限的产生增量包,如果app当前版本过小,版本差超过一定的数值(可以看实况自定义,比如5或者10个),服务器可以觉得返回给用户一个全量包,比如app当前版本是0.0.1,服务器最新版本是0.0.20,就没必要让app下载一个差异包0.0.1-0.0.20.zip,直接下载0.0.20.zip的全量包。

增量算法

assets目录计算增量比较简单,目录对比,将新增或者改动的文件输出;
jsbundle文件的差异计算可以借助开源库: https://github.com/bystep15/google-diff-match-patch

只用到2个接口:
计算文件差异:patch_make(text1, text2) => patches
合并文件差异:patch_apply(patches, text1) => [text2, results]

接口设计

请求接口:http://kfgch.rnbundle.fxwork.kugou.com/patch/checkUpdate?bundleV=0.0.1&appV=2.9.5&platform=ios&app=fanxing&business=kugoulive&kugouId=669012
参数说明:

  1. bundleV:当前bundle版本,如0.0.1
  2. appV:当app版本,如2.9.5
  3. platform:手机平台,如ios&android
  4. app: 独立繁星还是酷狗客户端,如fanxing&kugou
  5. business: 具体业务线,kugoulive
  6. kugouId: 酷狗ID

返回值:

  1. {
  2. status: 1,
  3. data: {
  4. canUpdate: true,
  5. msg: "可以升级,jsbundle版本为:0.0.3",
  6. canUpdateBundleV: "0.2.27",
  7. platform: "ios",
  8. downloadUrl: "http://kfgch.rnbundle.fxwork.kugou.com/patch/ios/0.0.1-0.0.3.zip",
  9. isPatch: false
  10. },
  11. msg: ""
  12. }
  1. {
  2. status: 1,
  3. data: {
  4. canUpdate: true,
  5. msg: "可以升级,jsbundle版本为:0.0.20",
  6. canUpdateBundleV: "0.0.20",
  7. platform: "ios",
  8. downloadUrl: "http://kfgch.rnbundle.fxwork.kugou.com/bundle/ios/0.0.20.zip",
  9. isPatch: false
  10. },
  11. msg: ""
  12. }
  1. {
  2. status: 1,
  3. data: {
  4. canUpdate: false,
  5. msg: "未找到可升级的版本",
  6. platform: "ios"
  7. },
  8. msg: ""
  9. }

灰度升级

如果某些新的功能我们只希望部分用户可以看到,那么我们的升级服务就需要做灰度处理。

  1. {
  2. bundleV: '0.0.2' //bundle版本
  3. ip: '10.12.0.[110-120]', //ip正则,匹配则可以升级
  4. kugouid: '\d', //kugouId正则,匹配则可以升级
  5. number: 500 //灰度用户数量,大于0则表示可以升级
  6. }
  1. bundleV:当前bundle版本,如0.0.1
  2. appV:当app版本,如2.9.5
  3. platform:手机平台,如ios&android
  4. app: 独立繁星还是酷狗客户端,如fanxing&kugou
  5. business: 具体业务线,kugoulive
  6. kugouId: 酷狗ID

客户端更新

客户端的主要工作就是发请求到升级服务器询问是否能升级,然后根据返回的信息,下载升级包,解压升级包,合并差异包,安装升级包。

更新方式

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注