前言:React Native 的其中一个卖点是程序可热更新,当前官方和非官方对这类实操的完整指导不多,所以在我们的项目实践中,我们做了一套自己的方案,iOS 侧已经上线运行,理论上和实践上没啥问题,这里梳理出来,一方面作为后续我们在 Android 的对齐基准,另一方面与大家共享思路方便探讨调优。
要做好 React Native 的热更新,主要需要处理好如下几个情况:
本地启动:为保证启动速度,不能全部依赖线上的 bundle,需保证还未下载到 bundle 的时候,能如常载入 bundle 并启动,所以初始化 RCTBridge 或 RCTRootView 时用的 bundleURL 得指向本地而非网络;
及时更新:为实现所用 bundle 能够及时更新,需要在合适时机拉取最新版的 bundle 存放到本地,细则如下:在 app 启动时,在 app 从后台切到前台后,以及在网络状态发生变化后,发起请求拉取最新的配置信息,根据配置信息确定是否需要下载 bundle 以及后续处理。
流量节约:为实现可控的流量节约,配置信息中包含了要使用的 bundle 信息如下:
当读取到上述信息后,基于配置中的 token 与本地值比较是否一致确认是否结束流程,如果不一致则以配置中的 url 发起一个请求,得到 bundle 后,保存到本地,同时把配置中的 token 也保存到本地。
版本并存:为实现多版本同时并存,提供 A/B Test、灰度发布等能力,需要做到:
实现因应不同情况输出不同配置信息的能力,有两种做法:
a. 搭个动态 server,提供个接口,接受表达客户端情况的几个参数,根据这些参数的不同输出不同的配置信息,客户端读取配置信息时,都通过访问 server 上的这个接口来;
b. 写个 JavaScript 文件,在其中写个函数,接受表达客户端情况的几个参数,根据这些参数的不同输出不同的配置信息,把这个 JavaScript 文件作为静态资源部署到 server,客户端读取配置信息时,都通过访问 server 拉取这个 JavaScript 文件,然后将其中的内容作为 JavaScriptCore 的 code 执行一下,然后调用其中的函数来获取配置信息;
由于懒得搭动态 server,我们选择了 b 做法,关键代码如下;
// versionControl.js,
// 实际上这是个全局通用的资源版本控制配置文件,
// react-native bundle 作为其中一种资源存于其中。
// 注意:这里的代码是要放到 JavaScriptCore 中直接执行的,所以高级的 ES6 语法不能用。
var latestReactNativeBundleMetas = {
ios: {
url: 'http://cdn.xxx.com/react-native/1.1.0/04291109.ios.bundle',
token: 'a69cc86a12115f0b962ef4bd8c0a8241'
},
android: {
url: 'http://cdn.xxx.com/react-native/1.0.3c.android.bundle',
token: ''
}
};
var versionControlGetters = {
production: function(platform, appVer, innerId) {
// 每次在测试环境测试通过后,请将上边的 latestReactNativeBundleMetas.ios 的值复制到这里。
var meta = {
url: 'http://cdn.xxx.com/react-native/1.1.0/04291109.ios.bundle',
token: 'a69cc86a12115f0b962ef4bd8c0a8241'
};
return {
"react-native": {
meta: meta,
urging: 1
}
};
},
test: function(platform, appVer, innerId) {
return {
"react-native": {
// 这里的值一般维持不变,使用 latestReactNativeBundleUrls.ios 的值即可。
meta: latestReactNativeBundleMetas[platform],
urging: 3
}
};
}
}
function getVersionControl(envType, platform, appVer, innerId) {
return versionControlGetters[envType](platform, appVer, innerId);
}
- (void)getVersionControl:(void(^)(NSDictionary *data))callback
{
if (callback) {
NSString *url = @"http://cdn.xxx.com/config/versionControl.js";
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager GET:url
parameters:nil
success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
NSString *code = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
JSContext *context = [JSContext new];
[context evaluateScript:code withSourceURL:[NSURL URLWithString:url]];
NSArray *args = @[[PlatFormUtil isNormalService] ? @"production" : @"test", @"ios", [PlatFormUtil AppVer], @(getCurrentInnerId())];
NSDictionary *data = [[context[@"getVersionControl"] callWithArguments:args] toDictionary];
callback([data objectForKey:@"react-native"]);
}
failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
callback(nil);
}];
}
}
错误跟踪:为实现诸如错误上报版本跟踪、问题反馈版本跟踪等需求,需在代码中提供版本号和 Build 号信息,为此,提供一个 version 模块,考虑到 iOS、Android 并存,提供了一个公共的 version.base 模块,在 version.ios 和 version.android 中分别引用并扩展平台相关的信息;
// version.base.js
'use strict';
export default class Version {
code = '1.1.0';
build = '04291109';
folderUrl = 'http://cdn.xxx.com/react-native/';
platformCode = 'unknown';
};
// version.ios.js
'use strict';
import Version from './version.base';
export default new Version({
platformCode: 'ios'
});
// version.android.js // 预留,尚未启用
'use strict';
import Version from './version.base';
export default new Version({
platformCode: 'android'
});
鉴于 version.ios 和 version.android 的代码是固定的,所以版本升级时,主要维护的是 version.base,
发布流程自动化;
一般来说,一个发布过程应该包括如下过程:
这么个流程,人工搞是可以,不过未免过于琐碎繁琐、易于出错,所以建议搞脚本,把这流程自动化起来。这个话题的细节比较多,后边会单独撰文详述。
2024 - 快车库 - 我的知识库 重庆启连科技有限公司 渝ICP备16002641号-10
企客连连 表单助手 企服开发 榜单123