bravemobile-react-native-code-push:基于 React Native 鸿蒙生态的自托管热更新库项目

自托管热更新CodePush方案

分支3Tags2

bravemobile-react-native-code-push

本项目基于 Soomgo-Mobile/react-native-code-push 开发。如果在使用过程中有任何问题,可以在 GitCode 提交 Issue,会及时跟进。

项目介绍

bravemobile-react-native-code-push(npm 发布版本 12.4.0,与上游基线同号)是面向 HarmonyOS/OpenHarmony React Native 工程的自托管热更新库,适用于需要在运行期下发 RN JS Bundle、样式、文案和随包资源的业务场景。

鸿蒙适配 npm 包名为 @hxa-rn/react-native-code-push;其 HAR 内部 ohpm 模块名为 @oh-rn/react-native-code-push。JS/TS 使用者仍通过 @bravemobile/react-native-code-push 导入。调用方通过 codePush 高阶组件、sync、checkForUpdate、RemotePackage.download、LocalPackage.install 与宿主侧 CodePushJSBundleProvider 完成更新检查、下载、安装、生效和回滚闭环。

核心能力包括:

  • 自托管发布历史读取:由业务方实现 releaseHistoryFetcher,库侧负责版本、灰度、强制更新和失败包过滤。
  • 更新包下载与校验:支持完整包和差分包下载、解压、合并、SHA-256 完整性校验和本地元数据落盘。
  • 多种生效策略:支持立即生效、下次启动生效、回到前台生效和进入后台后生效。
  • 状态与进度回调:提供同步状态、下载进度、版本不匹配、更新成功、回滚和异常回调。
  • 宿主换包接线:通过 CodePushJSBundleProvider 让 RN 实例优先加载沙箱更新包,并在无可用更新时回落内置 bundle。

集成指南

1. 安装 JS 依赖

宿主 React Native 工程安装 npmjs 上的精确版本:

npm install @hxa-rn/react-native-code-push@12.4.1 --save-exact --legacy-peer-deps

安装后宿主 package.json 中的依赖应为:

{
  "dependencies": {
    "@hxa-rn/react-native-code-push": "12.4.0"
  }
}

安装后本包位于 node_modules/@hxa-rn/react-native-code-push/,发布包已经包含 harmony/dist/code_push.har。RNOH 0.72.143 的自动链接需要真实目录形态的 node_modules;如果使用 pnpm,请执行 pnpm install --node-linker=hoisted,避免普通 pnpm 符号链接导致 linked 0 libraries。JS 侧仍按原库名导入 @bravemobile/react-native-code-push,由 npm 包 package.json 中的 harmony.alias 映射到鸿蒙适配包。

2. 生成鸿蒙侧 JS Bundle

以下命令必须在宿主 React Native 工程根目录执行(该目录应包含业务侧 index.js;不要在本仓库目录执行)。命令中的 harmony/entry/... 是宿主工程的入口模块路径,不是本仓库的 harmony/code_push/ HAR 模块路径。

当前交付默认只生成并加载 bundle.harmony.js,不要求预编译 Hermes HBC。

cd <宿主RN工程根目录>

# 生成宿主工程使用的 JS bundle
npx react-native bundle-harmony --dev false \
  --bundle-output harmony/entry/src/main/resources/rawfile/bundle.harmony.js

@hxa-rn/react-native-code-push 是 npm 安装包,@oh-rn/react-native-code-push 是 HAR 的 ohpm 模块名,JS 业务代码继续使用 @bravemobile/react-native-code-push。如果宿主工程没有自动生成鸿蒙原生侧依赖,按下面步骤手动接入。 如果宿主明确启用 Hermes HBC,需要按宿主 RNOH 工具链另行生成 hermes_bundle.hbc;该文件不是本库当前交付和默认编译的必需品。

3. 接入鸿蒙原生模块(手动链接)

在宿主鸿蒙工程的 harmony/oh-package.json5(需要落到与 OH_MODULES_DIR 一致的 harmony/oh_modules)中加入:

{
  dependencies: {
    "@oh-rn/react-native-code-push": "file:../node_modules/@hxa-rn/react-native-code-push/harmony/dist/code_push.har",
  },
  overrides: {
    // 必须与宿主 RNOH har 保持一致:覆盖 code_push.har 内部指向(下例为 RNOH 官方预编译包,与本仓示例相同)
    "@rnoh/react-native-openharmony": "file:../node_modules/@react-native-oh/react-native-harmony/react_native_openharmony_release.har",
  },
}

