开发工具: 华为云码道

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

上游仓库: williambout/react-native-proximity

react-native-proximity 将系统接近传感器事件封装为 React Native 接口,应用可以接收靠近或远离状态以及系统原始读数。本文以 @react-native-ohos/react-native-proximity 3.0.0-0.0.1 为例,介绍源码准备、OHOS 工程配置、原生实现和 example 设备运行。

本适配保留上游 Android/iOS 实现,新增鸿蒙支持。源码位于 AtomGit 配套仓库,文中的代码以提交 d0a9218a1052c6ec964ecf32c5b5f490937895c0 为参考。配套仓库包含对应的鸿蒙实现和示例工程,文章中的版本、接口与运行结果以该仓库为准。


在这里插入图片描述

给初学者的阅读提示:
本文会同时出现四个目录或产物:插件仓库是源码所在的位置,example/ 是用来演示插件的应用,HAR 是插件的鸿蒙原生模块包,HAP 是最终安装到手机上的应用包。除非特别说明,命令都在插件仓库根目录执行。

可以先记住几个名词:RNOH 是 React Native 连接鸿蒙的运行层,ArkTS 是鸿蒙原生代码使用的语言,TurboModule 是 React Native 调用原生能力的一种模块,Metro 是把 JS/TS 打包成应用资源的工具。调用链是:React Native 页面调用 JS/TS 接口,RNOH 把调用转给 ArkTS 原生模块,ArkTS 再调用 HarmonyOS 系统能力,结果沿原路返回页面。

一、插件简介与适配目标

接近传感器用于检测物体是否靠近设备感应区域。应用可以据此显示交互提示或调整自己的业务操作,但库本身不自动熄屏、模拟通话或控制亮度。

设备必须具备实际接近传感器和系统传感器能力。订阅请求发出后,收到的硬件事件才是状态判断依据。

事件对象包含 proximity:boolean 与 distance:number。鸿蒙契约为 distance=0 靠近,正数远离;distance 不承诺厘米单位。负数、NaN 和 Infinity 被丢弃。传感器失败只记录日志,不发送假的“远离”值。


二、环境准备

环境搭建参考社区文档:React Native OpenHarmony 环境初始化,完成 Node、对应 RN/RNOH 依赖、环境变量和 DevEco Studio 配置;框架能力与版本关系见 ohos_react_native。框架版本按下表选择,不直接套用最新分支。

完成后,在宿主机终端执行以下命令,确认当前工具链,并能发现目标设备:

node --version
npm --version
hdc list targets

node --version 和 npm --version 用来确认 Node.js 与包管理器可用;hdc list targets 用来确认电脑已经发现鸿蒙设备或模拟器。前两条命令只要能打印版本号即可,最后一条至少应出现一个设备 ID;如果列表为空,先处理 USB 调试授权或模拟器连接,再继续后面的构建。

工程使用的工具链和 SDK 配置如下:

项目版本或配置用途
Node.js24.12.0验证环境中的版本
React Native0.84.1鸿蒙宿主 RN 版本
React19.2.3与鸿蒙宿主配套
RNOH / Harmony CLI0.84.3JS 框架与鸿蒙 CLI
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 工程
适配包版本3.0.0-0.0.1package.json 中的交付版本
原生实现ArkTS / C++系统能力与 RNOH 桥接
插件产物harmony/proximity.har供宿主消费的 release HAR

在这张表里,React Native 和 React 是页面使用的 JavaScript 层;RNOH 和 Harmony CLI 负责把这层代码接到鸿蒙工程;compileSdkVersion 是编译时使用的 API,targetSdkVersion 是应用面向的系统行为版本,compatibleSdkVersion 是工程声明的最低兼容版本。HAR 是插件交付包,HAP 是可以安装到设备上的应用包。

2.1 开发套件版本与工程中的 SDK 版本配置

26.0.0 是本工程 compile/target 字段值,对应编译 API 26;5.0.5(17) 声明最低兼容 API 17。SDK、IDE 版本和手机系统版本是不同信息。

对应 example/harmony/build-profile.template.json5 的 product 配置如下,合并到现有工程时保留其他产品和模块:

{
  "name": "default",
  "compileSdkVersion": "26.0.0",
  "compatibleSdkVersion": "5.0.5(17)",
  "targetSdkVersion": "26.0.0",
  "runtimeOS": "HarmonyOS"
}

这组配置使用 API 26 SDK 编译,并以 API 26 为目标版本,最低兼容 API 17。安装后能否使用对应能力,还取决于设备系统和具体硬件,最低配置不代表对应版本设备已通过真机验证。


三、从源码仓库开始准备适配工程

