近几个月在帮团队把一个 React Native 项目往 OpenHarmony 设备上迁移,目标产品是一款英雄联盟助手类 App。在手机端一直跑得挺顺的页面,切到 RK3568、RK3588 这类开发板后,问题全冒出来了:分辨率适配、原画大图内存、RN 与 OpenHarmony 原生容器之间的通信,每一个都够喝一壶。整个项目里最典型也最值得单独拿出来说透的,就是“皮肤详情”页面的实现。
皮肤详情这个模块表面上看很简单,无非是几张皮肤原画加英雄信息、价格、购买按钮,但它恰恰是 RN for OpenHarmony 场景里最完整的一个样本:有横向轮播、有沉浸式头图、有大图懒加载、有状态切换,还需要调 OpenHarmony 侧的系统能力。搞定这一页,基本就等于把 RN 到 OpenHarmony 的整套开发链路摸通了。
文章会按照我实际做项目的顺序来写,从方案选型、页面拆解,到组件实现、图片性能优化,再到 OpenHarmony 原生侧能力调用和问题排查。适合正在评估 RN 跨端方案、或者手头已经在用 React Native 做 OpenHarmony 应用的客户端同学看。下面不讲废话,直接进入正题。
1. 为什么选这条路——RN for OpenHarmony 项目的三个判断
1.1 “RN for OpenHarmony”到底是个什么东西
很多第一次接触这个方向的同学,会把 OpenHarmony 和 Android 的开发方式搞混。严格来说,OpenHarmony 是一个开源的操作系统,不是换壳 Android。它自己有 UI 框架 ArkUI、声明式语法 ArkTS,底层也不是 Android 的 Art 虚拟机那一套。想在 OpenHarmony 上跑 React Native,必须有一层“适配层”,把 RN 的 JS 执行、组件渲染、原生模块通信都映射到 OpenHarmony 的系统能力上。
目前在社区里,这套工程实现一般被叫 RNOH,全称是 React Native on OpenHarmony。它的核心思路是保留 RN 的 JavaScript 开发和组件模型,但底层不再走 Android/iOS 的渲染路径,而是映射到 OpenHarmony 的组件体系上。换句话说,RN 写的 View、Text、Image,最终会对应到 OpenHarmony 侧的原生组件去渲染,而不是自己画一套 UI。
这个架构对现有 RN 项目非常友好,绝大多数 JS 业务代码不需要改,甚至可直接复用。我们团队当时产品手里已经有一套跑在 Android 上的英雄联盟助手 RN 页面,目标是在 OpenHarmony 设备上上线,纯 ArkTS 重写所有页面显然不现实,而 RNOH 能让我们把已有代码带过去,这就是最开始考虑这个方案的最大前提。
1.2 三个可行方案,为什么最后留下 RNOH
在正式动手前,团队花了一周时间做了简单技术选型,路线其实就是三条:
| 方案 | UI 还原度 | 现有代码复用 | 原生能力调用 | 团队学习成本 | 当前生态成熟度 |
|---|---|---|---|---|---|
| 纯 ArkTS + ArkUI 重写 | 高,但要逐页做 | 零复用 | 最直接 | 很高 | 原生生态,稳定 |
| Flutter for OpenHarmony | 较好,但绘图引擎是自绘 | 需要 Dart 重写 | 依赖插件适配 | 高 | 还在快速演进 |
| RN for OpenHarmony | 受适配库影响 | 高,主要 JS 层复用 | 需写桥接模块 | 低 | 社区组件逐渐补齐 |
纯 ArkTS 的方案最稳,却是工作量最大的,页面数量一多,开发周期完全不可控。Flutter 那边自己也还在打磨,要为一个已完成的 RN 业务重写 Dart 层,产品进度接受不了。RNOH 当时最吸引我们的是:JS 层代码几乎不动,主要工作是逐个检查依赖的原生模块在 OpenHarmony 上有没有对应实现,没有就自己补一个 TurboModule。
当然,这个选择不是没有代价。第一个代价是资料少,很多问题只能自己去翻源码;第二个代价是 RN 的社区库不一定都能直接用,比如一些依赖 Android/iOS 原生视图的三方组件,需要找 OpenHarmony 适配版或自己桥接。但这些在“快速上线一个已存在 RN 产品”的诉求面前,是可以接受的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 皮肤详情页的方案设计——先拆页面,再定数据协议
2.1 把页面拆成六个视觉区块
页面拆解的思路不是我发明的,而是从移动端性能优化里常用的“可见分块”来的。一个复杂的详情页,如果所有内容一次性渲染,内存和帧率都会扛不住,尤其皮肤原画的尺寸动不动就是几千乘几千像素。我把整个页面拆成下面六块:
- Hero 区:英雄皮肤原画作为沉浸式背景,顶部状态栏透明穿透
- 返回与标题栏:半透明浮层,包含返回按钮和当前皮肤名称
- 缩略图横滑条:皮肤系列全部皮肤的横向缩略图列表,点击切换
- 详情信息卡:英雄名字、皮肤标题、标签、发布时间、获取方式
- 大图画廊:上下滚动的完整原画大图区域
- 操作按钮:置灰的“尚未拥有”“购买”“试玩”等状态按钮
这个拆法有两个直接好处。第一,每个区块可以独立加载、独立做空状态和错误状态,不会因为一张大图加载失败毁了整页;第二,渲染顺序可以人工控制,Hero 区先渲染,信息卡跟上,大图画廊懒加载,用户感知到的首屏速度会明显变快。
2.2 定义数据模型与接口字段
客户端代码结构稳定之前,我会先把数据模型定下来,因为 RN 页面最烦的就是接口返回来一个很随意的 JSON,前端到处写 if 判断。皮肤详情的数据协议是自己服务端定义的,字段尽量精简,一次详情请求返回页面需要的全部数据。
typescript复制export interface SkinSummary {
id: number;
championId: number;
heroAvatar: string;
skinName: string;
skinPreview: string;
}
export interface SkinDetail extends SkinSumm
