前言

本次参加开源鸿蒙跨平台开发学习活动,选择了 React Native 开发 HarmonyOS技术栈,在学习的同时顺便整理成一份系列笔记,记录从环境到开发的全过程。react-native-openharmony 目前已经比较成熟,能够让 RN 项目直接运行在鸿蒙设备或模拟器上,对于想做跨平台开发的人来说,是一个不错的选择。

本篇作为第一篇,重点介绍环境准备与项目初始化流程。

一、为什么选择 React Native 开发 HarmonyOS?

React Native 的优势在于生态成熟、组件丰富、工具链完善,开发效率高。通过社区的适配层,RN 可以在 Android、iOS、HarmonyOS 三端同时运行,这意味着:

  • 一套代码多端复用;

  • 保留现有 RN 技术栈;

  • 降低适配鸿蒙的门槛;

  • 更适合前端或跨端背景的开发者。

就比如我本身就是前端开发者,有Vue和RN开发经验,如果要开发HarmonyOS应用,使用RN的跨平台开发就是一个不错的选择。

RNOH架构

如图,React Native for OpenHarmony 在 React Native 的新架构(0.68 以及之后的版本)的基础上,进行了鸿蒙化的适配。按照功能可以进行如下的划分:

  • RN 应用代码:开发者实现的业务代码。
  • RN 库代码:在 React Native 供开发者使用的组件和 API 的封装与声明。
  • JSI(JavaScript Interface):JavaScript 与 CPP 之间进行通信的 API。
  • React Common:所有平台通用的 CPP 代码,用于对 RN 侧传过来的数据进行预处理。
  • OpenHarmony 适配代码:接收并处理 React Common 传过来的数据,对接原生的代码,调用 ArkUI 的原生组件与 API。主要包括了两个部分:分别是 TurboModule 与 Fabric。
  • OS 代码:对接系统底层功能,根据适配层代码传过来的数据进行渲染,或完成对应的功能。

二、环境准备

正式开始前,需要准备以下工具:

环境 说明
Node.js 18+ 建议使用 nvm 管理
React Native CLI 用于创建 RN 项目
OpenHarmony SDK(必装) 可通过 DevEco Studio 或 CLI 安装
HarmonyOS 模拟器或真机 真机可直接 USB 调试
react-native-openharmony RN 的鸿蒙适配层

版本兼容性确认

  • React Native ≥ 0.71

  • react-native-openharmony ≥ 根据OpenHarmony SDK对应

  • DevEco Studio ≥ 5.1(可选)

  • OpenHarmony SDK ≥ API 12(推荐)

HDC环境变量设置