如果宿主工程模板要求 harmony/entry/oh-package.json5 显式声明,也可在该文件中使用同一 HAR 路径;两处路径需保持一致。

cd <宿主RN工程根目录>\harmony
ohpm install

如果 ohpm install --all 长时间无输出,可用 ohpm install --all --fetch_timeout 10000 --retry_times 0 --log_level debug 观察依赖解析进度。Windows 下工程根目录的完整路径建议不超过 40 个字符,否则原生构建的中间文件路径可能超过系统上限。

4. 注册 RNPackage(ETS)

在 harmony/entry/src/main/ets/RNPackagesFactory.ets 中加入本库的 Package:

import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony/ts';
import { createRNOHPackages as createRNOHPackagesAutolinking } from './RNOHPackagesFactory';
import CodePushPackage from '@oh-rn/react-native-code-push';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    ...createRNOHPackagesAutolinking(ctx),
    new CodePushPackage(ctx),
  ];
}

5. 接入 Bundle Provider(换包入口)

在宿主承载 RNApp 的 ArkTS 页面(RNOH 工程模板为 harmony/entry/src/main/ets/pages/Index.ets)中接入 CodePushJSBundleProvider,让运行期优先加载 CodePush 已下载的更新包,未命中时回退到应用内置 bundle:

import {
  AnyJSBundleProvider,
  ResourceJSBundleProvider,
  RNApp,
} from '@rnoh/react-native-openharmony';
import { CodePushJSBundleProvider } from '@oh-rn/react-native-code-push';

const jsBundleProvider = new CodePushJSBundleProvider(
  this.rnohCoreContext!.uiAbilityContext,
  new AnyJSBundleProvider([
    new ResourceJSBundleProvider(this.rnohCoreContext!.uiAbilityContext.resourceManager, 'bundle.harmony.js'),
  ]),
  'bundle.harmony.js',
);

RNApp({
  rnInstanceConfig: { createRNPackages },
  appKey: 'YourApp',
  jsBundleProvider,
});

接入后即可在 JS/TS 代码中按原库名导入:

import codePush from '@bravemobile/react-native-code-push';

6. 检查鸿蒙 SDK 版本

code_push.har 面向 RNOH 工程使用。确认宿主 harmony/build-profile.json5 的 runtimeOS 为 HarmonyOS,并保证 compatibleSdkVersion、targetSdkVersion 与宿主 RNOH 版本要求一致;示例工程当前使用 compatibleSdkVersion 5.0.1(13)、targetSdkVersion 6.1.0(23)。

7. 编译、安装、启动

# DevEco Studio 打开 harmony/ 工程,等待后台任务完成
# File > Project Structure > Signing Configs 勾选自动签名
# 选 entry 运行配置,连接设备,Debug/Run

# 或命令行:
cd <宿主RN工程根目录>\harmony
hvigorw --mode module -p module=entry@default -p product=default -p buildMode=release assembleHap

# 产物:harmony/entry/build/default/outputs/default/entry-default-signed.hap

# 装机与启动(真机已连接,hdc 可见设备):
hdc file send entry-default-signed.hap /data/local/tmp/entry.hap
hdc shell bm install -p /data/local/tmp/entry.hap
hdc shell aa start -a EntryAbility -b <你的 bundleName>   # 见 AppScope/app.json5

使用说明

接入 CodePush 高阶组件

import codePush from '@bravemobile/react-native-code-push';

const codePushOptions = {
  checkFrequency: codePush.CheckFrequency.ON_APP_START,
  installMode: codePush.InstallMode.ON_NEXT_RESTART,
  releaseHistoryFetcher: async (request) => {
    const response = await fetch(`https://your-server.example.com/releases/${request.app_version}.json`);
    return await response.json();
  },
  onUpdateSuccess: (label) => {
    console.info('CodePush updated:', label);
  },
  onUpdateRollback: (label) => {
    console.info('CodePush rollback:', label);
  }
};

function App() {
  return <Root />;
}

export default codePush(codePushOptions)(App);

手动检查、下载与安装

const remotePackage = await codePush.checkForUpdate();

if (remotePackage) {
  const localPackage = await remotePackage.download((progress) => {
    console.info(`${progress.receivedBytes}/${progress.totalBytes}`);
  });

  await localPackage.install(codePush.InstallMode.IMMEDIATE);
  await codePush.notifyAppReady();
}

一站式同步

