从 0 开始在鸿蒙上使用 react-native-webp-format:环境搭建、应用创建、库集成与 6 大场景真机验证

本文面向想把 React Native 应用跑在 HarmonyOS 上的开发者,以 react-native-webp-format(已鸿蒙适配,tag v1.3.1)为例,走完一条完整的从 0 到 1 路线:RNOH 环境搭建 → 创建 RN 应用 → 集成三方库 → 编写 6 个应用场景案例 → 真机调试验证。全部步骤在 RNOH 0.84 + API 26 真机上实测通过。

一、react-native-webp-format 是什么,能做什么

react-native-webp-format 的定位一句话讲完:让 RN 的 <Image> 组件认识 WebP 格式(含动画 WebP)

WebP 在同等画质下比 JPEG 小 25%~35%、比 PNG 小 26%,且支持有损/无损/透明通道/动画,是移动端图片流量优化的主力格式。但 RN 的默认图片管线在 iOS / Android 上并不完整支持 WebP:

  • iOS:RN 默认图片管线不支持 WebP,这个库通过 RCTImageDataDecoder 协议 + SDWebImageWebPCoder 注册原生解码器;
  • Android:依赖 Fresco 的 webpsupport / animated-webp 模块补齐解码能力。

而到了鸿蒙上,情况完全不同——ArkUI 的 <Image> 组件原生支持 WebP(官方支持格式清单:png, jpg, jpeg, bmp, svg, webp, gif, heif, tiff,静态与动画 WebP 都支持)。RNOH 的 <Image> 底层渲染走的就是 ArkUI <Image>,所以解码能力是"系统白送"的。

1.1 鸿蒙侧的能力详解

适配后的库在鸿蒙侧提供的能力:

能力鸿蒙侧表现是否需要 JS 代码
静态 WebP 渲染ArkUI 原生解码,require('./x.webp') 直接用不需要
动画 WebP 播放ArkUI 原生逐帧播放不需要
网络 WebP(uri)HTTP 拉取 + 原生解码不需要
ImageBackground 背景图同样直接支持 .webp不需要
resolveAssetSource 元信息可获取 uri / width / height / scale不需要
resizeMode 缩放模式contain / cover / stretch / center 全支持不需要
TurboModule 兼容层ReactNativeWebPFormatPackage 满足 autolinking 契约自动注册

两个必须知道的差异点

  1. Image.getSize() 对 WebP 不工作,统一用 Image.resolveAssetSource() 替代(本文场景 5 演示);
  2. 多个 ~5MB 的动画 WebP 同时渲染可能 OOM,大图场景注意控制数量。

另外,鸿蒙侧 Platform.OS 返回 'android',库在所有平台上工作方式一致,JS 侧不需要写平台分支。

1.2 三平台实现对比(为什么鸿蒙适配"极小")

平台实现方式工作量
iOSRCTImageDataDecoder + SDWebImageWebPCoder大(解码器注册、CocoaPods 集成)
AndroidFresco webpsupport + animated-webp中(gradle 依赖、解码器注册)
HarmonyOSArkUI 原生解码,TurboModule 骨架即最终实现极小

这正是这篇适配的经验总结:跨端适配第一步永远是查目标平台的原生能力清单。想了解适配过程的完整踩坑复盘(librnoh_app.so、module.json5 的 skills 缺失"跳设置页"等),见同系列上一篇文章。

二、从 0 搭建 RNOH 开发环境(macOS)

RNOH 0.84 对环境的要求:Node.js ≥ 22.11、DevEco Studio 26.x、OpenHarmony SDK API ≥ 17、pnpm ≥ 10,且 C-API 架构(RNOH_C_API_ARCH=1)是默认架构。

2.1 工具清单

工具版本要求安装方式
Node.js≥ 22.11nvm install 22 && nvm use 22
pnpm≥ 10corepack enable
DevEco Studio26.x官网下载
OpenHarmony SDKAPI ≥ 17DevEco Studio → Preferences → SDK 勾选安装
hdc随 SDK{SDK}/openharmony/toolchains/ 加入 PATH

