一、环境搭建
1. 基础设置
本文参考:ohos_react_native/docs/zh-***/环境搭建.md · OpenHarmony-SIG/ohos_react_native - AtomGit | GitCode
本文记录操作步骤以及遇到的错误
- 安装 DevEco Studio。
- 设置 DevEco Studio 开发环境。
可参考我之前写的文章
开源鸿蒙-Fluuter编译开发-CSDN博客
2. hdc环境配置
hdc 是 OpenHarmony 为开发人员提供的用于调试的命令行工具,鸿蒙 React Native 工程使用 hdc 进行真机调试。hdc 工具通过 OpenHarmony SDK 获取,存放于 SDK 的 toolchains 目录下,请将 {DevEco Studio安装路径}/sdk/{SDK版本}/openharmony/toolchains 的完整路径添加到环境变量P——系统变量的path中。
2.1 windows 环境变量设置方法:
在此电脑 —— 属性 —— 高级系统设置 —— 高级 —— 环境变量中
- 编辑系统变量path,添加hdc工具路径。
- 添加 HDC 端口变量名为:
HDC_SERVER_PORT,变量值可设置为任意未被占用的端口,如7035。
2.2. 如何查看如何查看电脑未被占用的端口:
Window系统
命令提示符间接排查:
-
按下 Win+R 输入 cmd 打开命令提示符,输入
***stat -ano | findstr "LISTENING"该命令会列出所有正在监听的已占用端口。排除这些端口后,剩余端口即为未被占用状态;
-
若想验证特定端口(如 8080)是否空闲,可输入
无输出则表明端口未被占用。***stat -ano | findstr ":8080"
macOS系统
打开终端,执行以下命令,打开 .bash_profile 文件。
vi ~/.bash_profile
输入以下内容,在 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. 配置 CAPI 版本环境变量
当前RN框架提供的 Demo 工程默认为 CAPI 版本,需要配置环境变量 RNOH_C_API_ARCH = 1。
在系统变量中点击新建,添加变量名为:RNOH_C_API_ARCH,变量值为 1。
4. 编辑用户级 .npmrc 配置文件
为了使用加速 npm 包的下载,可以配置镜像源;如果关闭 SSL 证书校验还可以进一步加速下载,但是这会降低安全性,需用户评估后再使用。配置文件位置在 C:\Users\用户名\.npmrc,如果没有则手动创建。
在这里我选择了在 C:\Users\用户名创建了一个.npmrc文档
.npmrc 配置文件内容如下
strict-ssl=false
sslVerify=false
registry=https://repo.huaweicloud.***/repository/npm/
修改 registry 后需执行 npm cache clean --force 清理缓存,以确保新的 registry 生效。
出现一下警告
npm 之所以会给出这个警告,是因为强制清理理论上可能导致正在进行的其他 npm 操作(比如另一个终端窗口正在安装包)失败或损坏。
清理完成后,可以通过以下命令验证:
npm config get registry
如果返回你之前设置的源(https://repo.huaweicloud.***/repository/npm/),说明配置已经生效。
5. 注意事项
安卓和 iOS 的 React Native 完整环境搭建请参考 React Native 官网搭建开发环境 · React Native 中文网
二、创建React Native工程
1. 创建新项目
1.1 创建前的准备
因为React Native 开发需要依赖 Node.js、JDK、Android Studio(安卓开发)/ Xcode(iOS 开发),以及 Watchman(macOS 推荐)。
所以在创建项目前需要先安装核心依赖
- Node.js:推荐 v18 或更高版本(官网下载:Node.js 中文网),安装后可通过 node -v 验证。
- JDK:需安装 JDK 17(React Native 推荐版本),可通过 Android Studio 内置安装(后续步骤说明),或官网下载(https://www.oracle.***/java/technologies/downloads/)
- Watchman(macOS 必装):用于监听文件变化,提升开发效率。通过 Homebrew 安装:
brew install watchman
Node.js的安装详见这篇文章
2024最新版Node.js下载安装及环境配置教程【保姆级】_nodejs下载-CSDN博客
1.2 创建文件
选择一个目录,例如 D 盘根目录,使用 React Native 内置的命令行工具来创建一个名为 “AwesomeProject” 的新项目。这个命令行工具不需要安装,可以直接用 node 自带的 npx 命令来创建
npx react-native@0.72.5 init AwesomeProject --version 0.72.5
出现警告
这些是 npm 的 废弃(deprecated)警告,不是错误!核心意思是:你安装 yarn 时,它的部分依赖包(不是 yarn 本身)已经被作者标记为「不再维护 / 过时」,建议后续替换为更新的替代包。
1.3 创建完成
2. 安装鸿蒙依赖包并生成bundle
本节中使用的各类文件的版本版本配套关系,可以参考GitCode - 全球开发者的开源社区,开源代码托管平台打开 AwesomeProject 目录下的 package.json,在 scripts 下新增 OpenHarmony 的依赖:
可以直接使用这个链接的文件,找到对应文件进行一一替换,修改版本信息并运行。ohos_react_native/docs/zh-***/环境搭建.md · OpenHarmony-SIG/ohos_react_native - AtomGit | GitCode
下载并安装鸿蒙化依赖
2.1 打开 AwesomeProject 目录下的 package.json,在 scripts 下新增 OpenHarmony 的依赖:
{
"name": "AwesomeProject",
"version": "0.0.1",
"private": true,
"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" /*加这一行*/
},
"dependencies": {
"react": "18.2.0",
"react-native": "0.72.5"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@react-native/eslint-config": "^0.72.2",
"@react-native/metro-config": "^0.72.11",
"@tsconfig/react-native": "^3.0.0",
"@types/react": "^18.0.24",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.2.1",
"eslint": "^8.19.0",
"jest": "^29.2.1",
"metro-react-native-babel-preset": "0.76.8",
"prettier": "^2.4.1",
"react-test-renderer": "18.2.0",
"typescript": "4.8.4"
},
"engines": {
"node": ">=16"
}
}
2.2 在 AwesomeProject 目录下运行安装依赖包命令:
npm i @react-native-oh/react-native-harmony@x.x.x
这里前面已经安装完成了
运行指令并生成bundle
打开 AwsomeProject\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);
在 AwesomeProject 目录下运行生成 bundle 文件的命令。运行成功后,会在 AwesomeProject/harmony/entry/src/main/resources/rawfile 目录下生成 bundle.harmony.js 和 assets 文件夹,assets 用来存放图片(如果 bundle 中不涉及本地图片,则没有 assets 文件夹)。
执行命令:
npm run dev
这里我出现了报错:
这个错误是因为你的项目配置中(大概率是 metro.config.js 或 package.json)引用了 @react-native-oh/react-native-harmony/metro.config 模块,但这个模块要么没安装,要么安装路径不对—— 结合之前的操作,原因是:RN 版本的鸿蒙包不适配。
我的HarmonyOS版本是 6.0.1.112
重新安装了一个版本的Node.js
npm i @react-native-oh/react-native-harmony@0.72.90
重新执行命令:npm run dev
成功解决:
也可以使用 Metro 服务来加载 bundle 包,可以参考这篇文章GitCode - 全球开发者的开源社区,开源代码托管平台
三、创建鸿蒙工程
在DevEco Studio新建工程
在这里我新建了一个MyApplication_a
记得在 File ——Project Structure,在弹窗界面点击 Signing Configs,勾选 Support HarmonyOS 和 Automatically generate signature,然后点击 Sign In 登录华为账号,并签名
3.1 添加 React Native 配置
在DevEco Studio中 entry 目录下执行以下命令:
ohpm i @rnoh/react-native-openharmony@0.72.90
执行完成后会在工程级目录以及模块级目录下生成 oh_modules 文件夹。
3.2 集成RNOH
3.2.1 补充CPP侧代码
在F:\存放项目的路径\MyApplication_a\entry\src\main 目录下新建 cpp 文件夹。
在 cpp 目录下新增CMakeLists.txt和PackageProvider.cpp文件,并将 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-***mand-line-argument -Qunused-arguments")
set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")
add_***pile_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)
在PackageProvider.cpp文件添加以下代码
该文件需要满足以下要求:
- 需要导入
RNOH/PackageProvider; - 实现
getPackages方法,用于创建三方库或自定义 TurboModule 或 Fabric 的 package 对象。
#include "RNOH/PackageProvider.h"
using namespace rnoh;
std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
return {};
}
打开 MyApplicaton_a\entry\build-profile.json5,将 cpp 中的代码添加到应用工程的编译构建任务中
build-profile.json5文件内容如下:
{
"apiType": "stageMode",
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "",
"cppFlags": "",
"abiFilters": ["arm64-v8a", "x86_64"]
},
"resOptions": {
"copyCodeResource": {
"enable": false
}
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
}
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}
3.2.2 补充ArkTS侧的代码
3.2.2.1 修改EntryAbility.ets文件
打开 MyApplicaton_a\entry\src\main\ets\entryability\EntryAbility.ets,引入并使用 RNAbility
该文件需要满足:重写 getPagePath,返回程序的入口 page。
文件内容如下:(添加的内容后面报错了,这里记录一下,添加的内容在下面)
import { RNAbility } from '@rnoh/react-native-openharmony';
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
const DOMAIN = 0x0000;
export default class EntryAbility extends RNAbility {
getPagePath() {
return 'pages/Index';
}
}
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
try {
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
} catch (err) {
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
}
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'testTag', 'Su***eeded in loading the content.');
});
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
}
}
这里遇到报错:
报错的内容大概为:
- 修改后的
CMakeLists.txt没被正确读取(可能是没保存,或路径配置还是错的); - CMake 语法顺序反了;
- 始终找不到 RNOH 的
cpp目录
但是检查了一下之前配置的文件,没发现什么问题
后来看了这篇文章,发现是之前安装的版本不适配
开源鸿蒙-React编译开发HarmonyOS_开源鸿蒙 csdn-CSDN博客
把添加的内容换成:
import { RNAbility } from '@rnoh/react-native-openharmony';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
const DOMAIN = 0x0000;
export default class EntryAbility extends RNAbility {
getPagePath() {
return 'pages/Index';
}
override onCreate(want: Want): void {
super.onCreate(want);
hilog.info(DOMAIN, 'testTag', '%{public}s', 'EntryAbility onCreate');
}
override onDestroy(): void {
super.onDestroy();
hilog.info(DOMAIN, 'testTag', '%{public}s', 'EntryAbility onDestroy');
}
override onWindowStageCreate(windowStage: window.WindowStage): void {
super.onWindowStageCreate(windowStage);
hilog.info(DOMAIN, 'testTag', '%{public}s', 'EntryAbility onWindowStageCreate');
}
override onWindowStageDestroy(): void {
super.onWindowStageDestroy();
hilog.info(DOMAIN, 'testTag', '%{public}s', 'EntryAbility onWindowStageDestroy');
}
override onForeground(): void {
super.onForeground();
hilog.info(DOMAIN, 'testTag', '%{public}s', 'EntryAbility onForeground');
}
override onBackground(): void {
super.onBackground();
hilog.info(DOMAIN, 'testTag', '%{public}s', 'EntryAbility onBackground');
}
}
成功解决报错
3.2.2.2 新增 RNPackagesFactory.ets文件
在 MyApplicaton\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 [];
}
3.2.2.3 Index.ets文件添加RNOH的使用代码
打开 MyApplicaton\entry\src\main\ets\pages\Index.ets,添加RNOH的使用代码,修改后如下:
import {
AnyJSBundleProvider,
***ponentBuilderContext,
FileJSBundleProvider,
MetroJSBundleProvider,
ResourceJSBundleProvider,
RNApp,
RNOHErrorDialog,
RNOHLogger,
TraceJSBundleProviderDecorator,
RNOHCoreContext
} from '@rnoh/react-native-openharmony';
import { createRNPackages } from '../RNPackagesFactory';
@Builder
export function buildCustomRN***ponent(ctx: ***ponentBuilderContext) {}
const wrappedCustomRN***ponentBuilder = wrapBuilder(buildCustomRN***ponent)
@Entry
@***ponent
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 ***pletely
// 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
arkTs***ponentNames: []
},
initialProps: { "foo": "bar" } as Record<string, string>,
appKey: "AwesomeProject",
wrappedCustomRN***ponentBuilder: wrappedCustomRN***ponentBuilder,
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/***.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%')
}
}
3.3 加载bundle包
在上一章节中已经完成了 bundle 文件的生成,接下来将它加载到 DevEco Studio 中以运行 MyApplication 项目。加载 bundle 有三种方式:
-
方式一:本地加载 bundle。将 bundle 文件和 assets 图片放在
entry/src/main/resources/rawfile路径下,在entry/src/main/ets/pages/Index.ets中使用。 -
方式二:使用 Metro 服务加载 bundle。详细流程参考Metro热加载。
-
方式三:加载沙箱目录的bundle:
-
应用沙箱是一种以安全防护为目的的隔离机制,避免数据受到恶意路径穿越访问。在这种沙箱的保护机制下,应用可见的目录范围即为“应用沙箱目录”。
-
开发者在应用开发调试时,需要向应用沙箱下推送一些文件以期望在应用内访问或测试,此时有两种方式:
− 第一种:可以通过 DevEco Studio 向应用安装路径中放入目标文件,详见应用安装资源访问。
− 第二种:在具备设备环境时,可以使用另一种更为灵活的方式,通过 hdc 工具来向设备中应用沙箱路径推送文件。推送命令如下,其中,沙箱路径可通过向应用沙箱推送文件查询:
hdc file send ${待推送文件的本地路径} ${沙箱路径} -
加载沙箱目录 bundle,需要在 RNApp 的
jsBundlePrivider参数中使用new FileJSBundleProvider('bundlePath')将 bundle 注册进框架,并运行 bundle。
-
在上一章节的 MyApplicaton\entry\src\main\ets\pages\Index.ets 文件中,创建 RNApp 时传入 jsBundleProvider 用于加载 bundle。jsBundleProvider 的 AnyJSBundleProvider 传入了 FileJSBundleProvider,用于沙箱目录加载 bundle。
此内容来源于:GitCode - 全球开发者的开源社区,开源代码托管平台
这里我选择方法一:
把路径AwesomeProject\harmony\entry\src\main\resources\rawfile中的
assets文件和bundle.harmony.js文件复制到之前创建的项目MyApplication_a文件中的
MyApplication_a\entry\src\main\resources\rawfile
这里出现了css的样式报错:
问题出在font属性的写法上
更改
font-size: 14px;
line-height: 14px;
font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
四、启动并运行工程
成功运行