从 0 到 1:react-native-screenshot-aware 鸿蒙适配实战(RNOH 0.84)
从 0 到 1:react-native-screenshot-aware 鸿蒙适配实战(RNOH 0.84)
记录一个 React Native 三方库完整适配 HarmonyOS/OpenHarmony(RNOH 0.84)的 0-1 过程: 从能力调研、工程搭建、原生模块编写,到 autolinking 接入、构建踩坑、真机验证。
0. 为什么写这篇文章
随着 OpenHarmony / HarmonyOS 生态的发展,React Native OpenHarmony(RNOH)作为跨平台框架已经具备相当的成熟度(本文基于 RNOH 0.84 版本线,即官方 ohos_react_native 仓库的 0.84 分支)。但 RN 生态中绝大多数三方库只实现了 iOS / Android 原生代码,鸿蒙侧需要额外适配。
react-native-screenshot-aware 是一个"小而美"的典型三方库:对外 API 简洁(一个 hook + 一个事件),原生逻辑聚焦单一能力(截图感知)。用它作为 0-1 适配的完整样例再合适不过——麻雀虽小,五脏俱全:TurboModule、Package 注册、autolinking、C++ 胶水层、构建链路、真机验证,一个都不少。
本文完整记录这次适配的决策、代码、踩坑与修复,希望能给准备适配 RN 三方库的读者一条可复制的路径。
1. 背景:RNOH 生态与三方库适配路径
1.1 RNOH 是什么
RNOH(React Native OpenHarmony)是 OpenHarmony 社区对 React Native 的官方移植,架构与 iOS / Android 对齐:
-
版本配套:RNOH 0.84 对应 React Native 0.84;npm 包为
@react-native-oh/react-native-harmony,鸿蒙侧 ArkTS 依赖为@rnoh/react-native-openharmony; -
架构:仅支持 New Architecture(TurboModule / Fabric),这一点在选库时就要注意——只支持旧架构的三方库无法直接适配;
-
工具链:DevEco Studio + hvigor 构建 + ohpm 包管理,autolinking 由
@react-native-oh/react-native-harmony-cli的link-harmony命令(或 hvigor 插件自动触发)完成。
1.2 三方库适配的本质
一个 RN 三方库在鸿蒙上"跑起来",本质要做三件事:
-
JS 侧零改动:库的 JS 代码通过
TurboModuleRegistry.getEnforcing('XXX')获取原生模块,只要鸿蒙侧提供同名的 TurboModule 即可; -
原生侧能力对齐:把 iOS / Android 的原生实现翻译成鸿蒙 ArkTS 能力(可能由于系统 API 差异需要调整语义,这是适配中最需要设计的部分);
-
构建接入:让 RNOH 工程能自动发现并链接这个库(autolinking 配置 + 可构建的 HAR 模块)。
2. 适配前的准备:先摸清"库"与"平台"两张牌
2.1 摸清库的对外接口
动工前先做接口分析(用 rnoh-lib-interface-analyzer 技能对库做了一次完整的接口规格梳理),结论是:
| 对外能力 | 类型 | 说明 |
|---|---|---|
useScreenshotAware(callback) | React Hook | 挂载订阅、卸载自动移除 |
ScreenshotAware.addListener(cb) | 方法 | 返回 EmitterSubscription |
ScreenshotAware.removeAllListeners() | 方法 | 清空监听 |
ScreenshotAwareEvent | 常量 | 事件名(原生侧 emitDeviceEvent 上报) |
原生侧只有一个 TurboModule:ScreenshotAware(name 固定),Spec 中 addListener / removeListeners 为满足契约的空实现——事件传递由各平台事件机制完成,不走 TurboModule 方法调用。这个信息非常关键:鸿蒙侧不需要实现任何方法调用,只需要"注册系统监听 + 触发同名事件"。
2.2 摸清鸿蒙平台能力(本次适配最核心的决策)
截屏感知在鸿蒙上有两条路,调研后结论非常明确:
| 方案 | 能力 | 三方应用可用性 | 结论 |
|---|---|---|---|
@ohos.screenshot | 截图系统接口 | ❌ 需要 ohos.permission.CAPTURE_SCREEN,仅系统应用可用 | 不可用 |
@ohos.display 的 captureStatusChange | 屏幕内容被获取(截屏 / 投屏 / 录屏)的状态变化监听 | ✅ API 12+,三方应用可用 | 采用 |
这是一个典型的"系统能力不可直接迁移,用等效事件替代"的适配决策:
-
iOS:
UIApplicationUserDidTakeScreenshotNotification(截图专属通知); -
Android 14+:
registerScreenCaptureCallback+DETECT_SCREEN_CAPTURE权限(截图专属 API); -
鸿蒙:
display.on('captureStatusChange'),回调true表示屏幕内容开始被获取——截屏、投屏、录屏都会触发,无法单独区分。
所以鸿蒙版的事件语义与 iOS 接近(都是"屏幕被截取"类通知),但与 Android 14 的"仅截图"存在差异。这个差异必须在 README 中明示,让接入方按业务语义设计逻辑。适配不是逐行翻译,而是能力对齐 + 语义说明。
2.3 环境清单
| 组件 | 版本 |
|---|---|
| React Native / RNOH | 0.84.x(0.84 版本线) |
| DevEco Studio | 6.1.0 及以上(API 17 Compile SDK) |
| 真机 | HarmonyOS 设备(API 26,ALN-AL00) |
| 构建 | hvigor + ohpm(华为镜像源) |
3. 搭建 RNOH 0.84 测试工程
适配的第一步是在本地复现"库 + 宿主工程"的运行环境。RNOH 官方仓库(ohos_react_native 0.84 分支)自带 RNOH084Demo 示例工程,直接以它为宿主工程改造,避免从零配置 RNOH 构建链。
3.1 工程结构
RNOH084Demo/ ├── App.tsx # RN 侧入口(appKey: RNOH084Demo) ├── index.js # JS bundle 入口 ├── node_modules/ │ └── react-native-screenshot-aware/ # 本次要适配的三方库(链接进 node_modules) └── harmony/ # 鸿蒙宿主工程 ├── oh-package.json5 # 项目级依赖(file: 指向 .har) ├── entry/ │ ├── oh-package.json5 # entry 级依赖 │ └── src/main/ets/ │ ├── PackageProvider.ets # RN 包注册入口(由 autolinking 驱动) │ ├── RNOHPackagesFactory.ets # link-harmony 自动生成 │ ├── entryability/EntryAbility.ets │ └── pages/Index.ets # RNApp 挂载页
3.2 接入库的两种形态
适配过程中,库的鸿蒙侧代码以两种形态在宿主工程中出现:
-
源码目录形态:
harmony/screenshot_aware/(ArkTS 模块源码,可直接被 hvigor 构建); -
产物形态:
harmony/screenshot_aware.har(assembleHar打包出的 HAR,ohpm 依赖的载体)。
💡 适配开发阶段用源码目录形态迭代最快;发布阶段把
screenshot_aware.har放进仓库,宿主工程通过ohpm install或 autolinking 引用。
4. 编写鸿蒙原生模块
4.1 TurboModule 实现(ScreenshotAwareTurboModule.ets)
核心逻辑:监听 display.captureStatusChange,回调为 true 时通过 RNOH 的 emitDeviceEvent 上报 ScreenshotAwareEvent,并在销毁时注销监听。
import { AnyThreadTurboModule, AnyThreadTurboModuleContext } from '@rnoh/react-native-openharmony';
import display from '@ohos.display';
export class ScreenshotAwareTurboModule extends AnyThreadTurboModule {
public static readonly NAME = 'ScreenshotAware';
public static readonly EVENT_NAME = 'ScreenshotAwareEvent';
private captureStatusCallback: (captureStatus: boolean) => void;
constructor(ctx: AnyThreadTurboModuleContext) {
super(ctx);
this.captureStatusCallback = (captureStatus: boolean): void => {
if (captureStatus) {
this.ctx.rnInstance.emitDeviceEvent(ScreenshotAwareTurboModule.EVENT_NAME, null);
}
};
this.registerCaptureStatusChange();
}
private registerCaptureStatusChange(): void {
try {
display.on('captureStatusChange', this.captureStatusCallback);
} catch (err) {
console.error(`[ScreenshotAware] register captureStatusChange failed: ${JSON.stringify(err)}`);
}
}
private unregisterCaptureStatusChange(): void {
try {
display.off('captureStatusChange', this.captureStatusCallback);
} catch (err) {
console.error(`[ScreenshotAware] unregister captureStatusChange failed: ${JSON.stringify(err)}`);
}
}
// Spec 要求的契约方法,事件传递由 RNOH 事件机制完成,实现为空(与 Android 一致)
addListener(eventName: string): void {}
removeListeners(count: number): void {}
__onDestroy__(): void {
super.__onDestroy__();
this.unregisterCaptureStatusChange();
}
}
要点:
-
继承
AnyThreadTurboModule:RNOH 的三方库 TurboModule 通常继承它(运行在任意线程),保持与宿主 JS 线程解耦; -
emitDeviceEvent:RNOH 提供的跨端事件上报 API,事件名必须与 JS 侧常量ScreenshotAwareEvent一致; -
__onDestroy__注销监听:避免模块销毁后监听泄漏。
4.2 Package 注册(ScreenshotAwarePackage.ets)
RNOH 通过 RNOHPackage 组织 TurboModule 工厂,把模块名映射到工厂函数:
import { RNOHPackage, AnyThreadTurboModule, AnyThreadTurboModuleContext } from '@rnoh/react-native-openharmony';
import { ScreenshotAwareTurboModule } from './ScreenshotAwareTurboModule';
export class ScreenshotAwarePackage extends RNOHPackage {
override getAnyThreadTurboModuleFactoryByNameMap():
Map<string, (ctx: AnyThreadTurboModuleContext) => AnyThreadTurboModule | null> {
return new Map<string, (ctx: AnyThreadTurboModuleContext) => AnyThreadTurboModule | null>([
[ScreenshotAwareTurboModule.NAME, (ctx) => new ScreenshotAwareTurboModule(ctx)],
]);
}
}
4.3 模块导出(Index.ets)
autolinking 生成的工厂文件使用 default import 引用 Package 类,因此 Index.ets 必须同时提供命名导出与默认导出(这是后续运行期踩坑的伏笔之一):
export { ScreenshotAwarePackage } from './src/main/ets/ScreenshotAwarePackage';
export { ScreenshotAwarePackage as default } from './src/main/ets/ScreenshotAwarePackage';
4.4 库级配置
-
oh-package.json5:声明对@rnoh/react-native-openharmony的依赖; -
module.json5:鸿蒙侧不需要申请CAPTURE_SCREEN等权限(该权限仅系统应用可用,申请了也无法使用); -
package.json(库根目录)新增harmony.autolinking配置,这是 autolinking 发现本库的钥匙:
{
"harmony": {
"autolinking": {
"ohPackageName": "@rnoh/react-native-screenshot-aware",
"etsPackageClassName": "ScreenshotAwarePackage"
}
}
}
5. autolinking 接入与第一次构建
5.1 autolinking 做了什么
在宿主工程执行 ohpm install(或构建时 hvigor 插件自动触发 link-harmony)后,RNOH 会扫描 node_modules 中所有配置了 harmony.autolinking 的包,自动完成:
-
在项目 / entry 级
oh-package.json5中写入file:依赖(指向库的 HAR 或源码目录); -
生成
entry/src/main/ets/RNOHPackagesFactory.ets——注册ScreenshotAwarePackage; -
生成
entry/src/main/cpp/autolinking.cmake与RNOHPackagesFactory.h——C++ 侧注册; -
生成
entry/src/main/cpp/RNOHPackagesFactory.h引用的库侧 C++ Package 头文件。
5.2 第一次构建:一连串连环坑
真正的"0-1"过程几乎注定不是一次成功的。这次适配的构建期踩坑,每一个都值得单独记录。
6. 构建踩坑实录(四连坑)
🕳️ 坑 1:autolinking 扫到两个 .har,包名被加了奇怪后缀
现象:生成的 RNOHPackagesFactory.ets 中 import 的是
import ScreenshotAwarePackage from '@rnoh/react-native-screenshot-aware--screenshot_aware';
包名后面多了一个 --screenshot_aware 后缀,随后 ohpm 报警告:local dependency ... does not match the actual name。
根因:autolinking 的 resolveHarPackageNames 逻辑中,当 harFilePaths.length > 1 时会给包名追加 --<har文件名> 后缀来区分同名 HAR。而我的库目录里恰好有两个 .har:
node_modules/react-native-screenshot-aware/harmony/screenshot_aware.har # 根目录产物 node_modules/react-native-screenshot-aware/harmony/screenshot_aware/build/.../screenshot_aware.har # 模块内构建产物
修复:删除模块内 build/ 构建产物,保证 harmony/ 下只有一个 .har,后缀自然消失。
💡 教训:不要把构建产物放在 autolinking 的扫描路径内。HAR 打包后的 build 目录务必清理(已加入 .gitignore)。
🕳️ 坑 2:纯 ArkTS 库没有 cpp 目录,CMake 直接报错
现象:
CMake Error at autolinking.cmake:7 (add_subdirectory): add_subdirectory given source ".../oh_modules/@rnoh/react-native-screenshot-aware/src/main/cpp" which is not an existing directory.
根因:autolinking 生成的 autolinking.cmake 无条件为每个 autolinking 库执行 add_subdirectory(src/main/cpp) 并链接 rnoh__react_native_screenshot_aware 这个 C++ target。我的库是纯 ArkTS 实现,没有 src/main/cpp 目录。
修复:为库补充一个空 C++ 模块,提供合法的 CMake target:
# harmony/screenshot_aware/src/main/cpp/CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE on)
file(GLOB screenshot_aware_SRC CONFIGURE_DEPENDS *.cpp)
add_library(rnoh__react_native_screenshot_aware SHARED ${screenshot_aware_SRC})
target_include_directories(rnoh__react_native_screenshot_aware PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(rnoh__react_native_screenshot_aware PUBLIC rnoh)
再放一个 empty.cpp(或后续真实实现)充当编译单元。
🕳️ 坑 3:C++ 侧缺 Package 类,头文件找不到
现象:CMake 配置通过后,C++ 编译报错:
RNOHPackagesFactory.h:8:10: fatal error: 'ReactNativeScreenshotAwarePackage.h' file not found
根因:autolinking 生成的 RNOHPackagesFactory.h 会 include 库侧的 Package 头文件并 make_shared<rnoh::ReactNativeScreenshotAwarePackage>(ctx),要求库在 rnoh 命名空间提供同名 C++ 类。
修复:提供一个继承 rnoh::Package 的最小 C++ 类:
// ReactNativeScreenshotAwarePackage.h
#pragma once
#include "RNOH/Package.h"
namespace rnoh {
class ReactNativeScreenshotAwarePackage : public rnoh::Package {
using Super = rnoh::Package;
public:
using Super::Super;
};
} // namespace rnoh
// ReactNativeScreenshotAwarePackage.cpp #include "ReactNativeScreenshotAwarePackage.h"
🕳️ 坑 4(最深):运行期 "Couldn't find Turbo Module on the ArkTs side"
现象:构建全部通过、HAP 装到真机、应用启动后 JS 侧报错:
[Error: Exception in HostObject::get for prop 'ScreenshotAware': Couldn't find Turbo Module on the ArkTs side, name: 'ScreenshotAware' Suggestions: Have you linked a package that provides this turbo module on the CPP side? [TypeError: Cannot read property 'useScreenshotAware' of undefined]
而设备日志却显示 ArkTS 侧明明创建成功了:
TurboModuleFactory.cpp:54> Creating Turbo Module: ScreenshotAware setupRNOHWorker::TurboModuleProvider TM created: ScreenshotAware
根因(这是全文最值得记录的一坑):RNOH 的 C++ 侧 TurboModuleFactory::create 流程是:
-
先在 ArkTS 侧 provider 中查
hasModule(name)/getModule(name)——成功拿到了 ArkTS 实例引用(所以有 "TM created" 日志); -
再遍历 C++ 侧
TurboModuleFactoryDelegate列表,调用delegate->createTurboModule(ctx, name)把 ArkTS 实例包装成 C++ TurboModule; -
如果所有 delegate 都不认这个模块名(返回 nullptr),且 ArkTS 实例引用已存在 → 抛出
FatalRNOHError,suggestion 指向 "CPP side"。
官方 codegen 生成的 BaseXxxPackage 会自动实现 createTurboModuleFactoryDelegate(),为每个 ArkTS 模块生成 ArkTSTurboModule 包装。但手动适配时,这个 delegate 不会凭空出现——我的 C++ Package 类只继承了 rnoh::Package,没有重写该方法,于是 C++ 侧没人能把 ArkTS 实例包装起来。
修复:重写 createTurboModuleFactoryDelegate,提供将 ArkTS TurboModule 包装为 ArkTSTurboModule 的 delegate:
// ReactNativeScreenshotAwarePackage.h
#pragma once
#include "RNOH/Package.h"
#include "RNOH/ArkTSTurboModule.h"
#include "RNOH/TurboModuleFactory.h"
namespace rnoh {
class ScreenshotAwareTurboModuleFactoryDelegate : public TurboModuleFactoryDelegate {
public:
SharedTurboModule createTurboModule(Context ctx, const std::string& name) const override {
if (name == "ScreenshotAware") {
return std::make_shared<ArkTSTurboModule>(ctx, name);
}
return nullptr;
}
};
class ReactNativeScreenshotAwarePackage : public rnoh::Package {
using Super = rnoh::Package;
public:
using Super::Super;
std::unique_ptr<TurboModuleFactoryDelegate> createTurboModuleFactoryDelegate()
override {
return std::make_unique<ScreenshotAwareTurboModuleFactoryDelegate>();
}
};
} // namespace rnoh
💡 适配纯 ArkTS TurboModule 的黄金法则:ArkTS 侧实现业务逻辑,C++ 侧只需一个把 ArkTS 实例桥接成
ArkTSTurboModule的 delegate。ArkTSTurboModule(ctx, name)这个包装类会通过ctx.arkTSTurboModuleInstanceRef把调用转发到 ArkTS 实例——这就是"ArkTS 原生实现 + C++ 胶水"的最终形态。
7. 真机运行验证
7.1 部署与启动
# 1. 构建 HAP hvigorw --mode module -p product=default assembleHap --no-daemon # 2. 安装到真机 hdc install entry/build/default/outputs/default/entry-default-signed.hap # 3. 启动 hdc shell aa start -a EntryAbility -b com.rnoh084.demo
7.2 验证要点
-
进程存活、无崩溃:
hdc shell ps -A | grep rnoh084; -
RN 实例正常:日志出现
RNInstanceCAPI::startSurface / measureSurface; -
TurboModule 注册成功:日志出现
Creating Turbo Module: ScreenshotAware setupRNOHWorker::TurboModuleProvider TM created: ScreenshotAware
且不再有 Couldn't find Turbo Module / useScreenshotAware of undefined 错误。
最终在真机(ALN-AL00,HarmonyOS API 26)上运行效果如下:

7.3 一个容易被忽略的坑:真机连不上 Metro
RNOH 的 debug 模式需要 Metro 提供 JS bundle。真机通过 USB 连接时,设备上的 localhost:8081 指向的是设备自身,必须做端口反向转发:
hdc reverse tcp:8081 tcp:8081
否则应用会卡在 "Cannot connect to Metro"(日志中 URL: localhost:8081 且连接失败),看起来像适配失败,实际只是网络问题。
8. 收尾:文档、版本配套与发布
适配不是"能跑就行",要让它可交付、可持续:
-
能力差异文档化:新增
README.OpenHarmony.md/README.OpenHarmony_CN.md,明示鸿蒙版的能力差异(captureStatusChange会覆盖投屏/录屏、无法区分截图与录屏、API 12+ 要求等); -
版本配套表格:React Native 0.84 ↔ RNOH 0.84 ↔ @rnoh/react-native-openharmony 0.84 ↔ Compile SDK API 17,写进 README 防止接入方配错;
-
发布形态:
harmony/screenshot_aware.har随仓库发布,宿主工程通过package.json的harmony.autolinking自动发现; -
最终提交:一次 commit 包含 ArkTS 模块 + C++ 胶水 + autolinking 配置 + 文档(本仓库已提交并推送)。
9. 经验总结
| 阶段 | 关键动作 | 一句话经验 |
|---|---|---|
| 选库 | 确认库是 New Architecture(TurboModule) | 旧架构库适配成本高得多 |
| 接口分析 | 梳理对外 API / 事件名 / Spec 契约 | 事件驱动型库的适配重点是"同名事件",不是方法实现 |
| 能力调研 | 对比 iOS / Android / 鸿蒙系统 API | 系统能力不可用时找等效事件替代,并文档化语义差异 |
| 工程搭建 | 复用 RNOH 官方 Demo 做宿主 | 0-1 阶段别从零配构建链 |
| 原生实现 | AnyThreadTurboModule + emitDeviceEvent | ArkTS 侧只需业务逻辑 + 事件上报 |
| autolinking | 配置 harmony.autolinking + 单一 .har | 构建产物别放扫描路径内 |
| C++ 胶水 | Package 类 + createTurboModuleFactoryDelegate | 纯 ArkTS 库也要补 C++ 桥接,这是最容易漏的 |
| 运行验证 | 真机 + hdc reverse + 日志关键字 | 区分"适配 bug"与"环境问题"(Metro 连接) |
| 交付 | 双语 README + 版本配套表 + .har | 让接入方"照着配就能跑" |
最终形态一句话:一个纯 ArkTS 的 RNOH 三方库 = ScreenshotAwareTurboModule.ets(业务)+ ScreenshotAwarePackage.ets(注册)+ 一个 C++ Package 类与 ArkTSTurboModule 桥接 delegate(胶水)+ harmony.autolinking 配置(发现机制)。
希望这篇 0-1 记录能帮到正在把 RN 三方库搬上鸿蒙的你。下一个库,会更快。
10. 参考资料与 RNOH 相关组织
按代码托管平台排序:AtomGit 优先,随后为 GitHub 等平台。
10.1 组织与官方仓库(AtomGit)
| 组织 / 仓库 | 地址 | 说明 |
|---|---|---|
| CPF-RN 组织 | CPF-RN - 开源代码托管,代码协作 - AtomGit | RNOH 中文化与技能集组织 |
| ohos_react_native | ohos_react_native:基于 React Native 生态的 OpenHarmony 适配项目 - AtomGit | RNOH 0.84 版本线官方仓库(含 RNOH084Demo 示例工程) |
| skills 技能集 | skills:基于 React Native 与 HarmonyOS 的开发自动化工具集合项目 - AtomGit | RNOH 适配相关技能(autolinking、接口分析、文档生成等) |
| oh-react-native 组织 | oh-react-native - 开源代码托管,代码协作 - AtomGit | 鸿蒙适配库组织 |
| react-native-screenshot-aware(鸿蒙适配版) | react-native-screenshot-aware:基于 React Native 的跨平台截图感知项目 - AtomGit | 本文适配目标库的仓库 |
10.2 官方组件与文档
| 组件 / 文档 | 地址 | 说明 |
|---|---|---|
| @react-native-oh/react-native-harmony(npm) | https://www.npmjs.com/package/@react-native-oh/react-native-harmony | RNOH 前端包(0.84.x) |
| @react-native-oh/react-native-harmony-cli(npm) | https://www.npmjs.com/package/@react-native-oh/react-native-harmony-cli | link-harmony / autolinking 命令行工具 |
| @rnoh/react-native-openharmony(ohpm) | https://ohpm.openharmony.cn | 鸿蒙侧 ArkTS 运行时依赖(0.84.x) |
| RNOH 官方仓库(GitHub) | https://github.com/react-native-oh/react-native-harmony | RNOH 上游源码与文档 |
| React Native OpenHarmony 官网 | https://react-native-oh.github.io/react-native-harmony | 官方文档站点 |
10.3 上游库
| 项目 | 地址 | 说明 |
|---|---|---|
| react-native-screenshot-aware(上游) | https://github.com/a29740/react-native-screenshot-aware | 本文适配前的原始库(iOS / Android) |
更多推荐



所有评论(0)