2.2 环境变量(写入 ~/.zshrc

# RNOH 0.82+ 必需:CAPI 架构开关(缺失会导致运行时回退旧架构甚至崩溃)
export RNOH_C_API_ARCH=1

# hdc 调试端口(推荐)
export HDC_SERVER_PORT=7035

# DevEco SDK 路径(macOS 默认安装路径下,react-native run-harmony 等 CLI 依赖)
export DEVECO_SDK_HOME="/Applications/DevEco-Studio.app/Contents/sdk/default"

source ~/.zshrc 后验证:

node -v      # v22.x
pnpm -v      # 10+
hdc version  # Ver: 3.2.0e
hdc list targets   # 应列出你的真机序列号

2.3 网络配置(国内环境)

npm 默认源在国内经常超时,建议配置华为云镜像:

npm config set registry https://repo.huaweicloud.com/repository/npm/

反模式提醒:不要全局关闭 strict-ssl,仅在遇到 SSL 错误时对镜像域名单独处理。

我的环境实测结果:Node 22.22.3、pnpm 12.3.4、DevEco 26.0.0.621、SDK API 26、hdc 3.2.0e,全部达标。

三、创建 RN 应用并接入鸿蒙宿主

3.1 创建 RN 工程

npx @react-native-community/cli init webp-demo --version 0.84.1
cd webp-demo

RN 0.84 工程的 package.json 关键依赖:

{
  "dependencies": {
    "react": "19.2.3",
    "react-native": "0.84.1"
  },
  "engines": { "node": ">= 22.11.0" }
}

3.2 安装 RNOH 依赖

npm i @react-native-oh/react-native-harmony@^0.84.3

3.3 配置 harmony Metro

metro.config.js 换成鸿蒙版配置(这一步决定 bundle 能以 --platform harmony 打出):

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

const config = createHarmonyMetroConfig({
  reactNativeHarmonyPackageName: '@react-native-oh/react-native-harmony',
});

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

3.4 在 DevEco Studio 中创建鸿蒙工程

在工程根目录新建 harmony/ 目录,用 DevEco Studio 在该目录创建 HarmonyOS 工程(Empty Ability 模板即可),然后手工完善为 RN 宿主。RNOH 宿主"五件套"缺一不可:

#组件说明
1EntryAbility 继承 RNAbility应用入口
2Index.etsRNApp注册包与 bundle provider
3cpp/PackageProvider.cpp + CMakeLists.txtC++ 胶水层,产出 librnoh_app.so(0.84 C-API 架构必需)
4oh-package.json5 依赖 @rnoh/react-native-openharmonyRNOH 运行时
5metro.config.jscreateHarmonyMetroConfigMetro 侧支持

EntryAbility.ets:

import { RNAbility } from '@rnoh/react-native-openharmony';

export class EntryAbility extends RNAbility {
  getPagePath() {
    return 'pages/Index';
  }
}

pages/Index.ets(双通道 bundle 加载:Metro 优先、离线兜底):

import { RNApp, MetroJSBundleProvider, ResourceJSBundleProvider, AnyJSBundleProvider } from '@rnoh/react-native-openharmony';
import common from '@ohos.app.ability.common';

@Entry
@Component
struct Index {
  build() {
    Column() {
      RNApp({
        rnInstanceConfig: {
          createRNPackages: (ctx) => [],
          enableNDKTextMeasuring: false,
        },
        appKey: 'webp_demo',
        jsBundleProvider: new AnyJSBundleProvider([
          // 开发态:Metro 热更新
          MetroJSBundleProvider.fromServerIp('localhost', 8083, ['webp_demo']),
          // 兜底:打进 HAP 的离线 bundle,脱离电脑也能跑
          new ResourceJSBundleProvider(
            (getContext(this) as common.UIAbilityContext).resourceManager,
            'bundle.harmony.js', ['webp_demo']),
        ]),
      })
    }
    .width('100%')
    .height('100%')
  }
}

坑位提醒(来自实测):

  • module.json5 必须有 mainElement: "EntryAbility"pages: "$profile:main_pages",否则启动 401 白屏;
  • EntryAbility 必须声明 skillsentity.system.home + action.system.home),否则点桌面图标会跳到设置页——aa start -a 显式启动验证不出这个问题,只有图标启动才走入口解析;
  • 纯 ArkTS 宿主在 0.84 跑不起来,C++ 胶水层产出的 librnoh_app.so 必须存在。