hdc 是 OpenHarmony 为开发人员提供的用于调试的命令行工具,鸿蒙 React Native 工程使用 hdc 进行真机调试。hdc 工具通过 OpenHarmony SDK 获取,存放于 SDK 的 toolchains 目录下,请将 {DevEco Studio安装路径}/sdk/{SDK版本}/openharmony/toolchains 的完整路径添加到环境变量中。

  • windows 环境变量设置方法:
  1. 此电脑 > 属性 > 高级系统设置 > 高级 > 环境变量中,编辑系统变量path,添加hdc工具路径。
  2. 此电脑 > 属性 > 高级系统设置 > 高级 > 环境变量中,添加 HDC 端口变量名为:HDC_SERVER_PORT,变量值可设置为任意未被占用的端口,如 7035

 

  • macOS 环境变量设置方法:
  1. 打开终端,执行以下命令,打开 .bash_profile 文件。
    vi ~/.bash_profile
  2. 输入以下内容,在 PATH 路径下添加 HDC 工具路径和添加 HDC_SERVER_PORT 端口信息:
    export PATH="/Applications/DevEco-Studio.app/Contents/sdk/{版本路径}/openharmony/toolchains:$PATH" # 按照实际 SDK 安装路径配置,需要选择{显示包内容}
    
    HDC_SERVER_PORT=7035 
    launchctl setenv HDC_SERVER_PORT $HDC_SERVER_PORT 
    export HDC_SERVER_PORT
  3. 执行以下命令使配置的环境变量生效:

    source ~/.bash_profile

     

  • 配置 CAPI 版本环境变量:当前RN框架提供的 Demo 工程默认为 CAPI 版本,需要配置环境变量 RNOH_C_API_ARCH = 1

  1. Windows 环境:在此电脑 > 属性 > 高级系统设置 > 高级 > 环境变量中,在系统变量中点击新建,添加变量名为:RNOH_C_API_ARCH,变量值为 1

  2. Mac环境:在终端中,输入以下命令来设置环境变量:export RNOH_C_API_ARCH=1,确认环境变量是否成功设置。在终端中输入以下命令:echo $RNOH_C_API_ARCH。如果输出为 1,则表示环境变量已成功设置。如果希望在每次打开终端时都自动设置该环境变量,可以将上述 export 命令添加到你的 bash 配置文件(例如~/.bash_profile、~/.bashrc 或 ~/.zshrc)。编辑相应的文件,并在末尾添加以下行:export RNOH_C_API_ARCH=1,保存文件并关闭编辑器。

  • 为了使用加速 npm 包的下载,可以配置镜像源

    strict-ssl=false
    sslVerify=false
    registry=https://repo.huaweicloud.com/repository/npm/

    修改 registry 后需执行 npm cache clean --force 清理缓存,以确保新的 registry 生效。完成以上环境配置即可进行鸿蒙的 React Native 项目开发。

三、安装 OpenHarmony SDK

DevEco Studio(官方推荐,最稳定)

  1. 下载 DevEco Studio 5.1+

  2. 第一次启动会弹出“安装 OpenHarmony SDK”提示

  3. 按向导安装完整 SDK

  4. 安装完成后可创建模拟器

验证方式:
DevEco → Tools → SDK Manager → 能看到 HarmonyOS API 12/13,即安装成功。

四、创建React Native工程

可选择一个目录,使用 React Native 内置的命令行工具来创建一个名为 “HarmonyRNTest” 的新项目。这个命令行工具不需要安装,可以直接用 node 自带的 npx 命令来创建,目前 React Native for OpenHarmony 仅支持 0.72.5 版本的 React Native:

npx react-native@0.72.5 init HarmonyRNTest --version 0.72.5
cd HarmonyRNTest

五、下载并安装鸿蒙化依赖

使用VS Code IDE打开生成的模版代码。

打开目录下的package.json,在scripts下新增 OpenHarmony 的执行脚本:

"scripts": {
   "android": "react-native run-android",
   "ios": "react-native run-ios",
   "lint": "eslint .",
   "start": "react-native start",
   "test": "jest",
    "dev": "react-native bundle-harmony --dev"
 }

运行安装依赖包命令:推荐使用pnpm我用npm总是失败。

pnpm i @react-native-oh/react-native-harmony@特定版本

注意:一定要使用和你的HarmonyOS SDK版本对应的react-native-harmony版本,否者会报错!!!

 

如何查找对应自己环境的版本:

  1. 查看自己配置之前环境变量配置的toolchains对应的SDK版本号:

    我的是6..0.0.47;

  2. 然后去https://gitcode.com/openharmony-sig/ohos_react_native/blob/master/docs/zh-cn/release-notes/react-native-harmony-v6.0.0.508.md地址查询对应的版本号:

    根据我的SDK版本6.0.0.47所以我下载的react-native-harmony版本就应该是0.72.90;

    pnpm i @react-native-oh/react-native-harmony@0.72.90

再去修改metro.config.js文件添加OpenHarmony 的适配代码。

const {mergeConfig, getDefaultConfig} = require('@react-native/metro-config');
const {createHarmonyMetroConfig} = require('@react-native-oh/react-native-harmony/metro.config');