3.1 将上游源码同步到 AtomGit

适配已有第三方插件时,先从 npm 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。

本例上游为 react-native-proximity,基线版本 3.0.0,采用 MIT 许可证。当前配套仓库已经同步了适配结果;直接接入时无需再次导入上游。

需要继续维护时,在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、package.json、LICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。

3.2 将代码拉取到宿主机

在安装了对应 RN/RNOH 工具链和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:

git clone https://atomgit.com/oh-react-native/react-native-proximity.git
cd react-native-proximity
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD

git clone 会创建 react-native-proximity/ 目录;cd 后的位置就是下文所说的插件仓库根目录,这里应能看到 package.json、harmony/ 和 example/。Git 仓库名与 npm 包名 @react-native-ohos/react-native-proximity 不是同一标识。

需要使用与本文相同的代码版本时,在没有未提交修改的仓库中切换到以下提交:

git switch --detach d0a9218a1052c6ec964ecf32c5b5f490937895c0

3.3 在仓库根目录创建适配分支

配套仓库的参考分支为 master。如果只是运行示例,保留固定提交即可;需要继续修改时,从该提交创建自己的工作分支。下面是建议的新分支名,不表示远端已经存在同名分支:

git switch -c feat/ohos_proximity_3.0.0-0.0.1
git branch --show-current

如果该分支已存在,使用 git switch feat/ohos_proximity_3.0.0-0.0.1 切换,并核对起点是否仍为本文参考版本。

3.4 自动补全 OHOS 适配结构

配套仓库已经包含 harmony/proximity/ 和 example/harmony/,直接运行示例时可以跳过结构生成。JS/TS 入口、原生 Package、C++ 桥接和宿主工程共同完成接线,不能只新建一个目录就称为适配完成。

库根目录提供 codegen:harmony 脚本,负责按目标 spec 生成 ArkTS/C++ 桥接;build:har 构建流程会调用它或对应的 build 脚本。消费者使用交付 HAR 时不再重复为该库运行 codegen。

3.5 适配后的项目目录

适配后的关键目录如下:

react-native-proximity/
├── package.json
├── index.harmony.js
├── src/NativeProximity.ts
├── harmony/
│   ├── proximity.har
│   └── proximity/
│       ├── index.ets
│       ├── oh-package.json5
│       └── src/main/
│           ├── ets/RNProximityTurboModule.ts
│           ├── ets/RNProximityPackage.ets
│           ├── cpp/
│           └── module.json5
├── example/App.tsx
├── example/harmony/entry/
├── scripts/build-harmony.sh
├── README.harmony.md
└── VALIDATION.md
文件主要职责
index.harmony.js业务公开接口及 RN 层行为
src/NativeProximity.ts类型/模块协议
harmony/proximity/src/main/ets/RNProximityTurboModule.ts系统能力与生命周期
RNProximityPackage.ets注册 UI TurboModule 工厂
RNProximityPackage.hC++ Package 与模块映射
example/harmony/entry可安装宿主应用
example/App.tsx完整示例页面与诊断操作

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

OHOS 实现需要遵循 JS/TS 层已有的方法、参数和回调约定。

对初学者来说,可以把这一层看成一条“传话链”:页面只负责调用公开函数,RNOH 负责把调用送到 ArkTS,ArkTS 负责访问系统 API,最后把成功结果或错误交回 JS/TS。只改页面代码而没有接好这条链,通常会得到“找不到原生模块”或“方法未实现”。先阅读 index.harmony.js 和 src/NativeProximity.ts,再查看 harmony/proximity/src/main/ets/RNProximityTurboModule.ts 的原生调用。适配其他已有平台的插件时,也应保留其公开接口和其他平台实现。

本例的对应关系如下:

公开入口模块协议OHOS 实现应保持的行为
addListener(callback)RNProximity.addListener + onSensorChangedsensor.on(PROXIMITY)第一个 JS 订阅启动原生
removeListener(callback)RNProximity.removeListener(最后一个)sensor.off(PROXIMITY, callback)只移除指定回调的订阅
removeListener() / subscription.remove()JS 监听集合管理按剩余监听数控制原生前者全清本库,后者只清当前订阅

4.1 跨端架构与调用时序

本例通过 RNOH UI TurboModule 执行系统调用,JS 模块名称为 RNProximity。ArkTS 和 C++ Package 同时参与注册;JS/npm 依赖安装成功不代表原生模块已经接入。

React Native 页面

JS/TS 公开接口

RNOH RNProximity

C++ 桥接

ArkTS UI TurboModule

HarmonyOS 系统能力

持续事件