3.5 生成离线 bundle

npx react-native bundle --platform harmony --dev false \
  --entry-file index.js \
  --bundle-output harmony/entry/src/main/resources/rawfile/bundle.harmony.js \
  --assets-dest harmony/entry/src/main/resources/rawfile/

四、集成 react-native-webp-format

4.1 JS 侧安装

npm i react-native-webp-format@^1.3.1

安装的就是社区同名库的鸿蒙适配版(发布在 oh-react-native/react-native-webp-format),JS 侧 API 与 iOS/Android 完全一致——因为本来就没有 JS API,WebP 支持是自动注册的。

4.2 鸿蒙侧依赖(autolinking)

harmony/entry/oh-package.json5 中添加依赖:

{
  "name": "entry",
  "dependencies": {
    "@rnoh/react-native-openharmony": "^0.84.0",
    "@react-native-ohos/react-native-webp-format": "^1.3.1"
  }
}

然后安装:

cd harmony
ohpm install --all

autolinking 会根据库 package.json 中声明的 harmony.autolinking.ohPackageName@react-native-ohos/react-native-webp-format)自动完成发现。

4.3 在 RNApp 中注册 Package

pages/Index.etscreateRNPackages 中注册:

import { ReactNativeWebPFormatPackage } from '@react-native-ohos/react-native-webp-format';

rnInstanceConfig: {
  createRNPackages: (ctx) => [
    new ReactNativeWebPFormatPackage(ctx),
  ],
  enableNDKTextMeasuring: false,
},

就这么多。没有解码器注册、没有 gradle/pod 配置、没有 JS 初始化——鸿蒙侧适配版把 iOS/Android 上"注册第三方解码器"的全部复杂度都消化掉了。

4.4 手动接入(可选)

如果宿主工程不走 autolinking,也可以手动接入:

  1. 将库仓库 harmony/react_native_webp_format 目录复制到鸿蒙模块;
  2. oh-package.json5 中添加该模块依赖(本地路径或构建出的 HAR);
  3. RNOHProvider / createRNPackages 中手动 new ReactNativeWebPFormatPackage(ctx)

4.5 准备 WebP 素材

把 WebP 图片放进 RN 工程(本文 Demo 素材来自库仓库 example/assets/):

webp-demo/
├── App.tsx
├── assets/
│   ├── 1.sm.webp                        # 静态 WebP
│   ├── 3.sm.webp                        # 静态 WebP(另一张)
│   └── animated-webp-supported.webp     # 动画 WebP
└── harmony/...

五、6 大应用场景案例

Demo 用一个 Tab 容器组织 6 个场景,每个场景对应一个真实业务里会遇到的用法。完整代码见配套仓库 example/App.tsx,下面按场景拆解。

场景 1:静态 WebP(本地资源)

最基础的用法——和 PNG/JPG 完全一样的 require 写法:

<Image source={require('./assets/1.sm.webp')} style={styles.image} />

不需要 import 任何额外模块,不需要初始化调用。适用场景:App 内置的插画、图标、客服二维码等用 WebP 减包体积。

场景1:静态 WebP 真机截图

场景 2:动画 WebP(自动播放)

<Image source={require('./assets/animated-webp-supported.webp')} style={styles.image} />

动画 WebP 由 ArkUI Image 原生逐帧解码播放,自动循环,JS 侧无任何播放控制代码。适用场景:加载动画、直播礼物动效、运营活动动图——以前这些只能用 GIF 或 Lottie,现在一张动画 WebP 就够了(体积通常比 GIF 小一半以上)。

场景2:动画 WebP 真机截图

场景 3:网络 WebP(uri 加载)

const [loaded, setLoaded] = useState(false);
<Image
  source={{uri: 'https://www.gstatic.com/webp/gallery/2.jpg'}}
  style={styles.image}
  onLoad={() => setLoaded(true)}
  onError={() => setLoaded(false)}