/**
* @type {import("metro-config").ConfigT}
*/
const config = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), createHarmonyMetroConfig({
  reactNativeHarmonyPackageName: '@react-native-oh/react-native-harmony',
}), config);

运行生成 bundle 文件的命令。运行成功后,会在 HarmonyRNTest/harmony/entry/src/main/resources/rawfile 目录下生成 bundle.harmony.js 和 assets 文件夹,assets 用来存放图片(如果 bundle 中不涉及本地图片,则没有 assets 文件夹)。

pnpm run dev

执行成功后,在项目根目录会生成Harmony文件夹。

好了,看到以上文件夹RN这边先告一段落。

六、在HarmonyOS工程中集成React Native

首先我们创建一个空的新工程。

然后我们再执行如下命令,在原生工程中集成 RNOH:

ohpm i @rnoh/react-native-openharmony@0.72.90

集成之后可以查看工程根目录的oh-package.json5文件:

好接下来修改默认工程入口Ability的实现:

  1. 需要重写getPagePath方法,返回程序的入口page;
  2. 如果需要扩展使用对应的生命周期函数请在代码中先用super调用父类也就是RNAbility的生命周期函数实现,因为RNAbility在生命周期函数中进行了对应的操作,这样才能确保原有功能不丢失;
  3. 需确保函数的参数列表与父类保持兼容,建议添加override关键字,以提升代码可读性并增强编译器检查;
import { RNAbility } from '@rnoh/react-native-openharmony';
import { Want } from '@kit.AbilityKit';

export default class EntryAbility extends RNAbility {
  getPagePath(): string {
    return "pages/Index"
  }

  override onCreate(want: Want): void {
    super.onCreate(want);
  }
}

在entry\src\main\ets目录下新增RNPackagesFactory.ets文件,它用来在 @rnoh/react-native-openharmony 库中导入 RNPackageContext 和 RNPackage以及在文件中导出 createRNPackages 方法,用于创建三方库或自定义 TurboModule、Fabric的package 对象,由于这次不涉及三方库,这块直接返回[]:

import { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony/ts';
export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [];
}

打开Index.ets文件,添加RNOH的使用代码,修改后如下:

import {
  AnyJSBundleProvider,
  ComponentBuilderContext,
  FileJSBundleProvider,
  MetroJSBundleProvider,
  ResourceJSBundleProvider,
  RNApp,
  RNOHErrorDialog,
  RNOHLogger,
  TraceJSBundleProviderDecorator,
  RNOHCoreContext
} from '@rnoh/react-native-openharmony';
import { createRNPackages } from '../RNPackagesFactory';

@Builder
export function buildCustomRNComponent(ctx: ComponentBuilderContext) {}

const wrappedCustomRNComponentBuilder = wrapBuilder(buildCustomRNComponent)

@Entry
@Component
struct Index {
  @StorageLink('RNOHCoreContext') private rnohCoreContext: RNOHCoreContext | undefined = undefined
  @State shouldShow: boolean = false
  private logger!: RNOHLogger

  aboutToAppear() {
    this.logger = this.rnohCoreContext!.logger.clone("Index")
    const stopTracing = this.logger.clone("aboutToAppear").startTracing();

    this.shouldShow = true
    stopTracing();
  }

  onBackPress(): boolean | undefined {
    // NOTE: this is required since `Ability`'s `onBackPressed` function always
    // terminates or puts the app in the background, but we want Ark to ignore it completely
    // when handled by RN
    this.rnohCoreContext!.dispatchBackPress()
    return true
  }