4.1.1 一次完整调用的时序
系统服务 RNProximity JS 订阅/Provider RN 页面 系统服务 RNProximity JS 订阅/Provider RN 页面 注册回调或挂载 Provider 先注册 JS 事件接收 请求开始监听 注册本模块系统回调 状态或读数 onSensorChanged 更新页面 移除订阅或卸载 最后一个业务订阅结束时停止 注销本模块回调

4.2 数据模型与模块声明

以下声明来自 src/NativeProximity.ts:

import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {
  getConstants(): {EVENT_ON_SENSOR_CHANGE: string};
  addListener(): void;
  removeListener(): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('RNProximity');

事件对象包含 proximity:boolean 与 distance:number。鸿蒙契约为 distance=0 靠近,正数远离;distance 不承诺厘米单位。负数、NaN 和 Infinity 被丢弃。传感器失败只记录日志,不发送假的“远离”值。

4.3 公开 API 与接口封装

以下为 index.harmony.js 的完整实现:

'use strict';

const {DeviceEventEmitter, TurboModuleRegistry} = require('react-native');
const nativeModule = TurboModuleRegistry.getEnforcing('RNProximity');
const eventName = nativeModule.getConstants().EVENT_ON_SENSOR_CHANGE;
const listeners = new Set();

function addListener(callback) {
  if (typeof callback !== 'function') {
    throw new TypeError('RNProximity.addListener requires a callback');
  }
  const entry = {callback, subscription: DeviceEventEmitter.addListener(eventName, callback)};
  listeners.add(entry);
  const subscription = {
    remove() {
      if (!listeners.delete(entry)) return;
      entry.subscription.remove();
      if (listeners.size === 0) nativeModule.removeListener();
    },
  };
  entry.remove = subscription.remove;
  try {
    if (listeners.size === 1) nativeModule.addListener();
  } catch (error) {
    subscription.remove();
    throw error;
  }
  return subscription;
}

function removeListener(listener) {
  for (const entry of Array.from(listeners)) {
    if (listener === undefined || entry.callback === listener) entry.remove();
  }
}

// Fast Refresh must not leave the previous module's subscriptions behind.
if (module.hot) module.hot.dispose(() => removeListener());

module.exports = {addListener, removeListener};

4.4 模块协议与资源处理

原生端需要保持模块名、方法名及参数语义一致。RNProximity 是桥接标识,不等同于 npm 包名或 HAR 文件名。

跨平台类型允许 addListener 返回 void,因此最小页面沿用 removeListener(callback),不假设所有平台都返回订阅对象。页面卸载只移除自己的函数;不要在多页面场景调用无参 removeListener() 清掉其他业务订阅。


五、补全 OHOS 原生实现与工程配置

这一章只处理鸿蒙侧文件。插件 HAR 负责提供原生能力,example/harmony/entry 负责把这些能力装进一个可以安装的 HAP;两者都配置好,手机上的示例页面才有机会调用到系统功能。

5.1 在 RNProximityTurboModule.ts 中实现原生能力

业务沿用第四节公开接口;下面按源码说明原生层如何完成系统调用、结果转换和异常处理。

5.1.1 引入 RNOH 与系统能力

原生文件位于 harmony/proximity/src/main/ets/RNProximityTurboModule.ts。本例实现继承 UITurboModule,通过 this.ctx 获取宿主运行时上下文。

5.1.2 核心实现与错误处理

requested 表示业务仍希望订阅,observing 表示当前已注册系统传感器。退后台时暂停 observing,保留 requested;恢复前台时按 requested 重试。注销失败时保留 ownership 状态,让下一次停止或销毁继续清理,避免失去回调引用后重复注册。

以下为该原生文件的完整源码:

// SPDX-License-Identifier: MIT
import sensor from '@ohos.sensor';
import { UITurboModule, UITurboModuleContext, LifecycleState } from '@rnoh/react-native-openharmony/ts';
import { RNProximity } from './generated/turboModules/RNProximity';

interface ProximityConstants {
  EVENT_ON_SENSOR_CHANGE: string;
}

const EVENT_NAME = 'onSensorChanged';

export class RNProximityTurboModule extends UITurboModule implements RNProximity.Spec {
  static readonly NAME: string = 'RNProximity';
  private requested: boolean = false;
  private observing: boolean = false;
  private foreground: boolean = true;
  private destroyed: boolean = false;
  private unsubscribe: (() => void)[] = [];
  private readonly onSensorChange = (response: sensor.ProximityResponse): void => {
    if (!this.requested || !this.observing || !this.foreground || this.destroyed) return;
    const distance = response.distance;
    if (!Number.isFinite(distance) || distance < 0) {
      this.ctx.logger.warn('RNProximity: invalid sensor distance');
      return;
    }
    // Harmony's contract is 0 = near, > 0 = far; it does not promise centimetres.
    this.ctx.rnInstance.emitDeviceEvent(EVENT_NAME, { proximity: distance === 0, distance });
  };

  constructor(ctx: UITurboModuleContext) {
    super(ctx);
    this.foreground = ctx.rnInstance.getLifecycleState() !== LifecycleState.PAUSED;
    this.unsubscribe.push(ctx.rnInstance.subscribeToLifecycleEvents('BACKGROUND', () => {
      this.foreground = false;
      this.stopObserving();
    }));
    this.unsubscribe.push(ctx.rnInstance.subscribeToLifecycleEvents('FOREGROUND', () => {
      this.foreground = true;
      if (this.requested) this.startObserving();
    }));
  }

  getConstants(): ProximityConstants {
    return { EVENT_ON_SENSOR_CHANGE: EVENT_NAME };
  }

  addListener(): void {
    if (this.destroyed) return;
    this.requested = true;
    if (this.foreground) this.startObserving();
  }

  removeListener(): void {
    this.requested = false;
    this.stopObserving();
  }

  private startObserving(): void {
    if (this.observing || this.destroyed) return;
    try {
      this.observing = true;
      sensor.on(sensor.SensorId.PROXIMITY, this.onSensorChange);
    } catch (error) {
      this.observing = false;
      // No sensor/service failure must be mistaken for a real "far" reading.
      this.ctx.logger.error('RNProximity: proximity subscription failed', String(error));
    }
  }

  private stopObserving(): void {
    if (!this.observing) return;
    try {
      // Remove only our callback, preserving other sensor consumers in this app.
      sensor.off(sensor.SensorId.PROXIMITY, this.onSensorChange);
      this.observing = false;
    } catch (error) {
      // Keep ownership so a later stop/destroy can retry without duplicate registration.
      this.ctx.logger.warn('RNProximity: proximity unsubscribe failed', String(error));
    }
  }

  __onDestroy__(): void {
    this.destroyed = true;
    this.requested = false;
    this.stopObserving();
    this.unsubscribe.forEach((unsubscribe: () => void) => unsubscribe());
    this.unsubscribe = [];
    super.__onDestroy__();
  }
}
5.1.3 调用结束与实例销毁

跨平台类型允许 addListener 返回 void,因此最小页面沿用 removeListener(callback),不假设所有平台都返回订阅对象。页面卸载只移除自己的函数;不要在多页面场景调用无参 removeListener() 清掉其他业务订阅。

5.2 声明插件和宿主权限

本库上述系统能力无需额外运行时权限,不应沿用其他传感器或定位库的权限清单。

5.2.1 插件 HAR 的权限

harmony/proximity/src/main/module.json5 声明 HAR 模块,本库没有额外 requestPermissions。能力是否存在仍需按系统和设备判断。

5.2.2 应用 entry 的权限

example/harmony/entry/src/main/module.json5 中的 ohos.permission.INTERNET 用于 RN 宿主的调试连接,不是此库读取或操作系统能力所必需的授权。

最终安装的是宿主应用。修改 module 配置时应合并现有字段,保留原有 Ability 等配置;权限声明和运行时授权是两个步骤。

5.3 注册并导出插件

ArkTS Package 注册如下,对应文件为 harmony/proximity/src/main/ets/RNProximityPackage.ets:

// SPDX-License-Identifier: MIT

import { RNOHPackage, UITurboModule, UITurboModuleContext } from '@rnoh/react-native-openharmony';
import { RNProximityTurboModule } from './RNProximityTurboModule';

export class RNProximityPackage extends RNOHPackage {
  getUITurboModuleFactoryByNameMap(): Map<string, (ctx: UITurboModuleContext) => UITurboModule> {
    return new Map<string, (ctx: UITurboModuleContext) => UITurboModule>([
      [RNProximityTurboModule.NAME,
        (ctx: UITurboModuleContext): UITurboModule => new RNProximityTurboModule(ctx)],
    ]);
  }
}

C++ Package 对应文件为 harmony/proximity/src/main/cpp/RNProximityPackage.h:

// SPDX-License-Identifier: MIT
#pragma once

#include "RNOH/generated/BaseReactNativeOhosReactNativeProximityPackage.h"

namespace rnoh {
class RNProximityPackage
    : public BaseReactNativeOhosReactNativeProximityPackage {
 public:
  explicit RNProximityPackage(Package::Context ctx)
      : BaseReactNativeOhosReactNativeProximityPackage(ctx) {}
};
} // namespace rnoh

HAR 入口 harmony/proximity/index.ets 导出 Package:

export { RNProximityPackage, RNProximityPackage as default } from './src/main/ets/RNProximityPackage';
export * from './ts';

package.json 中的自动链接元数据如下:

{
  "harmony": {
    "alias": "react-native-proximity",
    "autolinking": {
      "ohPackageName": "@react-native-ohos/react-native-proximity",
      "etsPackageClassName": "RNProximityPackage",
      "cppPackageClassName": "RNProximityPackage",
      "cmakeLibraryTargetName": "rnoh_proximity"
    }
  }
}

宿主启用自动链接后,还必须调用生成的 ArkTS/C++ 工厂,并在 CMake 中包含 autolinking.cmake、执行 autolink_libraries(rnoh_app)。只生成文件不等于接入完成。自动和手动注册选择一种,避免重复注册同一个 Package。

5.4 检查 example 的 OHOS 应用结构

示例工程的 example/harmony/build-profile.template.json5 同时包含 entry 和库模块。下面只列出需要关注的配置,合并时保留其他模块:

{
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [
        {
          "name": "default",
          "applyToProducts": [
            "default"
          ]
        }
      ]
    },
    {
      "name": "proximity",
      "srcPath": "../../harmony/proximity",
      "targets": [
        {
          "name": "default"
        }
      ]
    }
  ]
}