/>

网络 WebP 走 RNOH 的图片管线(HTTP 拉取 → ArkUI 解码),onLoad / onError 回调正常触发。适用场景:CDN 分发的商品图、 feeds 流图片——服务端出 WebP,客户端直接渲染,省流量不动代码。

场景3:网络 WebP 真机截图

场景 4:ImageBackground 背景图

<ImageBackground
  source={require('./assets/3.sm.webp')}
  style={styles.imageBackground}>
  <Text style={styles.description}>WebP 作为背景,文本正常叠加</Text>
</ImageBackground>

ImageBackground 同样直接吃 .webp 源,子组件叠加正常。适用场景:卡片头图、 banner、海报文字排版。

场景4:ImageBackground 真机截图

场景 5:resolveAssetSource 读取元信息

const source = Image.resolveAssetSource(require('./assets/1.sm.webp'));
// source.uri      -> 'asset://assets/1.sm.webp'
// source.width    -> 320
// source.height   -> 214
// source.scale    -> 1

⚠️ 这是鸿蒙侧最重要的差异点Image.getSize() 对 WebP 不工作,需要图片尺寸时统一用 resolveAssetSource()。这个方法同步返回资源元信息,不依赖解码完成,比 getSize() 的回调式 API 用起来更顺手。

适用场景:根据图片原始宽高比计算容器尺寸、埋点上报图片信息。

场景5:resolveAssetSource 元信息真机截图

场景 6:resizeMode 四种缩放模式

{['contain', 'cover', 'stretch', 'center'].map(mode => (
  <Image
    key={mode}
    source={require('./assets/3.sm.webp')}
    style={styles.resizeImage}
    resizeMode={mode}
  />
))}

四种模式在鸿蒙侧全部正常工作。适用场景:同一张运营图要适配不同比例坑位时的行为对齐验证。

场景6:resizeMode 四种缩放模式真机截图

Tab 容器骨架

const SCENES = [
  {key: 'static', title: '静态', Comp: StaticWebPScreen},
  {key: 'animated', title: '动画', Comp: AnimatedWebPScreen},
  {key: 'remote', title: '网络', Comp: RemoteWebPScreen},
  {key: 'background', title: '背景图', Comp: BackgroundWebPScreen},
  {key: 'source', title: '元信息', Comp: AssetSourceScreen},
  {key: 'resize', title: '缩放', Comp: ResizeModeScreen},
];

function App() {
  const [active, setActive] = useState(SCENES[0].key);
  const ActiveComp = SCENES.find(s => s.key === active)!.Comp;
  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.title}>react-native-webp-format 鸿蒙 Demo</Text>
      <ScrollView horizontal style={styles.tabBar}>
        {SCENES.map(s => (
          <Pressable key={s.key} onPress={() => setActive(s.key)}
            style={[styles.tab, active === s.key && styles.tabActive]}>
            <Text style={styles.tabText}>{s.title}</Text>
          </Pressable>
        ))}
      </ScrollView>
      <ScrollView contentContainerStyle={styles.scrollContent}>
        <ActiveComp />
      </ScrollView>
    </SafeAreaView>
  );
}

选型提醒:Demo 里用 RN 内置 SafeAreaView 而不是 react-native-safe-area-context——后者没有鸿蒙适配版本,RNOH 找不到对应 ComponentJSIBinder 时会整树静默白屏。选 JS 依赖前先到 oh-react-native 组织查一下有没有鸿蒙适配版。

六、真机调试与验证

6.1 构建与安装

cd harmony
hvigorw assembleHap --mode module -p product=default --no-daemon

构建产物在 entry/build/default/outputs/default/entry-default-signed.hap,推到真机:

hdc install -r entry-default-signed.hap

6.2 启动应用(走用户真实路径)

# 唤醒并解锁屏幕(开发者模式下系统无法自动解锁,需模拟上滑)
hdc shell "power-shell wakeup"
hdc shell "uitest uiInput swipe 576 2400 576 600 600"

# 先确认包名(AppScope/app.json5 里的 bundleName)
hdc shell aa start -b com.example.smarttoolbox -a EntryAbility
hdc shell "pidof com.example.smarttoolbox"   # 有 pid = 启动成功

