自托管热更新CodePush方案
当前访问频次受限,请登录后继续访问
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() | UpdateState | Promise<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 | 注入受控依赖,适合在受控环境下替换发布历史和原生桥。 |
InstallMode | IMMEDIATE、ON_NEXT_RESTART、ON_NEXT_RESUME、ON_NEXT_SUSPEND | 枚举对象 | 定义更新包安装和生效时机。 |
UpdateState | RUNNING、PENDING、LATEST | 枚举对象 | 定义更新元数据查询范围。 |
SyncStatus / CheckFrequency / DeploymentStatus | 无 | 枚举对象 | 提供同步状态、检查频率和部署状态常量。 |
CodePushJSBundleProvider | UIAbilityContext、兜底 provider、bundle 文件名 | JSBundleProvider | 宿主侧 bundle 来源解析入口,优先加载沙箱更新包。 |
CodePushRestartManager | 重启监听器 | void / boolean | 提供可选的换包重启监听与接管扩展点。 |
CodePushBundle | 调试开关、bundle 文件名、版本覆盖等 | 静态工具类 | 供宿主侧查询当前沙箱包、清理更新包或覆盖调试配置。 |
SemverVersioning / BaseVersioning | SemverVersioning(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依赖宿主构建流程提供CodePushHashrawfile;未提供时该字段可以为空,其他配置字段不受影响。 - 换包边界:运行期更新覆盖 RN JS Bundle、样式、文案和随更新包下发的资源;原生安装包声明、权限、Ability、HAR、so 与 ArkTS 原生代码需要通过应用版本更新完成。
- 大包耗时:更新包下载、解压、差分合并和 SHA-256 校验会占用一定时间,业务侧建议展示进度并避免在关键交互中触发大包更新。
- 调试服务影响:接入 Metro 调试服务时,Metro bundle 可能覆盖沙箱更新包;验证换包效果时建议使用内置 bundle 与沙箱更新包组合。
开源 License
本项目基于 MIT License 开源,与上游 Soomgo-Mobile/react-native-code-push 协议一致。请自由地享受和参与开源。