配置后,在 DevEco Studio 中执行一次 Sync Project。如果 entry 模块正常识别,Project 视图中会出现 entry,并能打开 entry 模块的签名配置。


六、补全交付文件并提交适配分支

6.1 除代码外还要补全哪些文件

代码适配完成后,还需要整理安装说明、接口文档、版本记录和开源信息。接收仓库有专用模板时,按其格式填写:

文件应写清楚的内容
README.OpenSource上游名称、源码地址、基线版本与 MIT 许可
README.md / README.upstream.md项目说明、上游信息与平台入口
README.harmony.md鸿蒙安装、配套版本、权限、接口和限制
VALIDATION.md测试、构建、设备场景及未覆盖项
LICENSE保留上游许可证和版权
package.json / index 类型入口包名、版本、导出、构建脚本与发布文件;类型入口按实际 package.json
harmony/proximity/oh-package.json5OHPM 依赖、原生入口与版本
harmony/proximity.har已构建的 release HAR,含消费者所需 C++ 源码
.gitignore排除构建缓存和本机签名材料

README.OpenSource 记录库本身的来源与版本。本例上游为 react-native-proximity 3.0.0,交付包为 @react-native-ohos/react-native-proximity 3.0.0-0.0.1;RN 和 HarmonyOS SDK 版本写入环境说明。OAT.xml 的语法有效性与官方扫描结果应分别记录。