const status = await codePush.sync(
  {
    installMode: codePush.InstallMode.ON_NEXT_RESTART,
    mandatoryInstallMode: codePush.InstallMode.IMMEDIATE,
    ignoreFailedUpdates: true
  },
  (syncStatus) => {
    console.info('CodePush status:', syncStatus);
  },
  (progress) => {
    console.info('CodePush progress:', progress.receivedBytes, progress.totalBytes);
  }
);

接入鸿蒙 bundle provider

import {
  AnyJSBundleProvider,
  ResourceJSBundleProvider,
  RNApp
} from '@rnoh/react-native-openharmony';
import { CodePushJSBundleProvider } from '@oh-rn/react-native-code-push';

const jsBundleProvider = new CodePushJSBundleProvider(
  this.rnohCoreContext!.uiAbilityContext,
  new AnyJSBundleProvider([
    new ResourceJSBundleProvider(
      this.rnohCoreContext!.uiAbilityContext.resourceManager,
      'bundle.harmony.js'
    )
  ]),
  'bundle.harmony.js'
);

RNApp({
  rnInstanceConfig: {
    createRNPackages
  },
  appKey: 'YourApp',
  jsBundleProvider
});

可选接管重启通知

import { CodePushRestartManager } from '@oh-rn/react-native-code-push';

CodePushRestartManager.addRestartListener((request) => {
  console.info('CodePush restart requested:', request.reason);
  return false;
});

接口文档

接口参数 / 构造返回值 / 类型说明
codePush(options)(Component)CodePushOptions、React 组件React 组件包装根组件,并按检查频率驱动同步流程。
codePush.sync()SyncOptions、状态回调、进度回调、版本不匹配回调Promise<SyncStatus>执行检查、下载、安装的一站式同步流程。
codePush.checkForUpdate()版本不匹配回调Promise<RemotePackage | null>读取发布历史并返回可用远程更新包;无可用更新时返回 null。
RemotePackage.download()下载进度回调Promise<LocalPackage>下载更新包并保存元数据;ZIP 全量包解压、差分包合并后校验目录摘要,非压缩 bundle 直接保存,不执行目录摘要校验。
LocalPackage.install()安装模式、最短后台时长、回调Promise<void>按指定安装模式写入待生效状态,并在合适时机触发换包。
codePush.getConfiguration()无Promise<object>读取应用版本、设备标识、部署键、服务地址和可选内置包摘要。
codePush.getUpdateMetadata()UpdateStatePromise<LocalPackage | null>查询运行中、待安装或最新更新包元数据。
codePush.notifyAppReady()无Promise<StatusReport | void>确认当前更新包已经正常启动,避免下次启动回滚。
codePush.restartApp()是否仅在有待安装包时重启void请求重建 RN 实例以加载目标 bundle。
codePush.allowRestart() / disallowRestart()无void控制重启闸门,禁用期间的重启请求会等待重新允许。
codePush.clearUpdates()无void清除已下载更新包、待安装状态、失败集和本地更新元数据。
codePush.log()日志内容void输出带 CodePush 前缀的日志。
codePush.setUpTestDependencies()SDK、配置、原生桥替身void注入受控依赖,适合在受控环境下替换发布历史和原生桥。
InstallModeIMMEDIATE、ON_NEXT_RESTART、ON_NEXT_RESUME、ON_NEXT_SUSPEND枚举对象定义更新包安装和生效时机。
UpdateStateRUNNING、PENDING、LATEST枚举对象定义更新元数据查询范围。
SyncStatus / CheckFrequency / DeploymentStatus无枚举对象提供同步状态、检查频率和部署状态常量。
CodePushJSBundleProviderUIAbilityContext、兜底 provider、bundle 文件名JSBundleProvider宿主侧 bundle 来源解析入口,优先加载沙箱更新包。
CodePushRestartManager重启监听器void / boolean提供可选的换包重启监听与接管扩展点。
CodePushBundle调试开关、bundle 文件名、版本覆盖等静态工具类供宿主侧查询当前沙箱包、清理更新包或覆盖调试配置。
SemverVersioning / BaseVersioningSemverVersioning(releaseHistory);BaseVersioning 子类通过 super(releaseHistory, sortingMethod) 构造版本决策对象提供语义化版本排序、灰度命中和最新发布项选择能力。

快速验证(运行 Example)

前置条件

依赖 版本要求
Node.js >= 20(示例工程 engines 要求);DevEco Sync 时 ohpm preInstall 钩子也要用 PATH 中的 Node.js 20+
DevEco Studio 6.1.1
HarmonyOS SDK API 13+(example/harmony 的 compatibleSdkVersion 为 5.0.1(13))
路径 Windows 下仓库根目录的完整路径不超过 40 个字符,否则原生构建的中间文件路径会超过系统上限

