React Native 三方库「react-native-widget-picker」的鸿蒙化适配指南
开发工具: 华为云码道
react-native-widget-picker 用 React Native 接口打开鸿蒙系统的卡片管理页,让用户选择是否把应用卡片添加到桌面或负一屏。本文以 @react-native-ohos/react-native-widget-picker 1.1.2-0.0.1 为例,介绍源码准备、Form 卡片配置、RNOH 原生桥接和示例接入。
本适配保留上游 Android/iOS 之外的公开接口,在鸿蒙侧接入 HarmonyOS Form Kit。源码位于 AtomGit 配套仓库,文章中的代码以提交 4c8a185d9ca7789abe5fe6e830ea2c96667cb93d 为参考。

给初学者的阅读提示:
本文操作的插件目录是react-native-widget-picker/。HAR 是插件的鸿蒙原生模块包,HAP 是可以安装到设备上的应用包;Form 是鸿蒙桌面卡片的配置和运行单元。RNOH 负责连接 React Native 与鸿蒙,ArkTS 负责调用系统能力,TurboModule 负责 JS 与原生模块之间的调用。这个库只负责打开系统管理页。用户是否真的把卡片添加到桌面,要由用户在系统页面中确认,不能把接口返回
success当成已经添加完成。
一、插件简介与适配目标
桌面卡片需要两部分:应用先声明一个 Form,系统才能知道卡片的名称、页面和尺寸;React Native 页面再调用插件,打开这个 Form 对应的系统管理页。
鸿蒙实现提供两个方法:isRequestPinAppWidgetSupported() 用于判断当前设备是否支持这项能力,requestPinAppWidget(widgetClassKey) 用于打开指定卡片的管理页。widgetClassKey 必须等于宿主 form_config.json 中 Form 的 name。
返回值只有一个 message 字段。系统接受请求时返回 success;没有找到对应 Form 时返回 widget not found;设备不支持时返回 not supported。系统 API 抛出的错误会以 rejected Promise 传给 JavaScript。
Android 的实现使用 AppWidgetManager,鸿蒙实现使用 formProvider.getFormsInfo() 查找卡片,再调用 formProvider.openFormManager()。两端都需要宿主应用先完成自己的卡片声明。
二、环境准备
环境搭建参考社区文档:React Native OpenHarmony 环境初始化,准备 Node.js、React Native、RNOH、Harmony CLI 和 DevEco Studio;框架能力与版本关系见 ohos_react_native。
在开发电脑的终端执行以下命令,macOS 和 Windows PowerShell 均可使用:
node --version
npm --version
hdc list targets
前两条命令应输出版本号;hdc list targets 应列出已经连接的鸿蒙设备或模拟器。列表为空时,先处理调试授权或模拟器连接。
| 项目 | 版本或配置 | 用途 |
|---|---|---|
| Node.js | 满足仓库 engines 要求 | 执行 npm、Metro 和构建脚本 |
| React Native | 0.84.1 | 示例宿主的 RN 版本 |
| React | 19.2.3 | 与示例宿主配套 |
| RNOH / Harmony CLI | 0.84.3 | RN 与鸿蒙之间的运行层和命令行工具 |
| OHPM RNOH | 0.84.3 | 鸿蒙原生运行时依赖 |
| compileSdkVersion | 26.0.0 | 编译使用的 SDK |
| targetSdkVersion | 26.0.0 | 应用目标行为版本 |
| compatibleSdkVersion | 5.0.5(17) | 工程声明的最低兼容版本 |
| DevEco Studio | 26.0.0 Release(HarmonyOS SDK 26.0.0 / API 26) | 打开并构建 example/harmony 工程 |
| 适配包版本 | 1.1.2-0.0.1 | 鸿蒙 npm 包版本 |
| 原生实现 | ArkTS / C++ | Form 查询、系统调用与 RNOH 注册 |
| 插件产物 | harmony/widget_picker.har | 供宿主应用引入的原生模块包 |
react-native-widget-picker 的 Node engines 要求为 22.11.0 或更高版本;示例依赖 React Native 0.84.1。第一次接入建议保持表中的 RN、RNOH 和 CLI 组合,不要只升级其中一个。
2.1 开发套件版本与工程中的 SDK 版本配置
示例工程的 product 配置如下:
{
"name": "default",
"compileSdkVersion": "26.0.0",
"compatibleSdkVersion": "5.0.5(17)",
"targetSdkVersion": "26.0.0",
"runtimeOS": "HarmonyOS"
}
compileSdkVersion 决定电脑上需要安装的编译 SDK,compatibleSdkVersion 是应用声明的最低安装门槛。IDE、编译 SDK 和设备系统版本属于不同环节,最低兼容配置也不等于所有设备都已经验证。
三、从源码仓库开始准备适配工程
3.1 将上游源码同步到 AtomGit
本例上游为 retyui/react-native-widget-picker,基线版本为 1.1.2,采用 MIT 许可证。配套 AtomGit 仓库已经包含鸿蒙适配代码,运行示例时直接克隆即可。
如果要继续维护,可以在 AtomGit 创建自己的 Fork,保留上游许可证、源码和提交历史。本文介绍的是仓库根目录中的这个包,不涉及其他项目。
3.2 将代码拉取到宿主机
在准备存放项目的目录执行以下命令:
git clone https://atomgit.com/oh-react-native/react-native-widget-picker.git
cd react-native-widget-picker
git switch --detach 4c8a185d9ca7789abe5fe6e830ea2c96667cb93d
git status --short --branch
git clone 会创建仓库目录;git switch --detach 把源码固定到本文参考版本。已有修改时,先保存修改再切换提交。
3.3 在仓库根目录创建适配分支
配套仓库使用 main 分支。需要继续修改时,从固定提交创建维护分支:
git switch -c feat/ohos_widget_picker_1.1.2-0.0.1
cd react-native-widget-picker
插件的 package.json 位于仓库根目录。依赖安装、类型检查和构建命令都在这个目录执行。
3.4 说明现有 OHOS 适配结构
仓库已经包含 harmony/widget_picker/ 原生模块和 example/harmony/ 应用工程。先安装 JavaScript 依赖:
npm install
安装成功后应生成 node_modules/。仓库使用 Yarn 4 锁文件维护依赖,但 npm 也能按 package.json 安装;如果需要严格复现锁文件,使用仓库约定的 Yarn 版本。
直接打开 DevEco Studio 前,先将模板复制为本地构建配置。macOS 在仓库根目录执行:
if [ ! -f example/harmony/build-profile.json5 ]; then
cp example/harmony/build-profile.template.json5 example/harmony/build-profile.json5
fi
Windows PowerShell 执行:
if (-not (Test-Path 'example/harmony/build-profile.json5')) {
Copy-Item 'example/harmony/build-profile.template.json5' 'example/harmony/build-profile.json5'
}
模板不包含本机证书和密码。已有的 build-profile.json5 可能包含本机签名配置,提交前不要把它加入公开提交。
npm run codegen:harmony 会根据 src/NativeWidgetPickerModule.ts 生成 ArkTS/C++ 桥接文件;codegen 只生成协议桥接,不会替你实现 Form 查询逻辑。
3.5 适配后的项目目录
react-native-widget-picker/
├── package.json
├── src/
│ ├── index.ts
│ └── NativeWidgetPickerModule.ts
├── harmony/widget_picker/
│ ├── widget_picker.har
│ ├── index.ets
│ └── src/main/
│ ├── ets/RNWidgetPickerTurboModule.ts
│ ├── ets/RNWidgetPickerPackage.ets
│ ├── cpp/
│ └── module.json5
├── example/
│ ├── App.tsx
│ ├── metro.config.js
│ └── harmony/entry/
├── scripts/
│ ├── build-harmony.sh
│ └── bundle-harmony.cjs
├── README.harmony.md
└── VALIDATION.md
| 文件 | 主要职责 |
|---|---|
src/NativeWidgetPickerModule.ts | 声明 TurboModule 名称、方法和返回类型 |
harmony/widget_picker/src/main/ets/RNWidgetPickerTurboModule.ts | 判断能力、查找 Form、打开系统管理页 |
RNWidgetPickerPackage.ets / RNWidgetPickerPackage.h | 注册 ArkTS 与 C++ Package |
example/harmony/entry/src/main/resources/base/profile/form_config.json | 声明示例 Form |
EntryFormAbility.ets / WidgetCard.ets | 提供系统卡片的生命周期和界面 |
example/App.tsx | 调用插件并显示结果 |
3.6 详细步骤
- 在仓库根目录克隆代码、切换到固定提交,然后进入仓库根目录。
- 进入仓库根目录运行
npm install;安装失败时先处理 Node、网络或缓存目录问题。 - 检查
example/harmony/entry/src/main/resources/base/profile/form_config.json,确认ExampleWidget已声明。 - 检查
entry的module.json5,确认存在EntryFormAbility和ohos.extension.form元数据。 - 运行
npm run typecheck和npm run codegen:harmony,确认 TypeScript 和桥接协议没有错误。 - 使用
npm run build:har生成harmony/widget_picker.har;再用npm run build:example构建示例 HAP。 - 用 DevEco Studio 打开
example/harmony,完成 Sync 和签名配置,再运行entry。 - 在页面点击“检查支持状态”,再点击“打开示例卡片”;系统页面出现卡片后观察
Widget Picker预览和添加入口。 - 点击“验证不存在卡片”,确认缺少 Form 时能得到
widget not found,再检查git diff --check和git status。
四、JS/TS 接口与原生桥接分析
这个库的公开接口很小,但调用链包含宿主 Form 配置和鸿蒙系统页面。页面先调用 JS API,RNOH 把请求交给 ArkTS,ArkTS 查询当前应用发布的 Form,再调用系统管理页。
| 公开入口 | 模块协议 | OHOS 实现 | 返回行为 |
|---|---|---|---|
WidgetPicker.isRequestPinAppWidgetSupported() | WidgetPickerModule | deviceInfo.apiAvailable(18) 和设备类型检查 | 同步 boolean |
WidgetPicker.requestPinAppWidget(key) | WidgetPickerModule | formProvider.getFormsInfo()、openFormManager() | Promise<{message: string}> |
widgetClassKey 不是任意字符串,它必须等于 Form 的 name。示例中两者都是 ExampleWidget。如果传入不存在的名称,返回 {message: 'widget not found'}。
4.1 跨端架构与调用时序
4.1.1 一次完整调用的时序
4.2 数据模型与返回语义
src/NativeWidgetPickerModule.ts 的完整协议如下:
import type { TurboModule } from "react-native";
import { TurboModuleRegistry } from "react-native";
export interface Spec extends TurboModule {
isRequestPinAppWidgetSupported(): boolean;
requestPinAppWidget(widgetClassKey: string): Promise<{ message: string }>;
}
export default TurboModuleRegistry.getEnforcing<Spec>("WidgetPickerModule");
能力判断只返回布尔值。请求方法返回对象而不是直接返回 true/false,这样可以区分 success、widget not found 和 not supported。
success 只说明 openFormManager() 已调用,系统页面是否打开、用户是否点击添加,需要在页面上继续观察。这个接口没有回调告诉 JavaScript 用户最终是否完成桌面添加。
4.3 公开 API 与接口封装
鸿蒙入口 src/index.ts 导出名为 WidgetPicker 的默认 TurboModule:
<function show at 0x102acac40>
在业务页面中可以这样调用:
import {WidgetPicker} from '@react-native-ohos/react-native-widget-picker';
const supported = WidgetPicker.isRequestPinAppWidgetSupported();
if (supported) {
const result = await WidgetPicker.requestPinAppWidget('MyAppWidget');
console.log(result.message);
}
如果系统页面请求失败,Promise 会进入 catch。如果设备不支持,当前实现返回 {message: 'not supported'},不抛出异常。
4.4 宿主 Form 配置与清理
插件不会替宿主创建卡片。宿主需要同时配置 FormExtensionAbility、form_config.json 和卡片页面。下面是示例的 Form 配置:
{
"forms": [
{
"name": "ExampleWidget",
"displayName": "$string:widget_display_name",
"description": "$string:widget_desc",
"src": "./ets/widget/pages/WidgetCard.ets",
"uiSyntax": "arkts",
"window": {
"designWidth": 720,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDynamic": false,
"isDefault": true,
"updateEnabled": false,
"defaultDimension": "2*2",
"supportDimensions": ["2*2"]
}
]
}
name 是插件调用时使用的 key,src 指向卡片页面,defaultDimension 和 supportDimensions 定义卡片尺寸。
Form 扩展能力负责提供卡片数据:
import { formBindingData, FormExtensionAbility, formInfo } from '@kit.FormKit';
import { Want } from '@kit.AbilityKit';
export default class EntryFormAbility extends FormExtensionAbility {
onAddForm(_want: Want): formBindingData.FormBindingData {
return formBindingData.createFormBindingData({ title: 'Widget Picker' });
}
onAcquireFormState(_want: Want): formInfo.FormState {
return formInfo.FormState.READY;
}
}
卡片界面由 ArkUI 页面提供:
@Entry
@Component
struct WidgetCard {
build() {
Column({ space: 8 }) {
Text('Widget Picker')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor('#14233D')
Text('React Native OpenHarmony')
.fontSize(14)
.fontColor('#42516A')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.padding(16)
.backgroundColor('#F5F7FA')
}
}
页面退出时没有原生监听需要注销。业务页面只需要在异步 Promise 完成后确认组件仍然存在,避免卸载后的状态更新。
五、补全 OHOS 原生实现与工程配置
这一章分别说明原生 TurboModule、Form 权限和宿主工程注册。Form 是宿主应用的能力,插件 HAR 只负责调用系统 API。
5.1 在 RNWidgetPickerTurboModule.ts 中实现原生能力
原生文件位于 harmony/widget_picker/src/main/ets/RNWidgetPickerTurboModule.ts。完整实现如下:
// SPDX-License-Identifier: MIT
import deviceInfo from '@ohos.deviceInfo';
import { Want } from '@kit.AbilityKit';
import { formInfo, formProvider } from '@kit.FormKit';
import { UITurboModule } from '@rnoh/react-native-openharmony/ts';
import { WidgetPickerModule } from './generated/turboModules/WidgetPickerModule';
export class RNWidgetPickerTurboModule extends UITurboModule implements WidgetPickerModule.Spec {
static readonly NAME: string = 'WidgetPickerModule';
isRequestPinAppWidgetSupported(): boolean {
return deviceInfo.apiAvailable(18) && deviceInfo.deviceType !== 'wearable';
}
async requestPinAppWidget(widgetClassKey: string): Promise<{ message: string }> {
if (!this.isRequestPinAppWidgetSupported()) {
return { message: 'not supported' };
}
try {
const forms = await formProvider.getFormsInfo();
const form = forms.find((candidate: formInfo.FormInfo): boolean => candidate.name === widgetClassKey);
if (form === undefined) {
return { message: 'widget not found' };
}
const want: Want = {
bundleName: form.bundleName,
abilityName: form.abilityName,
parameters: {
'ohos.extra.param.key.form_dimension': form.defaultDimension,
'ohos.extra.param.key.form_name': form.name,
'ohos.extra.param.key.module_name': form.moduleName,
},
};
formProvider.openFormManager(want);
return { message: 'success' };
} catch (error) {
throw error;
}
}
}
isRequestPinAppWidgetSupported() 要求设备能使用 API 18,并排除 wearable。getFormsInfo() 返回当前宿主已发布的 Form 列表;代码按 name 查找目标卡片,再把名称、模块、Ability 和尺寸放进 Want,交给 openFormManager()。
5.2 声明插件和宿主权限
插件模块的 module.json5 没有额外运行时权限。示例 entry 声明 ohos.permission.INTERNET,用于 RN 宿主的调试连接;Form 查询和打开管理页本身不要求定位、电话等权限。
{"requestPermissions": [{"name": "ohos.permission.INTERNET"}]}
5.3 注册并导出插件
ArkTS Package 注册:
// SPDX-License-Identifier: MIT
import { RNOHPackage, UITurboModule, UITurboModuleContext } from '@rnoh/react-native-openharmony';
import { RNWidgetPickerTurboModule } from './RNWidgetPickerTurboModule';
export class RNWidgetPickerPackage extends RNOHPackage {
getUITurboModuleFactoryByNameMap(): Map<string, (ctx: UITurboModuleContext) => UITurboModule> {
return new Map<string, (ctx: UITurboModuleContext) => UITurboModule>([
[RNWidgetPickerTurboModule.NAME, (ctx: UITurboModuleContext): UITurboModule =>
new RNWidgetPickerTurboModule(ctx)],
]);
}
}
C++ Package 继承 codegen 生成的基类:
// SPDX-License-Identifier: MIT
#pragma once
#include "RNOH/generated/BaseReactNativeOhosReactNativeWidgetPickerPackage.h"
namespace rnoh {
class RNWidgetPickerPackage
: public BaseReactNativeOhosReactNativeWidgetPickerPackage {
public:
explicit RNWidgetPickerPackage(Package::Context ctx)
: BaseReactNativeOhosReactNativeWidgetPickerPackage(ctx) {}
};
} // namespace rnoh
HAR 入口导出 Package:
// SPDX-License-Identifier: MIT
export * from './ts';
export { RNWidgetPickerPackage } from './src/main/ets/RNWidgetPickerPackage';
export { RNWidgetPickerPackage as default } from './src/main/ets/RNWidgetPickerPackage';
package.json 中的自动链接元数据如下:
{
"harmony": {
"alias": "react-native-widget-picker",
"autolinking": {
"ohPackageName": "@react-native-ohos/react-native-widget-picker",
"etsPackageClassName": "RNWidgetPickerPackage",
"cppPackageClassName": "RNWidgetPickerPackage",
"cmakeLibraryTargetName": "rnoh_widget_picker"
}
}
}
宿主启用自动链接后,还要让 ArkTS/C++ 工厂调用生成的包工厂,并在 CMake 中包含 autolinking.cmake、执行 autolink_libraries(rnoh_app)。示例工程使用手动注册:ArkTS 工厂返回 new RNWidgetPickerPackage(ctx),C++ PackageProvider.cpp 返回 std::make_shared<RNWidgetPickerPackage>(ctx)。两种方式选择一种,避免重复注册。
5.4 检查 example 的 OHOS 应用结构
示例的 build-profile.json5 声明 entry 和 widget_picker 两个模块。下面列出不含本机签名材料的关键结构:
{"modules": [{"name": "entry", "srcPath": "./entry"}, {"name": "widget_picker", "srcPath": "../../harmony/widget_picker"}]}
entry 通过 file:../../../harmony/widget_picker.har 引入 HAR:
{
"name": "entry",
"version": "1.0.0",
"description": "Widget Picker example entry",
"main": "./src/main/ets/entryability/EntryAbility.ets",
"dependencies": {
"@rnoh/react-native-openharmony": "0.84.3",
"@react-native-ohos/react-native-widget-picker": "file:../../../harmony/widget_picker.har"
}
}
示例构建前会先生成 JS bundle,写入 entry/src/main/resources/rawfile/bundle.harmony.js。EntryAbility.ets 中的 appKey 为 WidgetPickerExample,需要与 example/index.js 注册的组件名称保持一致。
六、补全交付文件并提交适配分支
6.1 除代码外还要补全哪些文件
| 文件 | 应写清楚的内容 |
|---|---|
README.md / README.harmony.md | 能力范围、Form 配置和接入方式 |
README.OpenSource / LICENSE | 上游来源、版本和 MIT 许可证 |
package.json / src/index.ts | npm 入口、版本、公开导出和自动链接元数据 |
src/NativeWidgetPickerModule.ts | TurboModule 方法和返回类型 |
harmony/widget_picker/ | ArkTS、C++、OHPM 与 HAR 构建配置 |
example/harmony/ | Form、扩展 Ability、卡片页面和宿主注册 |
VALIDATION.md | 检查范围与能力限制 |
.gitignore | 构建缓存与本机签名配置 |
release HAR 开启 ArkTS 混淆并包含 C++ 桥接源码。修改原生代码后,需要重新生成 harmony/widget_picker.har,再让业务应用更新 HAR 依赖。
6.2 提交前检查
完成第八章的检查后,在仓库根目录执行:
git branch --show-current
git diff --check
git status --short
git diff --stat
git diff
核对 Form 名称、TurboModule 名称、包版本和依赖路径是否一致。example/harmony/build-profile.json5 可能包含本机签名信息,提交前只保留模板中的共享工程配置。
6.3 提交并推送到 AtomGit
需要提交自己的维护改动时,在仓库根目录执行:
git add package.json src harmony scripts example README.md README.harmony.md README.OpenSource VALIDATION.md
git diff --cached --check
git diff --cached --stat
git diff --cached
git commit -m "feat: support widget picker on OpenHarmony"
git remote -v
git push -u origin feat/ohos_widget_picker_1.1.2-0.0.1
origin 应指向自己有写权限的仓库。推送后可在 AtomGit 发起合并请求,说明 Form 配置、系统版本范围、示例接入方式和已知限制。
七、使用插件包内 example 演示接入
鸿蒙示例位于 example/。页面文件是 example/App.tsx,原生宿主位于 example/harmony;仓库没有 example/package.json,依赖由插件根目录的 package.json 和 workspace 安装提供。
7.1 本地适配时使用源码依赖
仓库的 Metro 配置如下:
const path = require('path');
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const {createHarmonyMetroConfig} = require('@react-native-oh/react-native-harmony/metro.config');
const projectRoot = __dirname;
const monorepoRoot = path.resolve(__dirname, '..');
module.exports = mergeConfig(
getDefaultConfig(projectRoot),
createHarmonyMetroConfig({reactNativeHarmonyPackageName: '@react-native-oh/react-native-harmony'}),
{
projectRoot,
watchFolders: [monorepoRoot],
transformer: {babelTransformerPath: require.resolve('./transformer')},
},
);
projectRoot 指向 example/,watchFolders 指向仓库根目录,这样示例既能解析自己的依赖,也能读取插件源码。原生侧的 entry 使用本地 harmony/widget_picker.har。
在仓库根目录安装依赖后,可以单独启动 Metro:
npm --prefix example start
Metro 只提供 JS 调试服务,不会替你安装或启动鸿蒙应用;构建和运行仍由 DevEco Studio 或第八章命令完成。
7.2 通过 AtomGit 源码构建并引入插件
需要让业务应用使用这个适配包时,在插件仓库根目录运行:
npm run typecheck
npm run build
npm run build:har
mkdir -p artifacts
npm pack --ignore-scripts --pack-destination artifacts
生成的 tgz 位于 artifacts/。将它复制到业务应用的 JavaScript 根目录并安装:
npm install ./react-native-ohos-react-native-widget-picker-1.1.2-0.0.1.tgz
npx react-native link-harmony --harmony-project-path ./harmony
ohpm install --cwd harmony
如果业务应用使用的原生目录不是 harmony/,请把命令中的路径改成实际位置。自动链接后还需要按 5.3 将生成的 ArkTS/C++ 工厂接入宿主。
7.3 调用接口实现卡片选择
在已接入原生模块和 Form 的业务应用中,可以使用下面的最小 App.tsx:
import {useState} from 'react';
import {Text, View, Button} from 'react-native';
import {WidgetPicker} from '@react-native-ohos/react-native-widget-picker';
export default function App() {
const [result, setResult] = useState('尚未请求');
async function openCard() {
try {
if (!WidgetPicker.isRequestPinAppWidgetSupported()) {
setResult('当前设备不支持卡片管理');
return;
}
const response = await WidgetPicker.requestPinAppWidget('MyAppWidget');
setResult(response.message);
} catch (error) {
setResult(String(error));
}
}
return (
<View style={{padding: 24}}>
<Button title="选择桌面卡片" onPress={() => void openCard()} />
<Text selectable>{result}</Text>
</View>
);
}
MyAppWidget 必须与业务应用 form_config.json 的 forms[].name 一致。业务应用仍需提供 FormExtensionAbility 和卡片页面,插件不会自动创建这些文件。
7.4 页面退出时处理资源
插件没有持续监听,也没有 removeListener()。页面只需在 Promise 完成时检查组件是否仍然存在;如果按钮可能被连续点击,可以在请求期间禁用按钮,避免重复打开系统页面。
八、验证、构建与鸿蒙设备运行效果
建议按“类型检查 → codegen → HAR → HAP → 页面操作”的顺序排查。这样可以区分 TypeScript、桥接生成、原生构建和设备运行问题。
8.1 分别验证插件与 example
在仓库根目录执行:
npm run typecheck
npm run build
npm run build:har
npm run verify:package
npm run build:example
typecheck 检查公开接口和 TurboModule 类型;build 生成 JS 类型与 CJS/ESM 产物;build:har 生成包含 ArkTS/C++ 实现的 HAR;build:example 生成带离线 bundle 的示例 HAP。需要检查打包后的 npm 依赖和自动链接时,再执行:
npm run verify:integration
这两个 verify 命令会检查包内容、HAR、类型解析、Metro alias、自动链接和应用构建。它们生成的临时目录属于构建产物,不要加入提交。
Windows PowerShell 可以先执行不依赖 Bash 的检查:
npm run typecheck
npm run build
npm run codegen:harmony
HAR/HAP 脚本内部使用 Bash,并默认查找 macOS 的 DevEco 安装目录。Windows 环境应在 DevEco Studio 中执行对应 Hvigor 任务,或按本机工具路径调整脚本;不能把 Bash 命令原样当作 PowerShell 命令。
8.2 确认设备连接
hdc list targets
首次连接设备时,在设备端确认调试授权。列表为空时检查 USB、调试模式和授权状态。
8.3 配置签名
- 用 DevEco Studio 打开
example/harmony; - 等待 Sync 完成,确认 Project 视图中存在
entry和widget_picker; - 打开 File > Project Structure > Signing Configs;
- 为
defaultproduct 配置本机签名; - 确认包名、证书、Profile 和设备匹配;
- 不要把含有本机证书路径和密码的
build-profile.json5提交到公开仓库。
HAR 本身不需要应用签名,安装到设备的是宿主应用生成的 HAP。
8.4 运行示例
macOS 在仓库根目录执行:
npm run build:example
脚本会先生成 bundle.harmony.js,再构建 entry HAP。产物目录为:
example/harmony/entry/build/default/outputs/default/
配置签名后可以直接在 DevEco Studio 运行 entry,也可以使用 hdc 安装应用包:
rnwp_device_id='你的设备ID'
hdc -t "$rnwp_device_id" install -r example/harmony/entry/build/default/outputs/default/entry-default-signed.hap
启动应用后,页面会先检查模块是否可用,并提供打开示例卡片和验证不存在卡片的按钮。
8.5 在设备上测试核心操作
- 点击“检查支持状态”,确认页面能显示原生模块状态和布尔值。
- 点击“验证不存在卡片”,确认返回
widget not found。 - 点击“打开示例卡片”,观察系统是否打开卡片管理页,并确认页面展示卡片预览和添加入口。
- 在系统页面中分别观察添加到负一屏和添加到桌面的入口;返回应用后再检查接口结果。
- 在不支持的设备类型或较低 API 环境中,确认能力判断返回
false或请求返回not supported。
系统管理页中的最终选择由用户完成,插件没有接口判断用户是否点击了添加按钮。
8.6 鸿蒙设备运行效果
示例页面可以检查支持状态,并打开 ExampleWidget 对应的系统卡片管理页。管理页显示卡片预览和“添加至负一屏”“添加至桌面”入口;返回应用后,成功请求显示 success。验证不存在的卡片时显示 widget not found。

系统卡片是否真正出现在桌面取决于用户在管理页中的操作。Form 的尺寸、卡片页面内容和不同设备形态需要由宿主应用分别确认。
九、FAQ:适配过程与使用问题
9.1 Missing SDK components
这个错误发生在 Hvigor 同步阶段。先确认 DevEco Studio 已安装 product 使用的 SDK 组件,再检查命令行和 IDE 使用的 SDK 路径是否一致,重新执行 Sync。
9.2 DevEco Studio 中看不到 entry
打开 example/harmony,不要只打开仓库根目录或 harmony/widget_picker。确认 build-profile.json5 同时声明 entry 和 widget_picker,并等待 Sync 完成。
9.3 无法手动签名
签名配置依附于应用的 entry 模块和 default product。确认打开了 example/harmony,SDK Sync 成功,证书和应用包名匹配;HAR 模块本身没有应用签名入口。
9.4 找不到原生模块 WidgetPickerModule
检查 JS 导入、TurboModule 名称、ArkTS Package、C++ Package、HAR 依赖和 CMake 链接是否一致。新增原生模块后要完整重建应用,JS 刷新无法补上未编译的原生代码。
9.5 点击按钮没有打开系统页面
先确认设备满足 API 18 和设备类型要求,再检查传入的 key 是否与 form_config.json 的 forms[].name 完全一致。还要确认宿主声明了 FormExtensionAbility 和 ohos.extension.form 元数据。
9.6 返回 widget not found
这表示系统返回的 Form 列表中没有同名卡片。检查 form_config.json 的文件位置、forms[].name、模块路径和 entry 构建内容;修改 Form 后重新构建应用。
9.7 返回 success 但桌面没有卡片
success 只表示系统管理页请求已提交。用户还需要在系统页面中选择“添加至负一屏”或“添加至桌面”;接口没有返回用户最终选择结果。
9.8 RN CLI 或 Metro 使用了错误入口
在仓库根目录安装依赖并使用 example/metro.config.js。Harmony Metro 需要解析 src 入口;不要同时加载上游包和适配包的不同实现。
9.9 改了 ArkTS,页面为什么没有变化
原生代码变化需要重新生成 codegen、构建 HAR 并重新构建宿主 HAP。JS Fast Refresh 只会更新 JS,不能替代原生重建。
9.10 编译成功但安装失败
检查 HAP 签名、设备调试授权、包名和 Profile 是否匹配,也要确认设备满足 compatibleSdkVersion。设备中已有不同证书签名的同包名应用时,先卸载旧应用或改用匹配的签名。
相关链接
更多推荐



所有评论(0)