6.2 提交前检查

提交前先完成第八节的插件、example 和真机测试,再从根目录检查改动:

git branch --show-current
git diff --check
git status --short
git diff --stat
git diff

检查 diff 中的接口、平台注册和依赖变化,移除本机路径及签名信息,并使文档中的版本和分支与提交内容一致。

6.3 提交并推送到 AtomGit

文档和代码整理完成后,在自己的维护分支根目录暂存并提交。以下文件清单覆盖本库主要改动,新增其他文件时逐项补充:

git add package.json index.harmony.js src/NativeProximity.ts harmony scripts
git add README.md README.harmony.md README.OpenSource VALIDATION.md
git diff --cached --check
git diff --cached --stat
git diff --cached
git commit -m "docs: document OHOS support for react-native-proximity 3.0.0-0.0.1"
git remote -v
git branch --show-current
git push -u origin feat/ohos_proximity_3.0.0-0.0.1

DevEco 可能向 example/harmony/build-profile.json5 写入本机签名配置,需要在提交前从暂存内容中移除。暂存 example 时逐文件核对;推送时,origin 应指向自己有权限的 AtomGit 仓库,当前分支为前面创建的维护分支。

推送后在 AtomGit 发起合并请求,说明上游来源和版本、OHOS 实现范围、依赖及权限、测试环境、操作结果、已知限制,并附 Demo 运行图。目标分支和评审流程以接收仓库要求为准。


七、使用插件包内 example 演示接入

这一章站在使用者角度操作:先安装依赖,再让示例应用找到插件,最后点击页面按钮或执行调用。遇到问题时,先确认示例使用的是本章指定的包来源,避免把上游包和鸿蒙适配包混在一起。

仓库自带 example/,可以直接用来调试插件和体验接近传感器。鸿蒙实际页面为 example/App.tsx,原生工程为 example/harmony。

7.1 本地适配时使用源码依赖