运行步骤

1. 克隆仓库

git clone -b br_rnoh0.72 https://gitcode.com/hxa-rn/bravemobile-react-native-code-push.git
cd bravemobile-react-native-code-push

Windows 下可在克隆时指定短目录名,例如 git clone -b br_rnoh0.72 https://gitcode.com/hxa-rn/bravemobile-react-native-code-push.git C:\rcp。

2. 进入 example 目录,安装依赖

cd example
npm install --legacy-peer-deps

Example 从 npm 公仓安装 @hxa-rn/react-native-code-push@12.4.1,不需要在仓根执行 npm pack。

3. 生成 JS Bundle

npm run dev

产物:harmony/entry/src/main/resources/rawfile/bundle.harmony.js

4. 用 DevEco Studio 打开鸿蒙工程

  • 打开 DevEco Studio,选择 example/harmony 目录
  • 首次构建请在 File → Project Structure → Signing Configs 勾选 Automatically generate signature,Apply 后 Sync
  • 等待 Sync 完成

5. 编译并运行 HAP

在 DevEco Studio 中点击运行按钮,将 HAP 安装到设备。

example_auto(自动化用例工程)步骤相同,把 example 换成 example_auto 即可,两者的依赖与构建输出相互独立。

注意:Example 已预置 Package 注册、Metro alias 与 HAR 依赖,无需手动 Link。编 HAP 前必须先执行 npm run dev;改过 JS 后重新执行再运行。Example 已声明 ohos.permission.INTERNET,CodePush 运行时默认加载 bundle.harmony.js;如需验证热更新,请按「集成指南」第 5 步「接入 Bundle Provider(换包入口)」配置发布历史和 CodePushJSBundleProvider。

约束与限制

兼容性

项 版本 / 说明
React Native 0.72+,示例工程使用 0.72.5
RNOH 依赖 @react-native-oh/react-native-harmony 与 @rnoh/react-native-openharmony,版本以宿主工程 RNOH 配置为准
Node.js >=18;运行随库示例工程时建议使用 >=20
鸿蒙 SDK 以宿主 RNOH 工程为准;随库示例工程为 compatibleSdkVersion 5.0.1(13),targetSdkVersion 6.1.0(23)
工程模型 React Native + RNOH 工程,鸿蒙侧通过 ArkTS HAR 和 TurboModule 接入
支持设备类型 鸿蒙模块声明 default、tablet、2in1
上游形态 Android / iOS 原生模块与 JS 层 CodePush API
鸿蒙侧形态 RNOH TurboModule、ArkTS HAR、宿主 CodePushJSBundleProvider 接线

权限

本库依赖 ohos.permission.INTERNET,用于下载更新包、读取发布历史地址和上报部署状态。该权限为 normal 级别,无需运行时弹窗;宿主工程需要在入口模块声明网络权限。本库不申请位置、相机、麦克风、通讯录、电话、短信或账号类权限;更新包与状态数据保存在应用沙箱内。

功能约束

  • 自托管发布历史:本库不内置发布服务,调用方必须实现 releaseHistoryFetcher 并返回符合 CodePush 结构的发布历史数据。
  • bundle 文件名:更新包内 bundle 文件名应与 CodePushJSBundleProvider 第三个参数一致,默认使用 bundle.harmony.js。
  • 宿主接线要求:宿主必须把 CodePushJSBundleProvider 接入 RNApp,否则重建 RN 实例后仍可能加载内置 bundle。
  • 内置包摘要:getConfiguration().packageHash 依赖宿主构建流程提供 CodePushHash rawfile;未提供时该字段可以为空,其他配置字段不受影响。
  • 换包边界:运行期更新覆盖 RN JS Bundle、样式、文案和随更新包下发的资源;原生安装包声明、权限、Ability、HAR、so 与 ArkTS 原生代码需要通过应用版本更新完成。
  • 大包耗时:更新包下载、解压、差分合并和 SHA-256 校验会占用一定时间,业务侧建议展示进度并避免在关键交互中触发大包更新。
  • 调试服务影响:接入 Metro 调试服务时,Metro bundle 可能覆盖沙箱更新包;验证换包效果时建议使用内置 bundle 与沙箱更新包组合。

开源 License

本项目基于 MIT License 开源,与上游 Soomgo-Mobile/react-native-code-push 协议一致。请自由地享受和参与开源。

问题反馈渠道

使用过程中发现任何问题,欢迎提交 Issue,也欢迎提交 PR 参与共建。

项目介绍

自托管热更新CodePush方案

定制我的领域