这是一个基于华为鸿蒙操作系统与React Native深度集成的沙箱环境应用入口组件,展示了在现代跨平台移动应用开发中如何实现动态资源管理和沙箱环境下的应用运行。从技术架构的宏观层面来看,这段代码构建了一个完整的沙箱应用管理系统,通过条件渲染和异步操作来管理React Native bundle的下载、验证和加载。

在代码结构方面,首先导入了多个核心模块和接口。AnyJSBundleProvider提供统一的bundle提供者接口,支持多种bundle来源。ComponentBuilderContext是组件构建器的上下文环境,提供构建过程中的必要信息。FileJSBundleProvider、MetroJSBundleProvider、ResourceJSBundleProvider分别对应文件系统、Metro开发服务器和资源管理器的bundle提供实现。RNApp是React Native应用的核心组件,负责渲染和管理RN应用。RNOHErrorDialog提供错误提示对话框组件。RNOHLogger负责日志记录和追踪功能。TraceJSBundleProviderDecorator提供bundle加载的追踪装饰器。RNOHCoreContext提供核心上下文信息。RNAbility代表鸿蒙的能力对象。


README

这个项目包含两个演示,一个演示如何在 OpenHarmony 侧将 raw 文件推送到沙箱并成功加载,另一个演示如何在 React Native 侧加载沙箱中的图片。

目录结构

├── SandBoxNativeProject
|   └── Index.ets
|       ├── downloadBundle # OpenHarmony 侧推送 raw 文件到沙箱中
|       └── new FileJSBundleProvider # OpenHarmony 侧加载沙箱中的文件
└── SandBoxRnProject
    └── example.tsx  # React Native 侧加载沙箱中的图片

OpenHarmony 侧

  1. 将文件推送到沙箱: 使用 downloadBundle 函数的 getRawFileContent 方法将 rwaFile 中的文件推送到沙箱,以便之后的读取操作。
  2. 加载文件: 使用 new FileJSBundleProvider 类从沙箱中加载文件并验证其内容。

React Native 侧

  1. 加载沙箱中的图片: 使用 example.tsx 中的代码,从 OpenHarmony 沙箱中加载图片,并在 React Native 应用中显示。

运行步骤

  1. SandBoxRnProject 目录下执行 npm i @react-native-oh/react-native-harmony@x.x.xyarn add @react-native-oh/react-native-harmony@x.x.x 安装依赖,执行 npm run dev 生成 bundle;
  2. entry 目录下执行 ohpm i @rnoh/react-native-openharmony@x.x.x 安装依赖;
  3. 检查 SandBoxNativeProjectentry 目录下是否生成 oh-modules 文件夹;
  4. 用 DevEco Studio 打开 SandBoxNativeProject,执行 Sync and Refresh Project
  5. 点击 File > Project Structure > Signing Configs,登录并完成签名;
  6. 点击右上角的 run 启动项目;
  7. 确保鸿蒙和 rn 侧依赖下载无缺,版本配套正常;
  8. 打开生成的 app 来确认文件已正确加载。

注意事项

  • 确保 OpenHarmony 和 React Native 项目配置正确,以便文件可以在两个系统之间无缝传递与加载。
  • 沙箱路径的使用和文件访问权限需要根据具体环境进行调整。

效果预览

通过代码将bundle推送到沙箱:

请添加图片描述

从RNPackagesFactory导入createRNPackages函数,这表明应用支持模块化的RN包管理。fs模块的导入提供了文件系统操作能力,包括文件读写、状态检查等功能。

@Builder装饰器用于定义buildCustomRNComponent函数,这是一个自定义RN组件构建器。虽然当前实现为空,但为后续扩展自定义原生组件提供了接口。wrappedCustomRNComponentBuilder通过wrapBuilder函数对构建器进行包装,增强了组件的功能性和扩展性。

@Entry装饰器标识这个组件是应用的入口点,@Component装饰器声明这是一个自定义组件结构。组件内部定义了多个重要的状态变量和属性。

@StorageLink(‘RNOHCoreContext’)创建了一个与AppStorage双向绑定的rnohCoreContext私有属性。这种机制确保了应用状态的全局一致性管理,当全局状态发生变化时,所有相关的组件都会自动更新。

@State shouldShow是一个响应式状态变量,控制组件是否显示。private logger用于日志记录,通过RNOHLogger实例提供。

@StorageLink(‘RNAbility’) rnAbility与全局的RNAbility状态绑定,提供了对鸿蒙能力对象的访问。