源码联调使用 example/metro.config.js 和 scripts/build-harmony.sh;不要改成 file:../,否则可能跳过仓库已有的 Harmony 配置。Metro 的项目根目录、watchFolders、别名和入口配置共同决定 JS 来源;示例 OHPM 指向库源码模块。只修改 JavaScript 后可以重新打包;原生代码变化还需重新构建。

按仓库文档,在插件根目录安装对应工具依赖:

npm ci

安装完成后,终端会回到命令提示符,并在项目中生成或更新 node_modules 与锁文件;如果出现红色错误,先解决依赖安装问题,不要直接进入构建步骤。

下面是本库的 Harmony Metro 配置。上游 Android/iOS 配置不能直接用于鸿蒙:

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 = path.resolve(__dirname, '..');
module.exports = mergeConfig(
  getDefaultConfig(projectRoot),
  createHarmonyMetroConfig({reactNativeHarmonyPackageName: '@react-native-oh/react-native-harmony'}),
  {projectRoot, transformer: {babelTransformerPath: require.resolve('./transformer')}},
);

7.2 通过 AtomGit 源码构建并引入插件

当前没有可直接安装的 npm 版本。先按第三章从 AtomGit 获取固定提交,构建并打包,再在业务应用中安装生成的 tgz 文件。Git 仓库地址不能直接当作 npm 依赖使用。

以下命令在插件根目录执行;先完成 7.1 的依赖安装:

npm run build:har
mkdir -p artifacts
npm pack --pack-destination artifacts

产物名为 react-native-ohos-react-native-proximity-3.0.0-0.0.1.tgz。在采用 RN 0.84.1 / RNOH 0.84.3 的业务应用 JS 根目录执行,将绝对路径改为本机生成的文件位置:
把下一条命令中的 /absolute/path/to/...tgz 替换为你实际生成的 tgz 文件路径;示例路径不能直接照抄。

npm install /absolute/path/to/react-native-ohos-react-native-proximity-3.0.0-0.0.1.tgz
npx react-native link-harmony --harmony-project-path ./harmony
cd harmony
ohpm install

上述消费工程假设原生根目录为 harmony/。布局不同应调整 --harmony-project-path 和依赖路径。应用的 Harmony Metro 应使用 createHarmonyMetroConfig;包内 harmony.alias 支持上游导入名 react-native-proximity,TypeScript 的 paths 需要独立配置。下面最小页面直接使用适配包名,避免依赖 TypeScript 别名。

自动链接生成工厂后按 5.3 接入宿主。如果选择手动接入,ArkTS 注册 RNProximityPackage、C++ 注册 rnoh::RNProximityPackage,CMake 添加 HAR 内的 src/main/cpp 并链接 rnoh_proximity。完整手动工程见仓库 example/harmony/entry。

检查业务应用的依赖锁文件、实际 node_modules 和 OHPM 锁文件,确认 JS 与 HAR 来自同一版本,不要混用上游包的 JS 和另一个版本的原生实现。

7.3 调用接口实现接近传感器

下面是已安装适配包的 RNOH 业务应用可使用的最小 App.tsx,保留本库的公开 API。

若要在仓库源码示例中使用,按 example/App.tsx 的相对位置与现有源码解析配置调整 import,保留注册入口。不要覆盖 Android/iOS 的历史示例入口。

import React, {useCallback, useEffect, useRef, useState} from 'react';
import {Button, Text, View} from 'react-native';
import Proximity from '@react-native-ohos/react-native-proximity';

type Reading = {proximity: boolean; distance?: number};
export default function App() {
  const [reading, setReading] = useState<Reading | null>(null);
  const [listening, setListening] = useState(false);
  const [error, setError] = useState('');
  const alive = useRef(false);
  const started = useRef(false);
  const onChange = useCallback((value: Reading) => {
    if (alive.current) setReading(value);
  }, []);
  const stop = useCallback(() => {
    if (!started.current) return;
    Proximity.removeListener(onChange);
    started.current = false;
    if (alive.current) setListening(false);
  }, [onChange]);
  useEffect(() => {
    alive.current = true;
    return () => { alive.current = false; stop(); };
  }, [stop]);
  function start() {
    if (started.current) return;
    setReading(null);
    setError('');
    try {
      Proximity.addListener(onChange);
      started.current = true;
      setListening(true);
    } catch (cause) { setError(String(cause)); }
  }
  return (
    <View style={{padding: 24, gap: 16}}>
      <Text>{listening ? '已请求订阅' : '未订阅'}</Text>
      <Text>{reading ? (reading.proximity ? '靠近' : '远离') : '等待真实事件'}</Text>
      <Text>原始读数:{reading?.distance ?? '暂无'}</Text>
      <Text>{error}</Text>
      <Button title="开始监听" disabled={listening} onPress={start} />
      <Button title="停止监听" onPress={stop} />
    </View>
  );
}

