@garygchai
2016-06-06T14:51:39.000000Z
字数 5633
阅读 3495
reactnative
我们喜欢reactnative的原因除了它可以让开发人员使用javascript来开发native程序之外,还有它的热更新机制,这样我们可以摆脱每次改动都要发布到商城让用户下载的痛苦。在reactnative开发阶段,我们可以通过http://localhost:8081/index.ios.bundle?platform=ios&dev=true&hot=true
让reactnative下载jsbundle文件实现热更新,但是线上环境我们不会这样做,不然就没有这篇文章了,原因是这样太慢了,reactnative在下载完jsbundle文件到解析为native应用显示的时候页面都是空白的,所以我们都会选择将jsbundle文件打离线包存放在本地,让native去读取,这样可以节省网络下载jsbundle文件的时间。
$ 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资源文件就是我们要离线到native本地的,所以我们的热更新系统就是基于这个这样的方式进行开发的。
执行reactnative的项目bin
目录下面的bundle.js
文件可以在bundle
目录生成最新的jsbundle文件以及所需的静态资源文件,并且上传到升级服务器。用法如下:
node bin/bundle --platform ios --bundle-output ./bundle
--platform
和--bundle-output
是可选参数,如果指定--platform
,则只打包指定平台的文件,如果指定--bundle-output
,则会将产生的bundle文件同步到指定的目录。用法举例:
图中的/Users/gaochanghai/fanxing.git/rnbundle.fanxing.com/bundleSrc
可以是本地目录,也可以是升级服务器的远程目录。
执行打包命令的时候,上面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/bundle
和node bin/patch
命令,用于生成版本包和差异包。
监听到变化打出来的bundle全量包或者增量包我们会同步至CDN以供用户下载,否则下载包的流量会对服务器产生不小的流量压力。目前先同步至图片服务器:
echo "同步 bundle 目录到 10.16.6.89,请等待..."
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
echo "同步 patch 目录到 10.16.6.89,请等待..."
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文件的版本管理和维护:
{
"ios": [{
"bundleV": "0.0.1",
"appV": "2.9.5",
"date": "2016-05-20"
}],
"android": [{
"bundleV": "0.0.1",
"appV": "2.9.5",
"date": "2016-05-20"
}]
}
每一次版本改动都会更新update.config.json
文件,bundleV
和appV
分别表示当前bundle包的版本及其所依赖的最小app版本。假设有一次ios文件改动,那么此时打包之后update.config.json
应该更新为:
{
"ios": [{
"bundleV": "0.0.1",
"appV": "2.9.5",
"date": "2016-05-20"
},{
"bundleV": "0.0.2",
"appV": "2.9.5",
"date": "2016-05-20"
}],
"android": [{
"bundleV": "0.0.1",
"appV": "2.9.5",
"date": "2016-05-20"
}]
}
监听器在监听到bundleSrc
有变化的时候会执行node bin/bundle
进行一次版本打包,产生的bundle包会存放public/bundle目录下面,可以被外部访问。
bundle 存放全量bundle和全量assets的目录
0.0.1
略
0.0.2
ios
bundle.config.json 此版本的配置信息,包含要求app的最低版本等
index.ios.jsbundle 全量jsbundle文件
assets 全量图片目录
android
略 同ios
0.0.3
略
bundle.config.json
内容是根据update.config.json
自动生成的:
{
"bundleV": "0.0.2",
"appV": "2.9.5",
"date": "2016-05-20"
}
我们还会将版本对应目录生成一个压缩包,用于客户端下载。,这就是0.0.2
版本的全量包,客户端只需要通过地址http://rnbundle.fanxing.com/bundle/ios/0.0.2.zip
下载到版本进行解压,将本地离线包替换,就可以实现升级。
全量包的缺点就是不管每次改动量多大都是全量更新,一个纯净的index.ios.jsbundle压缩过后也有600多k,再加上图片,几乎每次升级都会下载将近1M的更新包,这对3G或者4G用户来说,绝对是可耻的。所以我们需要做增量升级。
patch 存放增量补丁的目录
0.0.1
略
0.0.2
ios
0.0.1-0.0.2.zip 增量包.zip
android
略 同ios
0.0.3
ios
0.0.1-0.0.2.zip 增量包.zip
0.0.1-0.0.3.zip 增量包.zip
android
略 同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]
bundleV:当前bundle版本,如0.0.1
appV:当app版本,如2.9.5
platform:手机平台,如ios&android
app: 独立繁星还是酷狗客户端,如fanxing&kugou
business: 具体业务线,kugoulive
kugouId: 酷狗ID
返回值:
{
status: 1,
data: {
canUpdate: true,
msg: "可以升级,jsbundle版本为:0.0.3",
canUpdateBundleV: "0.2.27",
platform: "ios",
downloadUrl: "http://kfgch.rnbundle.fxwork.kugou.com/patch/ios/0.0.1-0.0.3.zip",
isPatch: false
},
msg: ""
}
{
status: 1,
data: {
canUpdate: true,
msg: "可以升级,jsbundle版本为:0.0.20",
canUpdateBundleV: "0.0.20",
platform: "ios",
downloadUrl: "http://kfgch.rnbundle.fxwork.kugou.com/bundle/ios/0.0.20.zip",
isPatch: false
},
msg: ""
}
{
status: 1,
data: {
canUpdate: false,
msg: "未找到可升级的版本",
platform: "ios"
},
msg: ""
}
如果某些新的功能我们只希望部分用户可以看到,那么我们的升级服务就需要做灰度处理。
灰度规则
bundleV: 某个版本的灰度规则
IP:支持正则配置ip区间,请求ip匹配,则符合灰度要求;
KugouID:支持正则配,请求用户kugouId匹配,则符合灰度要求;
number:灰度用户数量,剩余number数据大于0则符合灰度要求
灰度配置
由业务方服务器端提供灰度配置:
{
bundleV: '0.0.2' //bundle版本
ip: '10.12.0.[110-120]', //ip正则,匹配则可以升级
kugouid: '\d', //kugouId正则,匹配则可以升级
number: 500 //灰度用户数量,大于0则表示可以升级
}
bundleV:当前bundle版本,如0.0.1
appV:当app版本,如2.9.5
platform:手机平台,如ios&android
app: 独立繁星还是酷狗客户端,如fanxing&kugou
business: 具体业务线,kugoulive
kugouId: 酷狗ID
客户端的主要工作就是发请求到升级服务器询问是否能升级,然后根据返回的信息,下载升级包,解压升级包,合并差异包,安装升级包。