bundlePath和assetsPath定义了bundle文件和资源文件的相对路径。这种相对路径的设计使得应用可以在不同的部署环境下灵活运行。

@State hasBundle表示是否已成功加载bundle。@State bundleStatus显示bundle加载的状态信息。

aboutToAppear生命周期函数在组件即将显示时执行。首先创建日志记录器实例,然后开始追踪过程,设置shouldShow状态为true,最后检查bundle更新状态。

downloadBundle是一个异步函数,负责从资源管理器中获取bundle文件并保存到沙箱环境。通过getRawFileContent方法获取rawfile目录下的bundle文件内容,将其转换为Uint8Array,然后获取buffer和文件大小。


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

@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;
  @StorageLink('RNAbility') rnAbility: RNAbility | undefined = undefined;
  // bundlePath 和 assetsPath 都是相对于 rnAbility.context.filesDir目录下的路径。
  bundlePath: string = 'sandbox.harmony.bundle';
  assetsPath: string = 'assets';
  @State hasBundle: Boolean = false;
  @State bundleStatus: string = '';

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

  async downloadBundle() {
    if (this.rnAbility) {
      // 将bundlejs下载并保存到沙箱。
      let uint8Array =
        await this.rnAbility.context.resourceManager.getRawFileContent('rawfile/bundle/sandbox.harmony.bundle');
      let rawBuffer = uint8Array.buffer;
      let num = rawBuffer.byteLength;
      // 获取沙箱路径
      const sandboxDir = this.rnAbility.context.filesDir;
      const bundlePath = sandboxDir + '/' + this.bundlePath;
      let stream = fs.createStreamSync(bundlePath, 'w');
      stream.writeSync(rawBuffer);
      stream.closeSync();
      this.hasBundle = true;
    }
  }

  checkBundleUpdated(): void {
    if (this.rnAbility) {
      const sandboxDir = this.rnAbility.context.filesDir;
      const bundlePath = sandboxDir + '/' + this.bundlePath;
      try {
        const stat = fs.statSync(bundlePath);
        if (stat.size == 0) {
          this.downloadBundle();
        } else {
          this.hasBundle = true;
        }
      } catch (e) {
        this.downloadBundle();
      }
    }
  }

  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 });
        }
        if (this.rnAbility && this.hasBundle) {
          RNApp({
            rnInstanceConfig: {
              createRNPackages,
              enableCAPIArchitecture: true,
              arkTsComponentNames: []
            },
            appKey: "Sandbox",
            wrappedCustomRNComponentBuilder: wrappedCustomRNComponentBuilder,
            onSetUp: (rnInstance) => {
              rnInstance.enableFeatureFlag("ENABLE_RN_INSTANCE_CLEAN_UP")
            },
            jsBundleProvider: new FileJSBundleProvider(this.rnAbility.context.filesDir + '/' + this.bundlePath)
          })
        } else {
          Row() {
            Text('未加载bundle')
            Text(this.bundleStatus)
          }
          .height(100)
        }
      }
    }
    .height('100%')
    .width('100%')
  }
}

获取沙箱目录路径,构建完整的bundle文件路径。使用fs.createStreamSync创建文件写入流,将buffer内容写入文件,最后关闭流并更新hasBundle状态。

checkBundleUpdated函数检查bundle文件是否已更新。获取沙箱目录和bundle文件路径,通过fs.statSync检查文件状态。如果文件大小为0,则重新下载bundle;如果文件正常存在,则设置hasBundle为true;如果文件不存在,捕获异常并触发下载过程。

onBackPress函数处理返回按钮的点击事件。通过rnohCoreContext.dispatchBackPress()分发返回事件,并返回true表示事件已被处理。

在build方法中,使用Column容器创建垂直布局。通过条件判断rnohCoreContext和shouldShow来决定是否显示内容。

如果处于调试模式且启用了调试功能,显示RNOHErrorDialog组件,提供错误提示界面。

如果rnAbility存在且hasBundle为true,渲染RNApp组件。rnInstanceConfig配置包含createRNPackages函数、enableCAPIArchitecture设置为true、arkTsComponentNames为空数组。

appKey设置为"Sandbox",标识这是一个沙箱环境应用。wrappedCustomRNComponentBuilder传递包装后的自定义组件构建器。

onSetUp回调函数在RN实例设置完成时执行,启用特定的功能标志。

jsBundleProvider使用FileJSBundleProvider,基于沙箱目录下的bundle文件路径。

否则,显示未加载bundle的状态信息,包括文本提示和当前bundle状态。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