前端开发APP之跨平台开发(ReactNative0.74.5)

目录

  • 一、方案背景与目标

  • 二、技术选型

  • 三、集成加载 React Native

  • 四、混编方案设计(iOS/Android 双端)

  • 五、洞窝 热更新功能设计

  • 六、测试与灰度方案

  • 七、风险与应对

一、背景与目标

1.1 背景

React Native(RN)具备跨平台、热更新能力。通过将 RN 混编进原生项目,可结合原生项目稳定性与 RN 快速迭代优势,减少应用商店审核依赖,紧急修复成本高的问题。

1.2 目标

一. 先跑通前期流程,RN项目融合到原生,并且实现第三方热更新。

  1. 前期跑通RN流程。 (1)搭建RN环境。 (2)创建React Native 项目。 (3)原生新工程空页面配置xcode。 (4)执行bundle exec pod install (后续混合到原生项目中)

  2. 实现 RN 模块与原生项目(iOS/Android)的无缝混编,支持原生调用 RN 页面、RN 调用原生能力。

  3. 原生加载 RN 页面(打包 JS Bundle)使原生项目跑通,并显示RN界面。

  4. 修改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
  1. 配置 Xcode 证书

  2. 最低版本号设置为 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 需要两个文件:

  1. Gemfile 文件在根目录 ./oc_rn_build 下载

  2. 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

  1. 配置 Xcode 证书

  2. 最低版本号设置为 iOS 13.4

    • React Native 0.74.5.0 最低支持 iOS 13.4
  3. 禁用 Xcode 的用户脚本沙盒

    • 打开 Xcode 项目

    • Build Settings 搜索 ENABLE_USER_SCRIPT_SANDBOXING

    • 将该选项的值设置为 NO

  4. 禁用 "基于视图控制器" 的控制方式,强制使用全局设置

    • 打开 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
  • 原生跳转 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.podspecyoga.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 核心流程

  1. 版本检查

    • 时机:App 启动时、RN 页面首次加载前

    • 逻辑:

  2. 更新包下载与校验

    • 下载:使用原生网络库(支持断点续传、进度监听)

    • 校验:下载完成后,计算文件 SHA256 与 manifest.json 中的 hash 比对,不一致则删除重试。

  3. 本地更新与存储

    • 存储路径:

      • iOS:NSDocumentDirectory/RNUpdates/{version}/

      • Android:getFilesDir()/rn_updates/{version}/

    • 版本记录:用 SharedPreferences(Android)或 UserDefaults(iOS)记录当前生效版本。

  4. 生效策略

    • 立即生效:下载完成后调用 reload() 重新加载 JSBundle

    • 下次启动生效:记录更新状态,下次打开 RN 页面时加载新包

  5. 回滚机制

    • 监听 RN 加载异常(如 onJSException

    • 发生异常时,切换至上次正常版本,并上报错误日志

    • 本地保留最近 2 个版本,超出则删除最旧版本

5.3.3 JSBundle 加载优先级

客户端加载 JSBundle 时按以下顺序优先选择:

  1. 热更新目录中的最新有效包(沙盒/RNUpdates/{version}/index.bundle

  2. 应用内置的基础包(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 灰度发布

  1. 灰度策略

    • 初期 10% 设备(随机选取)

    • 观察 24 小时,无异常则扩大至 50%

    • 再观察 24 小时,无异常则全量发布

  2. 定向灰度:支持按设备 ID 列表定向下发(用于内部测试)

七、风险与应对

风险点 影响范围 应对方案
热更新包与原生版本冲突 所有使用该更新的用户 服务端严格校验 App 版本范围,不匹配则不下发
下载失败 / 超时 单用户更新失败 实现断点续传 + 3 次重试,非强制更新不阻塞用户
JS 代码崩溃 单用户 RN 页面不可用 客户端捕获异常,自动回滚至旧版本,并上报日志
原生方法变更导致 RN 调用失败 所有使用该更新的用户 原生模块接口保持向后兼容,新增方法而非删除旧方法
更新包过大导致下载慢 低网速用户体验差 实现差量更新(仅下发变更文件),压缩资源文件
相关推荐
光影少年7 小时前
React Native 第三章
javascript·react native·react.js
EricStone7 小时前
iOS语音转换SDK相关记录
ios
qq_717410018 小时前
FAQ09934:相机prevew时候出现水印问题
android
望风的懒蜗牛8 小时前
android studio开发UniComponent<SurfaceView>组件
android·uni-app·android studio
奔跑吧 android9 小时前
【android bluetooth 协议分析 14】【HFP详解 2】【蓝牙电话绝对音量详解】
android·bluetooth·hfp·bt·ag
2501_916007479 小时前
Fastlane 结合 开心上架 命令行版本实现跨平台上传发布 iOS App
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张10 小时前
iOS 26 内存占用监控 多工具协同下的性能稳定性分析实战
android·macos·ios·小程序·uni-app·cocoa·iphone
奔跑中的蜗牛66610 小时前
一次崩溃率暴涨 10 倍的线上事故:从“无堆栈”到精准定位,到光速解决
android
Digitally10 小时前
7 种方法:如何将视频从电脑传输到安卓手机
android·电脑·音视频