React Native Harmony 图片资源沙箱路径加载机制详解

原创2026-10-13 13:42:381,872 阅读
文章标签:移动开发跨平台OpenHarmony

React Native Harmony 图片资源沙箱路径加载机制详解

导读

在 React Native 鸿蒙化(RNOH)应用中,当 JS Bundle 以文件形式放入应用沙箱(Sandbox)或随 HAP 打进 rawfile 目录时,图片等静态资源的实际加载路径与开发期 Metro 服务器模式完全不同。本文以 image组件资源沙箱路径加载.md 为核心,结合 Sandbox 示例工程 与 RNOH 图片缓存层的 C++ 实现,完整讲解图片资源在「打包 → 沙箱 / RAWFILE」两个场景下的路径合成规则,并给出可复现的工程配置与源码级验证。读完本文,你将掌握 require('./d.png') 这类写法在鸿蒙侧最终被解析成何种 URI、assets 目录在两种场景下存在与否的根本差异,以及如何在自己的 RNOH 工程中正确配置 bundle 与图片资源的落盘位置。

1 打包阶段的路径语义:相对路径是后续一切的基础

1.1 打包路径假设

文档以「打包路径为根路径 /」为假设建立模型。设 RN 代码位于 /aaa/bbb/c.tsx,三张图片分别位于不同目录:

图片 所在路径 RN 中的写法
图片 1 /aaa/bbb/d.png require('./d.png')
图片 2 /eee/f.png require('../../eee/f.png')
图片 3 /aaa/ggg/h.png require('../ggg/h.png')

注意 require() 的相对路径是相对于引用它的 JS 文件所在目录计算的:c.tsx 在 /aaa/bbb/,因此 ./d.png 实际指向 /aaa/bbb/d.png,../../eee/f.png 是 /aaa/bbb/ 向上两级到 / 再进入 eee/,../ggg/h.png 则是向上到 /aaa/ 再进入 ggg/。

1.2 图片路径前缀:Metro 生成的资源标识

打包时 Metro 会把每张图片映射为一个「相对工程根的路径前缀 + 文件名」,即文档中的图片路径前缀:

  • 图片 1 前缀:/aaa/bbb/
  • 图片 2 前缀:/eee/
  • 图片 3 前缀:/aaa/ggg/

这一前缀的生成逻辑可以在 packages/react-native-harmony-cli/src/assetResolver.ts 中看到:getResourceIdentifier 取 Metro 提供的 httpServerLocation(即图片相对工程根的目录)拼接文件名,最终得到 aaa/bbb/d.png、eee/f.png 这类相对路径。copyAssets 会按 getAssetDestRelativePath 把这些文件拷贝到目标目录,并保留目录层级。

1.3 资源打包结果:assets 目录

文档明确:assets 是在 package.json 中指定的目录,开发者可以任意设置目录名。上述三张图片打包后形成如下结构:

assets
|--aaa/bbb/d.png
|--aaa/ggg/h.png
|--eee/f.png

也就是说,打包结果完整保留了图片相对工程根的目录层级(aaa/bbb/、aaa/ggg/、eee/ 三个前缀都被保留下来),而不是拍平成一个平铺目录。这一点非常关键——正是由于层级被保留,后续运行时才能依据「图片相对打包路径」反推出实际物理位置。

2 沙箱场景:bundle 路径即前缀,图片与 bundle 同根

2.1 路径合成规则

在沙箱场景下,假设 bundle 落在 /data/storage/base/files/dir1/bundle.harmony.js,则三张图片的实际路径为:

图片 沙箱实际路径
图片 1 /data/storage/base/files/dir1/aaa/bbb/d.png
图片 2 /data/storage/base/files/dir1/eee/f.png
图片 3 /data/storage/base/files/dir1/aaa/ggg/h.png

合成公式可概括为:

图片实际路径 = bundle 所在目录 + 图片相对工程打包路径

即沙箱场景下以 bundle 路径作为前缀,以图片与工程打包路径的相对路径作为后缀。文档特别强调:沙箱场景下这里没有 assets 目录——图片与 bundle 直接同根放置,这是与 RAWFILE 场景最本质的差异。

2.2 示例工程中的实现细节