7.4 页面退出时处理资源

跨平台类型允许 addListener 返回 void,因此最小页面沿用 removeListener(callback),不假设所有平台都返回订阅对象。页面卸载只移除自己的函数;不要在多页面场景调用无参 removeListener() 清掉其他业务订阅。


八、验证、构建与鸿蒙设备运行效果

建议按“工具链—设备—构建产物—页面功能”的顺序排查。先确认命令能运行,再确认 hdc 能看到设备,然后检查是否生成 HAP,最后才判断插件功能;这样可以快速分辨是环境问题、安装问题还是接口问题。 文中的 <device-id> 是占位符,请用 hdc list targets 输出的真实设备 ID 替换。

8.1 分别验证插件与 example

以下命令作为复现步骤,在已完成 7.1 安装的插件仓库根目录执行:

npm test
npm run typecheck
npm run build:har
npm run build:example
npm run verify:integration

verify:integration 会创建一个临时应用,检查 npm 包、HAR、自动链接和打包能否一起工作。命令运行前先准备好应用的签名配置;如果使用过临时工程,后续直接按该工程的构建流程继续即可。

测试覆盖近远映射、非法读数、多监听、注销、首次事件、失败、前后台、销毁和热更新;系统依赖使用模拟对象,设备传感器结果仍需在目标设备上确认。

源码测试覆盖接口和业务逻辑;系统能力与设备行为需要在鸿蒙环境中验证。

8.2 确认设备连接

hdc list targets

设备首次连接电脑时,需要在手机端确认调试授权。列表为空时,检查 USB 连接、调试模式和电脑授权。

8.3 配置签名

真机安装的 HAP 通常需要有效签名。推荐使用 DevEco Studio 为 entry 模块配置签名:

  1. 用 DevEco Studio 打开 example/harmony,不是仓库根目录;
  2. 等待工程 Sync 成功,确认 Project 视图中存在 entry 模块;
  3. 打开 File > Project Structure > Signing Configs;
  4. 为 default product 选择或生成签名;
  5. 确认设备、应用包名、证书和 Profile 匹配;
  6. 再回到终端执行构建,或在 DevEco 运行 entry。

签名材料保存在本机,公开仓库中只保留构建所需的通用配置。HAR 不需要应用签名;要验收最终交付 HAR,应使用独立 npm 消费宿主,而不是以源码宿主代替。

本库首次构建由脚本从 build-profile.template.json5 生成本地 build-profile.json5。首次直接用 DevEco 打开前也可复制该模板;已有本地配置时保留它,避免覆盖签名。

8.4 运行示例

从插件根目录构建源码示例:

npm run build:example

本库脚本生成离线生产 JS bundle,并构建默认 debug HAP,不要求另开 Metro 服务。典型产物位于:

example/harmony/entry/build/default/outputs/default/

未签名文件 entry-default-unsigned.hap 仅能说明打包完成。配置有效签名并生成 entry-default-signed.hap 后,可在仓库根目录安装:

hdc -t <device-id> install -r example/harmony/entry/build/default/outputs/default/entry-default-signed.hap

将 <device-id> 替换为设备列表中的实际 ID;安装后从设备应用图标启动,或在 DevEco 直接运行 entry。构建工具默认来自 macOS DevEco Studio,其他位置使用脚本支持的 DEVECO_ROOT、DEVECO_SDK_HOME、OHPM_BIN、HVIGOR_JS 配置。

8.5 在设备上测试核心操作

  1. 启动示例应用,初始状态应保持等待事件,不默认当成远离。
  2. 开始监听,遮挡并移开传感器区域,观察 proximity 与 distance。
  3. 停止后继续遮挡,检查计数不再增加,再重新订阅。
  4. 后台恢复、停止后后台恢复、组件卸载分别检查,记录尚未覆盖场景。

页面默认值或“已请求”文本不等于系统操作完成。查询要检查结果来源,监听要观察真实事件,窗口操作要观察实际系统显示。

8.6 鸿蒙设备运行效果

首次订阅和重新订阅都能收到传感器读数,示例中的远离状态显示为 distance=5、proximity=false。停止、重复停止、回到前台和冷启动按接口约定处理;靠近状态需要由设备传感器实际产生事件。

在这里插入图片描述

示例展示远离状态;靠近状态、停止期间的物理遮挡、后台遮挡时序、多回调删除以及无传感器设备,需要分别测试。


九、FAQ:适配过程与使用问题

9.1 Missing SDK components

