目录
-
一、方案背景与目标
-
二、技术选型
-
三、集成加载 React Native
-
四、混编方案设计(iOS/Android 双端)
-
五、洞窝 热更新功能设计
-
六、测试与灰度方案
-
七、风险与应对
一、背景与目标
1.1 背景
React Native(RN)具备跨平台、热更新能力。通过将 RN 混编进原生项目,可结合原生项目稳定性与 RN 快速迭代优势,减少应用商店审核依赖,紧急修复成本高的问题。
1.2 目标
一. 先跑通前期流程,RN项目融合到原生,并且实现第三方热更新。
-
前期跑通RN流程。 (1)搭建RN环境。 (2)创建React Native 项目。 (3)原生新工程空页面配置xcode。 (4)执行bundle exec pod install (后续混合到原生项目中)
-
实现 RN 模块与原生项目(iOS/Android)的无缝混编,支持原生调用 RN 页面、RN 调用原生能力。
-
原生加载 RN 页面(打包 JS Bundle)使原生项目跑通,并显示RN界面。
-
修改RN项目,使其支持pushy热更新,并且加载到原生项目中。
二. 构建一套热更新系统,支持 RN 业务代码(JS/JSBundle、资源文件)的远程下发、本地更新、版本管理,实现独立可控的热更新能力。
二、技术选型
版本选择依据原生项目最低支持的ios13,所以我们版本是React Native 0.74.5
如果你的项目需要支持更低版本的 iOS(如 iOS 11 及以下),则需要使用 React Native 0.69 或更早版本,但需注意旧版本可能存在安全漏洞和功能限制。
1.RN的版本对比
以下是 React Native 0.74.5、0.75、0.76 及以上版本的核心对比表格,涵盖适配版本、核心优化、兼容性等关键信息,方便直观对比:
| 对比维度 | React Native 0.74.5 | React Native 0.75 | React Native 0.76 及以上 |
|---|---|---|---|
| 最低 iOS 版本 | iOS 12.4 | iOS 13.4 | iOS 15.1 |
| 最低 Android 版本 | Android 5.0(API 21) | Android 5.0(API 21) | Android 6.0(API 23) |
| React 依赖版本 | React 18.2.0 | React 18.2.0 | React 18.2.0(0.76+)/ React 19(未来版本计划) |
| Hermes 引擎版本 | Hermes 0.18.1 | Hermes 0.19.0(支持 WeakRef 等) | Hermes 0.21.0+(支持 ArrayBuffer.transfer 等) |
| 核心依赖升级 | Folly 2023.07.24.00 Yoga 2.0.0-rc.5 | Folly 2023.10.02.00 Yoga 2.0.0(稳定版) | Folly 2023.11.27.00+ Yoga 2.1.0+(布局性能优化) |
| 功能新增 | - 无重大新增 - 修复 TextInput、ScrollView 小问题 | - ScrollView 新增 contentOffset 回调参数 - Pressable 支持 borderless 水波纹 | - Text 组件新增 selectable 属性 - Image 支持 priority 属性(iOS) - Android 分屏模式响应优化 |
| API 废弃 / 移除 | - 无重大废弃 | - 废弃 SafeAreaView.forceInset - 移除 NavigationExperimental | - 彻底移除 NetInfo 旧 API - 废弃 View.removeClippedSubviews |
| 开发体验优化 | Metro 0.77.0(基础稳定性修复) | Metro 0.79.1(增量编译提速) | Metro 0.80.5+(TypeScript 5.2+ 支持,内存优化) |
| 系统适配 | 基础支持 iOS 16、Android 13 | 优化 iOS 16+ 旋转布局、Android 13 通知权限 | 深度适配 iOS 17(Modal 布局修复)、Android 14(媒体权限) |
| 兼容性范围 | 兼容 iOS 12.4+、Android 5.0+,支持旧设备 | 放弃 iOS 12,聚焦 iOS 13+ 功能优化 | 放弃 iOS 13/14、Android 5.0,仅支持现代系统 |
| 适用场景 | 需兼容老旧设备(如 iOS 12、Android 5.0)的项目 | 最低支持 iOS 13.4+,需 Hermes 性能提升的项目 | 仅支持新设备(iOS 15.1+、Android 6.0+),追求新特性 |
2.主要技术选型
| 模块 | 技术选择 | 说明 |
|---|---|---|
| 混编核心 | React Native(稳定版0.74.5) | 跨平台框架,提供 JS 与原生交互能力 |
| 原生容器 | iOS(UIViewController)、Android(Activity/Fragment) | 承载 RN 页面的原生容器 |
| 通信层 | RN 原生模块(Native Modules) | 实现 JS 与原生的方法调用、事件通知 |
| 热更新核心 | 自定义热更新引擎(基于文件差分) | 替代 pushy,支持 Bundle 下载/校验 |
| 网络请求 | 原生网络库(iOS: AFNetworking;Android: Retrofit) | 保证热更新包下载的稳定性 |
| 加密与校验 | SHA256 校验 + AES 加密 | 确保热更新包的完整性与安全性 |
| 版本管理 | 后端版本接口 + 本地版本记录 | 控制热更新包的下发范围与优先级 |
三、集成加载 React Native 0.74.5 页面
1.CLI 创建 React Native 项目
安装依赖
使用 Homebrew 安装 Node、Watchman、yarn。(node版本号 v24.10.0)
bash
$ brew install node
$ brew install watchman
$ brew install yarn
创建 RN 项目
先创建RN 开发页面,具体package.json内容:(可直接复制),放置到空目录cli_rn_modules下面
bash
{
"name": "cli_rn_modules",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest"
},
"dependencies": {
"react": "^18.2.0",
"react-native": "^0.74.5",
"react-native-safe-area-context": "^5.6.2"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@react-native/babel-preset": "^0.82.1",
"@react-native/metro-config": "^0.82.1",
"@types/react": "^18.2.6",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.6.3",
"eslint": "^8.19.0",
"jest": "^29.6.3",
"prettier": "^2.8.8",
"react-test-renderer": "18.2.0",
"typescript": "~5.3.3"
},
"engines": {
"node": ">=18"
}
}
安装 React 和 React Native:
bash
npm add react@18.2.0 react-native@0.74.5
创建个完整的项目(创建不出来也不要紧,结尾会附上demo图)
bash
npx react-native init
iOS 配置
配置 Xcode
-
配置 Xcode 证书
-
最低版本号设置为 iOS 13.4
安装 iOS 原生依赖和运行
按照上面 Run instructions for iOS 的操作步骤,安装 iOS Pod 依赖。
bash
$ cd ~/my-rn/cli_rn_modules/ios
$ bundle install
$ bundle exec pod install
==================== DEPRECATION NOTICE =====================
Calling `pod install` directly is deprecated in React Native
because we are moving away from Cocoapods toward alternative
solutions to build the project.
* If you are using Expo, please run:
`npx expo run:ios`
* If you are using the Community CLI, please run:
`yarn ios`
=============================================================
运行 iOS
bash
$ yarn ios
截止目前为止RN项目已经完成,并且可以正常打包出来了
bash
### 打包 JS Bundle
| 参数 | 说明 |
|-------|-------|
| --entry-file | JS 入口,通常是 index.js 或 App.js |
| --platform ios | 打包 iOS 版本 |
| --dev false | 生产环境,不包含调试代码 |
| --bundle-output | 打包生成的 JS 文件,放到 OC 项目里 |
| --assets-dest | 打包静态资源(图片、字体) |
``` bash
$ npx react-native bundle \
--entry-file index.js \
--platform ios \
--dev false \
--bundle-output ios/jsbundle/main.jsbundle \
--assets-dest ios/jsbundle/assets
2.新建demo的iOS项目集成 React Native
创建目录
先创建 RN 环境文件夹,再创建原生项目子文件夹
bash
$ mkdir ./oc_rn_build
$ mkdir ./oc_rn_build/ios
安装 NPM 依赖项
进入 oc_rn_build 根目录,还是安装 package.json 文件然后安装 npm 包。
bash
{
"name": "HelloWorld",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest"
},
"dependencies": {
"react": "^18.2.0",
"react-native": "0.74.5",
"react-native-safe-area-context": "^5.6.2"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@react-native/babel-preset": "^0.82.1",
"@react-native/metro-config": "^0.82.1",
"@types/react": "^18.2.6",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.6.3",
"eslint": "^8.19.0",
"jest": "^29.6.3",
"prettier": "^2.8.8",
"react-test-renderer": "18.2.0",
"typescript": "~5.3.3"
},
"engines": {
"node": ">=18"
}
}
cd ~/oc_rn_build:然后执行:
bash
bundle install
bundle exec pod install
注意点:一定要将 Hermes 版本改为 0.74.5
配置 CocoaPods
将原生项目根目录所有文件放在 ./oc_rn_build/ios 子文件夹。配置 CocoaPods 需要两个文件:
-
Gemfile文件在根目录./oc_rn_build下载 -
Podfile文件在子目录./oc_rn_build/ios下载
bash
$ curl -O https://raw.githubusercontent.com/react-native-community/template/refs/heads/0.75-stable/template/Gemfile
$ cd ./ios
$ curl -O https://raw.githubusercontent.com/react-native-community/template/refs/heads/0.75-stable/template/ios/Podfile
可以修改 Gemfile 文件
bash
-gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
+gem 'cocoapods', '1.16.2'
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
-gem 'xcodeproj', '< 1.26.0'
+gem 'xcodeproj', '1.27.0'
配置 Xcode
-
配置 Xcode 证书
-
最低版本号设置为 iOS 13.4
- React Native 0.74.5.0 最低支持 iOS 13.4
-
禁用 Xcode 的用户脚本沙盒
-
打开 Xcode 项目
-
Build Settings 搜索
ENABLE_USER_SCRIPT_SANDBOXING -
将该选项的值设置为
NO
-
-
禁用 "基于视图控制器" 的控制方式,强制使用全局设置
-
打开 Xcode 项目
-
Info 添加
UIViewControllerBasedStatusBarAppearance设置为NO
-
集成 RN 环境
先安装 Ruby 依赖项,然后集成 RN 环境。
bash
$ cd ./ios
$ bundle install
$ bundle exec pod install
3.iOS 加载 RN 页面
打包 JS Bundle
在 RN 项目的根目录打包 jsbundle 拷贝到原生项目的 jsbundle。
bash
$ cd ./cli_rn_modules
$ npx react-native bundle \
--entry-file index.js \
--platform ios \
--dev false \
--bundle-output ios/jsbundle/main.jsbundle \
--assets-dest ios/jsbundle/assets
$ cp -Rf ./cli_rn_modules/ios/jsbundle ./oc_rn_build/ios
| 参数 | 说明 |
|---|---|
| --entry-file | JS 入口,通常是 index.js 或 App.js |
| --platform ios | 打包 iOS 版本 |
| --dev false | 生产环境,不包含调试代码 |
| --bundle-output | 打包生成的 JS 文件,放到 OC 项目里 |
| --assets-dest | 打包静态资源(图片、字体) |
原生跳转 RN 页面
使用 Xcode 将 jsbundle 拖入项目工程,创建 ReactNativeFactoryDelegate 类用于初始化 RCTReactNativeFactory。
Objective-C
// ReactViewController.m
#import "ReactViewController.h"
#import "ReactNativeFactoryDelegate.h"
#import <RCTReactNativeFactory.h>
#import <RCTAppDependencyProvider.h>
@interface ReactViewController ()
@property (nonatomic, copy) NSString *moduleName;
@end
@implementation ReactViewController {
RCTReactNativeFactory *_factory;
id<RCTReactNativeFactoryDelegate> _factoryDelegate;
}
- (instancetype)initWithModuleName:(NSString *)moduleName {
self = [super init];
if (self) {
_moduleName = [moduleName copy];
}
return self;
}
- (instancetype)init {
return [self initWithModuleName:@"cli_rn_modules"]; // 默认模块名
}
- (void)viewDidLoad {
[super viewDidLoad];
_factoryDelegate = [ReactNativeFactoryDelegate new];
_factoryDelegate.dependencyProvider = [RCTAppDependencyProvider new];
_factory = [[RCTReactNativeFactory alloc] initWithDelegate:_factoryDelegate];
// 创建React Native视图,使用指定的模块名
UIView *rootView = [_factory.rootViewFactory viewWithModuleName:self.moduleName];
self.view = rootView;
}
@end
模块名称 moduleName 在 RN 项目 cli_rn_modules/index.js 文件注册页面。
Objective-C
// 注册页面
AppRegistry.registerComponent(appName, () => App);
AppRegistry.registerComponent('HomePage', () => Home);
AppRegistry.registerComponent('MyPage', () => My);
AppRegistry.registerComponent('SettingsPage', () => Settings);
AppRegistry.registerComponent('BusinessCardView', () => BusinessCardView);
...
4. 常见问题
-
React Native 中文网没有 0.82.0 版本的文档
-
yarn install 报错
-
如果是网络原因请检查 VPN
-
如果是 node 版本问题,请检查电脑 brew、nvm、npm 管理的 node 版本,更新到报错信息要求的版本
-
-
bundle exec pod install 后 Xcode 运行项目 Hermes 报错
- Build Settings 搜索
ENABLE_USER_SCRIPT_SANDBOXING,将该选项的值设置为NO
- Build Settings 搜索
-
原生跳转 RN 页面弹出报错弹窗
- Info 添加 UIViewControllerBasedStatusBarAppearance 设置为 NO
四、混编方案设计(iOS/Android 双端)
4.1 项目结构设计
采用「原生项目为主,RN 模块作为子模块」的结构,避免对原生项目的侵入性改造:
bash
原生项目根目录/
├── ios/ # iOS 原生代码
├── android/ # Android 原生代码
├── rn_module/ # RN 子模块
│ ├── src/ # RN 业务代码(组件、页面)
│ ├── index.js # RN 入口文件
│ ├── metro.config.js # RN 打包配置
│ └── package.json # RN 依赖
└── hotupdate/ # 热更新相关原生代码(双端共用逻辑抽象)
4.2 iOS 混编实现
4.2.1 集成 RN 环境
-
通过 CocoaPods 引入 RN 核心库(
React.podspec、yoga.podspec等),指定版本与原生项目兼容。 -
在
Podfile中添加 RN 子模块依赖,确保原生项目可访问 RN 代码:
ruby
bash
# Podfile 示例 target 'NativeProject' do pod 'React', :path => '../rn_module/node_modules/react-native/' pod 'yoga', :path => '../rn_module/node_modules/react-native/ReactCommon/yoga'# 其他 RN 依赖 end
4.2.2 RN 页面容器
自定义 RNViewController,继承 UIViewController,通过 RCTRootView 加载 RN 页面:
bash
objective-c
`// RNViewController.h
#import <UIKit/UIKit.h>
#import <React/RCTRootView.h>
@interface RNViewController : UIViewController
/// 初始化 RN 页面容器
/// @param moduleName RN 入口组件名称
/// @param initialProps 传递给 RN 的初始参数
● (instancetype)initWithModuleName:(NSString *)moduleName
initialProps:(NSDictionary *)initialProps;
@end`
objective-c
`// RNViewController.m
#import "RNViewController.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
@implementation RNViewController
● (instancetype)initWithModuleName:(NSString *)moduleName
initialProps:(NSDictionary *)initialProps {
if (self = [super init]) {
// 初始化 RN 桥接器
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self
launchOptions:nil];
// 创建 RN 根视图
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:moduleName
initialProperties:initialProps];
self.view = rootView;
}
return self;
}
pragma mark - RCTBridgeDelegate
(NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
// 开发环境加载本地服务,生产环境加载本地 bundle
#ifdef DEBUG
return [NSURL URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
#else
return [[NSBundle mainBundle] URLForResource:@"index.ios" withExtension:@"bundle"];
#endif
}
@end`
使用示例:原生页面打开 RN 页面
objective-c
bash
// 在原生按钮点击事件中调用 RNViewController *rnVC = [[RNViewController alloc] initWithModuleName:@"HomePage" initialProps:@{@"userId": @"10086"}]; [self.navigationController pushViewController:rnVC animated:YES];
4.2.3 原生与 RN 通信
- 原生模块暴露 :创建
RNBridgeModule继承RCTEventEmitter,暴露原生能力给 RN:
bash
objective-c
`// RNBridgeModule.h
#import <React/RCTEventEmitter.h>
@interface RNBridgeModule : RCTEventEmitter
@end`
objective-c
`// RNBridgeModule.m
#import "RNBridgeModule.h"
#import <UIKit/UIKit.h>
@implementation RNBridgeModule
// 模块名称(RN 中通过此名称调用)
RCT_EXPORT_MODULE();
// 暴露获取设备信息的方法给 RN
RCT_EXPORT_METHOD(getDeviceInfo:(RCTResponseSenderBlock)callback) {
NSString *deviceModel = [UIDevice currentDevice].model;
NSString *systemVersion = [UIDevice currentDevice].systemVersion;
callback(@[[NSNull null], @{@"model": deviceModel, @"systemVersion": systemVersion}]);
}
// 原生主动发送事件给 RN(例如网络状态变化)
● (void)networkStatusChanged:(NSString *)status {
[self sendEventWithName:@"onNetworkStatusChange" body:@{@"status": status}];
}
// 注册事件名称(RN 需要监听的事件)
● (NSArray<NSString *> *)supportedEvents {
return @[@"onNetworkStatusChange"];
}
@end`
● RN 调用原生:
javascript
运行
`import { NativeModules } from 'react-native';
// 调用原生方法
NativeModules.RNBridgeModule.getDeviceInfo((error, result) => {
if (error) {
console.error('获取设备信息失败:', error);
} else {
console.log('设备信息:', result);
}
});`
● RN 监听原生事件:
javascript
运行
`import { NativeEventEmitter } from 'react-native';
const eventEmitter = new NativeEventEmitter(NativeModules.RNBridgeModule);
const subscription = eventEmitter.addListener(
'onNetworkStatusChange',
(status) => {
console.log('网络状态变化:', status);
}
);
// 组件卸载时移除监听
subscription.remove();`
4.3 Android 混编实现
4.3.1 集成 RN 环境
-
在
settings.gradle中引入 RN 子模块:gradle
// settings.gradle include ':app', ':rn_module' project(':rn_module').projectDir = new File(rootProject.projectDir, '../rn_module/android') -
在
app/build.gradle中添加依赖:
gradle
// app/build.gradle dependencies { implementation project(':rn_module') implementation "com.facebook.react:react-native:+" // 与 RN 版本匹配 // 其他依赖 }
4.3.2 RN 页面容器
自定义 RNActivity 继承 AppCompatActivity,通过 ReactRootView 加载 RN 页面:
bash
`// RNActivity.java
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.shell.MainReactPackage;
public class RNActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 获取从原生传递的参数
String moduleName = getIntent().getStringExtra("moduleName");
Bundle initialProps = getIntent().getExtras();
// 初始化 RN 根视图
mReactRootView = new ReactRootView(this);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle") // 内置 bundle 名称
.setJSMainModulePath("index")
.addPackage(new MainReactPackage()) // 注册基础包
.addPackage(new CustomReactPackage()) // 注册自定义模块
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// 启动 RN 页面
mReactRootView.startReactApplication(mReactInstanceManager, moduleName, initialProps);
setContentView(mReactRootView);
}
// 处理返回键事件
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
// 生命周期同步
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause(this);
}
}
@Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy(this);
}
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
}
}
}`
使用示例:原生页面打开 RN 页面
// 在原生按钮点击事件中调用 Intent intent = new Intent(this, RNActivity.class); intent.putExtra("moduleName", "HomePage"); intent.putExtra("userId", "10086"); startActivity(intent);
4.3.3 原生与 RN 通信
- 自定义原生模块 :创建
RNBridgeModule继承ReactContextBaseJavaModule:
java
// RNBridgeModule.java
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
public class RNBridgeModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext reactContext;
public RNBridgeModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
// 模块名称(RN 中通过此名称调用)
@Override
public String getName() {
return "RNBridgeModule";
}
// 暴露获取设备信息的方法给 RN
@ReactMethod
public void getDeviceInfo(Callback callback) {
String model = android.os.Build.MODEL;
String version = android.os.Build.VERSION.RELEASE;
callback.invoke(null, new HashMap<String, String>() {{
put("model", model);
put("systemVersion", version);
}});
}
}
- 注册模块 :创建
CustomReactPackage实现ReactPackage:
java
// CustomReactPackage.java
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CustomReactPackage implements ReactPackage {
@Override
public List createNativeModules(ReactApplicationContext reactContext) {
List modules = new ArrayList<>();
// 注册自定义模块
modules.add(new RNBridgeModule(reactContext));
return modules;
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
五、洞窝热更新功能设计
5.1 热更新原理
RN 页面的核心逻辑通过 JS 代码编写,最终打包为 index.bundle(JSBundle),配合资源文件(图片、字体等)运行。热更新本质是通过远程下发新的 JSBundle 和资源,替代本地旧版本,实现无需应用商店审核的更新。
5.2 系统架构
热更新系统分为 客户端 、服务端 、管理后台 三部分:

5.3 客户端实现(核心)
5.3.1 更新包结构
每个热更新包为 zip 压缩文件,包含:

manifest.json元数据示例

5.3.2 核心流程
-
版本检查
-
时机:App 启动时、RN 页面首次加载前
-
逻辑:
-
-
更新包下载与校验
-
下载:使用原生网络库(支持断点续传、进度监听)
-
校验:下载完成后,计算文件 SHA256 与
manifest.json中的hash比对,不一致则删除重试。
-
-
本地更新与存储
-
存储路径:
-
iOS:
NSDocumentDirectory/RNUpdates/{version}/ -
Android:
getFilesDir()/rn_updates/{version}/
-
-
版本记录:用
SharedPreferences(Android)或UserDefaults(iOS)记录当前生效版本。
-
-
生效策略
-
立即生效:下载完成后调用
reload()重新加载 JSBundle -
下次启动生效:记录更新状态,下次打开 RN 页面时加载新包
-
-
回滚机制
-
监听 RN 加载异常(如
onJSException) -
发生异常时,切换至上次正常版本,并上报错误日志
-
本地保留最近 2 个版本,超出则删除最旧版本
-
5.3.3 JSBundle 加载优先级
客户端加载 JSBundle 时按以下顺序优先选择:
-
热更新目录中的最新有效包(
沙盒/RNUpdates/{version}/index.bundle) -
应用内置的基础包(
assets/index.bundle)
5.4 服务端与管理后台
5.4.1 服务端功能
-
版本检查接口
GET /api/rn/update/check请求参数:appVersion(App 版本)、rnVersion(当前 RN 版本)、deviceId(设备唯一标识)响应示例:json
{ "hasUpdate": true, "updateInfo": { "version": "1.0.1", "downloadUrl": "https://xxx.com/updates/update\_v1.0.1.zip", "isForce": false, "description": "修复首页bug" } } -
更新包下载接口
GET /api/rn/update/download?version=1.0.1支持断点续传(Range头)、限速控制。 -
版本管理存储更新包元数据,支持按 App 版本范围、设备比例下发。
5.4.2 管理后台功能
-
热更新包上传(自动生成
manifest.json) -
配置更新策略(定向发布、灰度比例、强制更新)
-
查看更新数据统计(覆盖用户数、成功率、回滚率)
5.5 安全性保障
-
传输层:所有接口使用 HTTPS,防止中间人攻击
-
内容加密:更新包用 AES-256 加密,客户端内置密钥解密
-
接口签名 :客户端请求时携带
timestamp + deviceId + signature(签名算法:SHA256(timestamp + deviceId + appSecret)) -
权限控制:服务端校验 App 合法性(如包名、签名)
六、测试与灰度方案
6.1 测试环境
-
测试服务端:搭建与生产环境一致的测试集群,用于验证更新流程
-
测试场景:
-
正常更新(下载→校验→生效)
-
边界情况(弱网、断网重连、包损坏、版本不兼容)
-
回滚测试(故意上传错误包,验证自动回滚)
-
6.2 灰度发布
-
灰度策略:
-
初期 10% 设备(随机选取)
-
观察 24 小时,无异常则扩大至 50%
-
再观察 24 小时,无异常则全量发布
-
-
定向灰度:支持按设备 ID 列表定向下发(用于内部测试)
七、风险与应对
| 风险点 | 影响范围 | 应对方案 |
|---|---|---|
| 热更新包与原生版本冲突 | 所有使用该更新的用户 | 服务端严格校验 App 版本范围,不匹配则不下发 |
| 下载失败 / 超时 | 单用户更新失败 | 实现断点续传 + 3 次重试,非强制更新不阻塞用户 |
| JS 代码崩溃 | 单用户 RN 页面不可用 | 客户端捕获异常,自动回滚至旧版本,并上报日志 |
| 原生方法变更导致 RN 调用失败 | 所有使用该更新的用户 | 原生模块接口保持向后兼容,新增方法而非删除旧方法 |
| 更新包过大导致下载慢 | 低网速用户体验差 | 实现差量更新(仅下发变更文件),压缩资源文件 |