Sandbox 示例(Index.ets)展示了把 bundle 从 rawfile 推送到沙箱并加载的完整流程:

  • downloadBundle():通过 resourceManager.getRawFileContent('rawfile/bundle/sandbox.harmony.bundle') 读出打包好的 bundle,再用 fs.createStreamSync(bundlePath, 'w') 写入 context.filesDir(即 /data/storage/base/files)下的 sandbox.harmony.bundle;
  • copyRawfileDirToSandbox():递归把 rawfile 中的 assets 目录复制到沙箱 filesDir/assets,注释明确指出「C++ 端根据 bundle 路径推导 assets 前缀为 file:///沙箱目录/assets/」;
  • 最后通过 new FileJSBundleProvider(filesDir + '/' + bundlePath) 加载 bundle。

其中 FileJSBundleProvider 是 RNOH 提供的标准 Bundle 提供者之一,其实现见 JSBundleProvider.ts:构造时接收沙箱中的文件路径,getBundle() 用 fs.access(path, READ_ONLY) 校验文件存在后返回 filePath,若不存在则抛出带修复建议的 JSBundleProviderError。

2.3 通过 assetsDest 控制资源子目录

与文档「沙箱场景无 assets 目录」的简化模型相比,真实工程往往借助 assetsDest 配置显式指定资源目录。示例中 RNApp 的 rnInstanceConfig 设置了 assetsDest: 'assets'(见 Index.ets)。该配置项在 RNInstance.ts 中有明确注释:

指定 RN 定位资源(assets)的路径;当使用自定义 --assets-dest 执行 react-native bundle-harmony 时必须设置该项。

因此两种模型并不冲突:文档描述的是「未显式指定子目录时,默认以 bundle 目录为前缀推导」的兜底规则;而实际工程可通过 assetsDest 让资源统一收敛到 沙箱目录/assets/ 下,与 C++ 端推导出的 file:///沙箱目录/assets/ 前缀对齐。

3 RAWFILE 场景:固定前缀 resource://RAWFILE/assets/

3.1 路径合成规则

在 RAWFILE 场景下,bundle 位于 HAP 的 rawfile 目录中,假设路径为 resource://RAWFILE/dir1/bundle.harmony.js,则三张图片的实际路径为:

图片 RAWFILE 实际路径
图片 1 resource://RAWFILE/assets/aaa/bbb/d.png
图片 2 resource://RAWFILE/assets/eee/f.png
图片 3 resource://RAWFILE/assets/aaa/ggg/h.png

合成公式为:

图片实际路径 = resource://RAWFILE/assets/ + 图片相对工程打包路径

文档强调:RAWFILE 场景下图片路径前缀固定指定为 resource://RAWFILE/assets/,且这里的 assets 目录是真实存在的(对应打包时 --assets-dest 指定的 rawfile 目录),与沙箱场景形成鲜明对比。

3.2 源码级验证:AssetLoadInterceptor

上述前缀在 RNOH 图片缓存层的 C++ 实现中可以直接找到证据。ImageCacheAssetLoadInterceptor.cpp 中声明了三个关键常量:

constexpr const char* ASSETS_DEST = "assets/";
constexpr const char* ASSET_PREFIX = "asset://";
constexpr const char* RAWFILE_PREFIX = "resource://RAWFILE/assets/";

拦截器 Resolve() 的解析逻辑(同文件 L224-L243)印证了文档结论:

  • 以 asset:// 开头的 URI(require() 资源经 RN 侧转换而来):优先在 HAP rawfile 中查找,失败后再回退到 option.assetsPrefix 解析出的文件路径(L178-L179 注释);
  • 以 resource://RAWFILE/assets/ 开头的 URI:GetRawfileRelativePath() 剥掉固定前缀后,得到图片相对路径,再拼回 assets/ 在 rawfile 中逐级尝试命中(对应 CollectReleaseRawfileCandidates,同文件 L106-L120)。