这个错误发生在 Hvigor 同步阶段。通常需要检查构建工具使用的 SDK 路径、组件是否完整,以及 Hvigor 与 SDK 的版本是否匹配。

处理顺序:

  1. 在 DevEco Studio SDK Manager 中确认 API 26 组件已经下载完整;
  2. 检查构建脚本与 DevEco 使用的 SDK 路径是否一致;
  3. 根据实际安装位置设置 DEVECO_SDK_HOME 或其他工具覆盖变量;
  4. 确认 SDK 包中工具链、ETS、JS 和 native 组件可用;
  5. 重新执行 DevEco Studio Sync,并查看脚本输出的构建日志。
为什么连接 API 24 手机仍然会报这个错误?

因为 Sync 和 Compile 首先读取 Mac 本地 SDK。手机 API 版本只在部署、安装和运行时参与兼容判断。即使完全不连接手机,本地 SDK 不完整时也会得到相同错误。

当前工程的 compatibleSdkVersion 是 API 17,因此 API 24 在安装版本门槛上是满足的;但设备还必须支持本库使用的能力,并满足签名要求。

9.2 DevEco Studio 中看不到 entry 模块

插件的 harmony/proximity 目录是 HAR 模块,可安装应用的 entry 模块位于 example/harmony/entry。

请直接使用 DevEco Studio 打开:

react-native-proximity/example/harmony

如果仍看不到 entry,先解决 SDK Sync 错误,再检查应用 build-profile 的 modules 是否包含 ./entry。同步失败时,Project Structure 无法正确解析模块,签名界面也可能不显示 entry。

9.3 无法手动签名

签名配置依附于可构建的应用模块和 product。只有 HAR 插件模块、工程 Sync 失败,或者打开了错误目录时,DevEco Studio 都可能无法提供 entry 签名入口。

建议先确认:

  • 打开的是 example/harmony;
  • SDK 组件完整并且 Sync 成功;
  • entry 的模块类型为 entry;
  • default product 和 target 已正确关联;
  • 当前账号、证书和调试设备状态有效。

9.4 distance=5 是五厘米吗

不能这样解释。当前 SDK 契约用于区分 0 靠近与正数远离,没有固定厘米保证,不能套用 Android 最大量程判定。

9.5 找不到原生模块 RNProximity

这通常表示 JS 入口找不到已注册的原生模块。新增原生插件后需要重新构建应用;检查 npm 适配包、OHPM HAR、ArkTS Package、C++ Package 和 CMake 链接,特别是自动工厂是否真正由宿主调用。不要用一个固定返回值或开发刷新替代原生接入。

9.6 重复操作或页面退出后的行为

跨平台类型允许 addListener 返回 void,因此最小页面沿用 removeListener(callback),不假设所有平台都返回订阅对象。页面卸载只移除自己的函数;不要在多页面场景调用无参 removeListener() 清掉其他业务订阅。

9.7 编译成功但安装失败

常见原因包括:

  • HAP 未签名或使用了错误的 Profile;
  • 设备未加入调试设备列表;
  • 包名与签名 Profile 不匹配;
  • 安装包的 compatibleSdkVersion 高于设备 API;
  • 手机上已经安装了使用不同证书签名的同包名应用。

根据安装错误码区分签名、版本和包名冲突,再处理对应配置。

9.8 RN CLI 或 Metro 使用了错误入口

在对应依赖目录确认安装的是本文配套 RN/RNOH 和 Harmony CLI。使用 example/metro.config.js,检查包的 react-native/main 入口与 Harmony 平台选择;Metro 别名不会自动给 TypeScript 提供 paths。调试时不要同时加载上游包和适配包的不同实现。

9.9 AtomGit 依赖提示找不到分支或无权限

先检查 URL 是否指向已同步的目标仓库,再确认需要的分支已推送。本文已固定到 d0a9218a1052c6ec964ecf32c5b5f490937895c0;自己新建的 feat/ohos_proximity_3.0.0-0.0.1 不会自动存在于远端。私有 Fork 还需在本机配置 Git 认证。

9.10 改了本地 ArkTS,Demo 为什么没变化

先检查应用消费的是源码模块还是打包的 HAR:已安装的 tgz 不会自动读取工作区修改。本地源码联调按 7.1 的配置,测试交付包则重建 HAR、重新打包并更新宿主依赖与 OHPM 锁文件。原生代码变动后停止应用并重新构建运行,不能只做 JS Fast Refresh。

9.11 开始监听没有报错就表示传感器可用吗

不表示。公开 API 没有 Promise 或错误回调,系统失败记录在 RNProximity 日志;页面应保持“等待事件”,不伪造 false。


相关链接

Logo

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

更多推荐