调试命令速查:

命令用途
hdc list targets确认真机连接
hdc shell aa start -b <bundle> -a <ability>启动应用
hdc shell "uitest dumpLayout -p /data/local/tmp/l.json"导出 UI 树(验证文本/组件渲染)
hdc shell "uitest uiInput click <x> <y>"模拟点击
hdc shell snapshot_display -f /data/local/tmp/s.jpeg截屏
hdc file recv /data/local/tmp/s.jpeg ./s.jpeg拉取文件到本机
hilog | grep <关键字>查日志

6.3 六大场景真机验证结果

通过 uitest dumpLayout 读取 UI 树文本 + 截图像素分析(非白像素占比)双重验证,六个场景全部通过:

场景UI 树验证像素分析结果
1. 静态 WebP标题/说明文本齐全53.2% 非白
2. 动画 WebP逐帧播放中57.1% 非白
3. 网络 WebPonLoad 已触发80.2% 非白
4. ImageBackground文本叠加正常48.1% 非白
5. resolveAssetSourceuri: asset://assets/1.sm.webp320 × 214scale: 1
6. resizeMode四格渲染47.2% 非白

场景 5 的实测输出(来自真机 UI 树原文):

uri: asset://assets/1.sm.webp
width × height: 320 × 214
scale: 1

所有截图(article/screenshots/)与验证数据均来自 HarmonyOS 真机(API 26,RNOH 0.84.3,Hermes 引擎)。

5.4 真机调试的几个实战要点

  1. 锁屏是第一个拦路虎:开发者模式下 aa start 会报 10106102 screen is locked——先 power-shell wakeupuitest uiInput swipe 上滑解锁;
  2. 验证走用户路径:桌面图标启动 ≠ aa start -a 显式启动。module.json5skills 声明时,显式启动一切正常,图标点击却会跳设置页;
  3. UI 树比截图更可信uitest dumpLayout 能读到组件文本与 bounds,白屏问题(截图像素 99% 白)用它一眼定位;
  4. Metro 端口连通 ≠ 端口正确:真机连 Metro 前先 lsof -nP -iTCP:8083 -sTCP:LISTEN 确认监听进程是 Metro 而不是别的服务;
  5. 离线 bundle 是演示保命符AnyJSBundleProvider 双通道(Metro 优先 + rawfile 兜底)让应用脱离电脑也能跑,本文所有真机验证都是离线 bundle 跑的。

七、总结

从 0 到 1 在鸿蒙上用起 react-native-webp-format,全流程只有五步:

环境检测通过(Node 22 / DevEco 26 / API 26 / hdc)
    ↓
创建 RN 0.84 工程 + harmony Metro 配置
    ↓
鸿蒙宿主五件套(RNAbility / RNApp / C++ 胶水层 / oh-package / metro)
    ↓
集成库(npm i + ohpm install + createRNPackages 注册,三行核心代码)
    ↓
hvigorw 构建 → hdc install → 真机 6 场景全通过

回顾全文,鸿蒙侧使用这个库的核心认知有三条:

  1. 能力是白送的:ArkUI <Image> 原生支持静态与动画 WebP,适配版的 TurboModule 只是 autolinking 契约骨架,JS 侧零改动、零学习成本;
  2. API 差异只有一处Image.getSize() 换成 Image.resolveAssetSource(),其余 <Image> / <ImageBackground> 用法与 iOS/Android 完全一致;
  3. 真机验证要覆盖真实路径:锁屏解锁、图标启动、UI 树 + 像素双验证,这些"用户视角"的检查项比 aa start 显式启动更有说服力。

如果你的 RN 应用已经用了这个库(或任何依赖 WebP 解码的图片库),迁移到鸿蒙的成本几乎为零——npm i + ohpm install + 一行 Package 注册,就能让 WebP 在鸿蒙上跑起来。

参考资料

如有问题欢迎评论区交流,也可以在 AtomGit 仓库提 issue。

社区

欢迎加入 RN 鸿蒙生态社区,共建三方库适配:

Logo

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

更多推荐