这正好解释了两种场景的差异来源:沙箱场景资源在真实文件系统(file://),RAWFILE 场景资源在 HAP 包内的只读 rawfile(resource://),二者前缀推导基准完全不同,但后缀都是「图片相对工程打包路径」,因此第 1 章保留目录层级的打包结果在此刻发挥了决定性作用。

4 端到端实战:Sandbox 示例如何把资源送进沙箱

4.1 RN 侧打包命令

Sandbox 的 RN 工程(SandBoxRnProject/package.json)通过 react-native bundle-harmony 一次性产出 bundle 与资源:

react-native bundle-harmony --dev false \
  --entry-file ./src/bundles/Sandbox/index.js \
  --bundle-output ../SandBoxNativeProject/entry/src/main/resources/rawfile/bundle/sandbox.harmony.bundle \
  --assets-dest ../SandBoxNativeProject/entry/src/main/resources/rawfile \
  --config ./sandbox.config.js

注意 --assets-dest 指向的是rawfile 根目录,于是打包结果被整体放进 rawfile/assets/ 下(对应第 3 章「assets 目录存在」)。这也解释了示例为何在 downloadBundle() 之后要调用 copyRawfileDirToSandbox('rawfile/assets', ...):把 rawfile 里的 assets 树递归复制到沙箱,使沙箱场景也能按 assetsDest: 'assets' 命中图片。

4.2 RN 侧加载写法:三种方式对照

示例页面 examples.tsx 同时展示了三种加载方式:

// 方式一:require() 相对路径资源(随 bundle 打包进 assets)
<Image source={require('../../../assets/expo.png')} />

// 方式二:显式 file:// 沙箱绝对路径 URI
<Image source={{ uri: 'file:///data/storage/el2/base/files/call.png' }} />

// 方式三:require() 相对路径资源(同样随 bundle 打包)
<Image source={require('../../../assets/call.png')} />

方式二是对本文规则的直接验证:call.png 被推送到沙箱后,通过完整的 file:// 沙箱绝对路径即可被 Image 组件加载;方式一和方式三则由 RNOH 图片加载链路依据第 2/3 章的规则自动把 require() 资源解析为最终 URI。

4.3 推送 bundle 到沙箱的两种途径

在 RNOH 中,把 bundle 推进沙箱有两种方式(详见 沙箱加载bundle.md):

  1. rawfile 推入沙箱:getRawFileContent() 读取 rawfile 中的 bundle 字节流,用 fs.createStreamSync 写入 context.filesDir 下的目标文件,示例代码即上文 downloadBundle() 所演示;
  2. hdc 工具推送:直接通过命令行把本地 bundle 推送到设备应用沙箱路径:
hdc file send ${待推送文件的本地路径} ${沙箱路径}

第 2 种方式适合在调试阶段快速更新沙箱中的 bundle 与图片,无需重新走构建流程;无论哪种方式,最终图片能否加载都取决于其落盘位置是否与第 2 章的路径合成规则一致。

5 总结与注意事项

对比维度 沙箱场景 RAWFILE 场景
bundle 示例路径 /data/storage/base/files/dir1/bundle.harmony.js resource://RAWFILE/dir1/bundle.harmony.js
图片前缀基准 bundle 所在目录(默认,无 assets 目录) 固定 resource://RAWFILE/assets/
assets 目录 默认不存在(可用 assetsDest 显式创建) 存在(对应 --assets-dest)
资源访问通道 真实文件系统(file://) HAP 只读 rawfile(resource://)
后缀 图片相对工程打包路径 图片相对工程打包路径

工程落地时需要特别注意:

  • 打包阶段保留目录层级是沙箱/RAWFILE 两种场景能正确反解图片路径的前提,--assets-dest 决定资源树落在何处;
  • 沙箱场景若想统一管理资源,应在 RNApp 的 rnInstanceConfig 中配置 assetsDest,并把资源树复制到对应的沙箱子目录(如示例中的 filesDir/assets);
  • hdc file send 推送资源时,务必使沙箱内的相对布局与打包结果一致,否则图片会因「前缀推导出的路径与实际落盘位置不匹配」而加载失败;
  • 沙箱路径的读写与文件访问权限需按实际运行环境(API 版本、应用沙箱目录映射)进行调整。

如需完整运行该示例,可参考 README_zh.md:在 SandBoxRnProject 目录执行 pnpm i 安装依赖、pnpm dev 生成 bundle,然后用 DevEco Studio 打开 SandBoxNativeProject 完成签名后运行即可。

沙箱加载运行效果图

登录后查看全文
ohos_react_native