开发工具: 华为云码道

本文配套仓库: oh-react-native/react-native-widget-picker

上游仓库: retyui/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 Native0.84.1示例宿主的 RN 版本
React19.2.3与示例宿主配套
RNOH / Harmony CLI0.84.3RN 与鸿蒙之间的运行层和命令行工具
OHPM RNOH0.84.3鸿蒙原生运行时依赖
compileSdkVersion26.0.0编译使用的 SDK
targetSdkVersion26.0.0应用目标行为版本
compatibleSdkVersion5.0.5(17)工程声明的最低兼容版本
DevEco Studio26.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 详细步骤

  1. 在仓库根目录克隆代码、切换到固定提交,然后进入仓库根目录。
  2. 进入仓库根目录运行 npm install;安装失败时先处理 Node、网络或缓存目录问题。
  3. 检查 example/harmony/entry/src/main/resources/base/profile/form_config.json,确认 ExampleWidget 已声明。
  4. 检查 entry 的 module.json5,确认存在 EntryFormAbility 和 ohos.extension.form 元数据。
  5. 运行 npm run typecheck 和 npm run codegen:harmony,确认 TypeScript 和桥接协议没有错误。
  6. 使用 npm run build:har 生成 harmony/widget_picker.har;再用 npm run build:example 构建示例 HAP。
  7. 用 DevEco Studio 打开 example/harmony,完成 Sync 和签名配置,再运行 entry。
  8. 在页面点击“检查支持状态”,再点击“打开示例卡片”;系统页面出现卡片后观察 Widget Picker 预览和添加入口。
  9. 点击“验证不存在卡片”,确认缺少 Form 时能得到 widget not found,再检查 git diff --check 和 git status。

四、JS/TS 接口与原生桥接分析

这个库的公开接口很小,但调用链包含宿主 Form 配置和鸿蒙系统页面。页面先调用 JS API,RNOH 把请求交给 ArkTS,ArkTS 查询当前应用发布的 Form,再调用系统管理页。

公开入口模块协议OHOS 实现返回行为
WidgetPicker.isRequestPinAppWidgetSupported()WidgetPickerModuledeviceInfo.apiAvailable(18) 和设备类型检查同步 boolean
WidgetPicker.requestPinAppWidget(key)WidgetPickerModuleformProvider.getFormsInfo()、openFormManager()Promise<{message: string}>

widgetClassKey 不是任意字符串,它必须等于 Form 的 name。示例中两者都是 ExampleWidget。如果传入不存在的名称,返回 {message: 'widget not found'}。

4.1 跨端架构与调用时序

React Native 页面

WidgetPicker 公开接口

WidgetPickerModule TurboModule

ArkTS 原生模块

查询宿主 Form

打开系统卡片管理页

返回 message

4.1.1 一次完整调用的时序
系统卡片管理页 HarmonyOS Form Kit RNWidgetPickerTurboModule WidgetPicker RN 页面 系统卡片管理页 HarmonyOS Form Kit RNWidgetPickerTurboModule WidgetPicker RN 页面 requestPinAppWidget('ExampleWidget') 转发方法与 key getFormsInfo() 返回 Form 列表 openFormManager(want) 系统接受请求 {message: 'success'} 显示结果

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.tsnpm 入口、版本、公开导出和自动链接元数据
src/NativeWidgetPickerModule.tsTurboModule 方法和返回类型
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 配置签名

  1. 用 DevEco Studio 打开 example/harmony;
  2. 等待 Sync 完成,确认 Project 视图中存在 entry 和 widget_picker;
  3. 打开 File > Project Structure > Signing Configs;
  4. 为 default product 配置本机签名;
  5. 确认包名、证书、Profile 和设备匹配;
  6. 不要把含有本机证书路径和密码的 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 在设备上测试核心操作

  1. 点击“检查支持状态”,确认页面能显示原生模块状态和布尔值。
  2. 点击“验证不存在卡片”,确认返回 widget not found。
  3. 点击“打开示例卡片”,观察系统是否打开卡片管理页,并确认页面展示卡片预览和添加入口。
  4. 在系统页面中分别观察添加到负一屏和添加到桌面的入口;返回应用后再检查接口结果。
  5. 在不支持的设备类型或较低 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。设备中已有不同证书签名的同包名应用时,先卸载旧应用或改用匹配的签名。


相关链接

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