  build() {
    Column() {
      if (this.rnohCoreContext && this.shouldShow) {
        if (this.rnohCoreContext?.isDebugModeEnabled) {
          RNOHErrorDialog({ ctx: this.rnohCoreContext })
        }
        RNApp({
          rnInstanceConfig: {
            createRNPackages,
            enableNDKTextMeasuring: true, // 该项必须为true,用于开启NDK文本测算
            enableBackgroundExecutor: false,
            enableCAPIArchitecture: true, // 该项必须为true,用于开启CAPI
            arkTsComponentNames: []
          },
          initialProps: { "foo": "bar" } as Record<string, string>,
          appKey: "HarmonyRNTest",
          wrappedCustomRNComponentBuilder: wrappedCustomRNComponentBuilder,
          onSetUp: (rnInstance) => {
            rnInstance.enableFeatureFlag("ENABLE_RN_INSTANCE_CLEAN_UP")
          },
          jsBundleProvider: new TraceJSBundleProviderDecorator(
            new AnyJSBundleProvider([
              new MetroJSBundleProvider(),
              // NOTE: to load the bundle from file, place it in
              // `/data/app/el2/100/base/com.rnoh.tester/files/bundle.harmony.js`
              // on your device. The path mismatch is due to app sandboxing on OpenHarmony
              new FileJSBundleProvider('/data/storage/el2/base/files/bundle.harmony.js'),
              new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'hermes_bundle.hbc'),
              new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js')
            ]),
            this.rnohCoreContext.logger),
        })
      }
    }
    .height('100%')
    .width('100%')
  }
}

注意:RNApp的参数appKey需要与RN工程中AppRegistry.registerComponent注册的appName保持一致,否则会导致白屏。

通过以上方式可以获取到这个注册的名字,也就是RN工程的名字。

然后我们还需要补充 CPP 侧代码,在entry/src/main目录下新建cpp文件夹。在cpp目录下新增CMakeLists.txt,并将RNOH的适配层代码添加到编译构建中生成librnoh_app.so。

CMakeLists.txt文件内容如下:

project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(OH_MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")

set(RNOH_CPP_DIR "${OH_MODULE_DIR}/@rnoh/react-native-openharmony/src/main/cpp")
set(RNOH_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated")
set(CMAKE_ASM_FLAGS "-Wno-error=unused-command-line-argument -Qunused-arguments")
set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")
add_compile_definitions(WITH_HITRACE_SYSTRACE)
set(WITH_HITRACE_SYSTRACE 1) # for other CMakeLists.txt files to use

add_subdirectory("${RNOH_CPP_DIR}" ./rn)

add_library(rnoh_app SHARED
    "./PackageProvider.cpp"
    "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)

target_link_libraries(rnoh_app PUBLIC rnoh)

 

在cpp目录下新增PackageProvider.cpp,该文件导入RNOH/PackageProvider,并且实现getPackages方法,用于创建三方库或自定义TurboModule或Fabric的package对象。此处不涉及三方库与自定义TurboModule或组件,需要返回空数组。然后打开entry/build-profile.json5,将cpp中的代码添加到应用工程的编译构建任务中;如果x86_64架构的模拟器上运行应用,需在externalNativeOptions配置中额外添加abiFilters字段,并包含x86_64架构参数,如下所示,abiFilters字段当前被注释,默认仅构建适应于arm64-v8a架构的版本。

加载bundle包

在上一章节中已经完成了 bundle 文件的生成,接下来将它加载到 DevEco Studio 中以运行 MyApplication 项目。我们采用加载 本地bundle 包的形式,将 bundle 文件和 assets 图片放在 entry/src/main/resources/rawfile 路径下,在 entry/src/main/ets/pages/Index.ets 中使用。

将之前我们执行npm run dev后的harmony/entry/src/main/resources/rawfile目录下的bundle文件和assets图片拷贝到鸿蒙工程entry/src/main/resources/rawfile路径下。

启动并运行工程~

然后就遇到了一个比较坑的问题,依赖:@rnoh/react-native-openharmony在CMakeLists.txt配置的${OH_MODULE_DIR}/@rnoh/react-native-openharmony路径下是一个“替身”文件,这个可能是MacOS才会报的错。

Logo

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

更多推荐