本文面向 RNOH 应用开发者,用于在深入分析代码前先回答一个问题:页面卡顿丢帧、响应时延或完成时延异常,是否由 RNOH 框架引起。

定界只确定责任域和后续排查方向,不替代根因定位。完成定界后,请继续阅读:

1. 定界结论应回答什么

一次有效定界至少给出以下结论之一:

责任域典型证据后续处理方
HarmonyOS 系统或 Render ServiceRS Frame 异常,应用按时提交;同设备多个应用同时劣化系统/图形侧
原生宿主或 ArkTS 业务应用主线程被非 RNOH 的 ArkTS、I/O、图片处理等任务占满原生业务侧
RN JS 业务RNOH_JS 长任务、频繁 React 提交或事件回调过重RN 业务侧
三方 RN 组件异常调用栈集中在三方组件,移除或降级后恢复三方组件维护方
RNOH 框架异常窗口关键路径落在 RNOH 实现,最小示例仍复现,且版本对比显示框架回归RNOH 框架侧
证据不足只有主观现象、平均 FPS 或单个长切片,不能形成端到端证据链补充采集

“使用了 RNOH”不等于“RNOH 框架问题”。例如,大量业务组件一次性提交后,挂载耗时发生在 RNOH 主线程,但如果耗时随业务节点数增长且最小 RNOH 页面正常,通常应先归为 RN 业务负载或组件使用问题,而不是框架缺陷。

2. 统一指标口径

2.1 卡顿和丢帧

帧预算由问题窗口内的实际目标刷新率决定:

单帧预算(ms) = 1000 / 目标刷新率(Hz)

例如,60 Hz、90 Hz、120 Hz 对应的理论预算约为 16.7 ms、11.1 ms、8.3 ms。设备可能动态切换刷新率,因此不能固定使用 16.7 ms,也不能仅用“FPS 低于 60”判断丢帧。应在 DevEco Profiler 中读取 Display、H:PreferredFrameRate 或等价信息,并结合 App Frame、RS Frame 判断。

2.2 响应时延

响应时延用于描述输入事件到首个可见反馈之间的时间。系统工具的具体起止点可能受事件上报和工具版本影响;例如起点可能是点击事件上报而不是物理触屏时刻,终点通常取首个响应帧上屏。因此报告中必须记录所用工具和起止点,不能把 RNOH 内部的触摸 trace 直接当作端到端响应时延。

2.3 完成时延

完成时延用于描述操作开始到目标页面达到工具所定义“完成状态”的时间。不同测试工具可能以页面切换后的目标帧、动画结束或占位内容加载完成作为终点。业务还可能把“关键内容可见”定义为完成。测试基线、问题复现和优化前后对比必须使用同一终点。

RNOH 的 CONTENT_APPEARED 表示根 ArkUI 节点加入 NodeContent;ROOT_VIEW_TTI 当前由 APP_STARTUP_START 到 CONTENT_APPEARED 聚合。它们是 RNOH 内部阶段标记,不证明像素已经上屏、页面内容完整,也不证明页面已经可交互。

HarmonyOS 指标和工具限制请以性能分析与 HiSmartPerf Device 性能使用指导为准。

3. RNOH 性能链路

一次 RN 页面更新通常跨越以下环节:

输入/业务触发
  → HarmonyOS 输入分发或 ArkTS 宿主逻辑
  → RNOH_MAIN 逻辑任务(触摸分发、挂载调度)
  → RNOH_JS(事件处理、React render/commit)
  → Fabric diff/layout 与 mutations
  → RNOH_MAIN(组件创建、属性更新、挂载)
  → ArkUI 布局/绘制
  → Render Service 合成
  → 屏幕显示

RNOH_MAIN 是 RNOH 的逻辑任务线程,复用宿主 N-API/uv 事件循环;在 trace 中通常需要通过应用主线程身份和 RNOH 调用栈确认,不能只按线程名匹配。RNOH_JS 是独立线程,但线程名也只是一条线索,最终仍需用调用栈和时间关系确认。

4. 定界前准备

4.1 固定测试条件

  • 使用 release 构建和 release bundle,关闭 Metro、HMR、Inspector 和高频调试日志。
  • 固定设备型号、HarmonyOS 版本、RNOH 版本、刷新率策略、网络和测试数据。
  • 记录温度、是否充电、电量和电源模式;先预热,再重复相同操作至少 5 次。
  • 性能浮层只用于快速观察。正式测量时关闭浮层、录屏和其他可能改变负载的调试功能。
  • 同时保留正常样本和异常样本,避免只分析单次偶发 trace。

4.2 准备采集物

建议每个问题至少提供:

  • 复现步骤、期望结果、实际现象和问题发生的大致时间点;
  • 应用包类型、设备/系统/RNOH 版本、屏幕刷新率;
  • DevEco Profiler trace 或可查询的 trace 数据库;
  • App Frame、RS Frame、应用主线程、RNOH_JS 和 Render Service 泳道;
  • 操作录屏或截图,以及与 trace 对齐的业务打点;
  • 正常/异常版本或最小示例的对照数据。

5. 六步定界法

5.1 第一步:确认系统级异常真实存在

不要先看 RNOH 调用栈。先用系统指标确认用户可见问题:

场景首选证据不能单独作为结论的证据
滑动卡顿问题窗口内 App/RS Frame、Present Fence、目标刷新率、连续丢帧平均 FPS、JS FPS、肉眼感受
响应慢输入锚点到首个响应帧上屏某个触摸函数耗时、一次 JS FPS 下跌
完成慢操作锚点到约定完成帧/业务完成点CONTENT_APPEARED、启动总耗时中的任一单段

如果系统级帧或时延指标正常,应先检查动画设计、反馈缺失、白块或业务完成点定义,而不是继续按“丢帧”处理。

5.2 第二步:锁定同一个异常窗口

选取一个明确锚点,例如触摸 DOWN、页面切换打点或滑动开始,并在报告中同时记录绝对时间戳和相对时间。后续的帧、调度、调用栈和 RNOH marker 都必须落在同一个窗口内。

不要把不同次操作的最低 FPS、最长 JS 任务和最慢主线程切片拼接成一条因果链。

5.3 第三步:先分应用侧和系统合成侧

App FrameRS Frame初步结论
异常正常应用未按时产帧,进入应用侧定界
正常异常Render Service、GPU、图层合成或系统资源竞争优先
异常异常追踪最早出现异常的一侧;可能是应用晚提交向后传导,也可能是合成阻塞反压
均正常体感异常检查业务反馈、白块、数据到达、动效设计和指标终点

App Frame 和 RS Frame 都异常时,不能只因应用线程存在长任务就认定应用负责。应比较时间顺序、帧依赖和调度状态。

5.4 第四步:在应用内分线程和等待关系

检查异常窗口内:

  1. 应用主线程是持续运行、Runnable 但抢不到 CPU,还是休眠等待;
  2. RNOH_JS 是否有覆盖异常窗口的长任务或大量排队任务;
  3. Fabric commit/diff/layout 和挂载是否在异常帧截止前完成;
  4. 是否存在同步 TurboModule、锁、I/O、图片解码、GC 或跨线程等待;
  5. 主线程结束后,Render Service 是否仍出现额外长耗时。

线程“忙”只说明存在负载。只有该负载位于异常帧或时延的关键路径,并推迟了可见结果,才能作为根因证据。

5.5 第五步:区分 RNOH 框架和业务负载

按以下顺序做对照,每次只改变一个变量:

  1. 静态页面对照:保留同一 RNOH Surface,移除交互回调、动画和数据更新。
  2. 业务功能二分:依次关闭埋点、图片、复杂列表项、同步模块和三方组件。
  3. 负载缩放:按 1/2、1/4 缩小节点数或数据量,观察耗时是否近似随业务规模下降。
  4. 纯 ArkUI 对照:用相同资源和相近节点规模实现关键路径,区分系统/资源问题与 RN 链路问题。
  5. RNOH 版本对照:同一应用、设备和数据仅切换 RNOH 版本,确认是否为框架回归。
  6. 最小示例:保留可触发异常的最少 RNOH API 和组件,验证框架问题能否独立复现。

以下证据组合才足以支持“RNOH 框架问题”:

  • 系统级异常已确认,且首先发生在应用侧;
  • 异常窗口的关键路径被 RNOH 实现占用或被其同步关系阻塞;
  • 去除业务负载后仍能在最小示例复现,或同负载下 RNOH 版本 A/B 显示明确回归;
  • 已排除刷新率变化、温控降频、系统抢占、Render Service、网络和资源解码等替代解释。

5.6 第六步:输出可复核结论

建议使用以下格式:

结论:RNOH 框架问题 / RN 业务问题 / 原生业务问题 / 系统侧问题 / 证据不足

现象与指标:
- 目标刷新率或时延口径:
- 异常值、正常基线、复现次数:

时间窗口:
- 锚点绝对时间:
- 异常相对区间:

证据链:
1. 哪个系统帧或时延指标异常;
2. App 与 RS 哪一侧最先异常;
3. 哪个进程、线程和调用栈占据关键路径;
4. 对照实验如何变化。

排除项:
- 已排除的替代解释及证据。

下一步:
- 进入对应定位文档的章节或提交最小示例。

6. 三类问题的快速判定

6.1 滑动卡顿丢帧

确认滑动窗口和目标刷新率
  → App Frame 是否先异常?
    ├─ 否:优先检查 RS/GPU/系统资源竞争
    └─ 是:检查应用主线程和 RNOH_JS
         → RNOH_JS 迟交付:查 onScroll、React 更新、JS 长任务
         → 主线程挂载/ArkUI 布局超预算:查 mutations、节点规模、同步任务
         → 两者正常:检查图片、原生宿主、RS 和数据填充时机

详细方法见页面滑动卡顿丢帧问题定位。

6.2 响应时延

输入锚点 → 首个响应帧上屏
  → 输入到应用触摸入口慢:系统输入分发/主线程调度
  → 触摸分发完成但 JS 事件晚:RNOH_JS 排队或阻塞
  → JS 提交及时但首个变化晚上屏:挂载、ArkUI 或 RS
  → 没有任何视觉变化:先检查业务是否设计了即时反馈

6.3 完成时延

先定义“完成”终点
  → 数据/网络未就绪:业务或服务侧
  → JS render/commit 晚:RN JS 业务或三方组件
  → mutations 已产生但挂载晚:RNOH_MAIN、节点规模或同步阻塞
  → 应用按时提交但上屏晚:ArkUI/RS/系统侧
  → 仅 CONTENT_APPEARED 早:不能据此判定页面已经完成

详细方法见响应时延与完成时延性能分析。

7. RNOH trace 标记及限制

标记/切片可说明什么不能说明什么
#RNOH::ArkUISurface::onTouchEvent::<ACTION>触摸已进入某个 RN Surface 并进行分发物理输入起点、首帧已上屏
FABRIC_COMMIT_* / FABRIC_DIFF_* / FABRIC_LAYOUT_*React/Fabric 对应阶段的时间范围ArkUI 绘制或 RS 合成完成
FABRIC_BATCH_EXECUTION_*一批 mutations 在 native 挂载侧执行页面内容完整
#RNOH::MountingManager::doMount / didMountRNOH 挂载调用和 batch 大小线索长切片必然是框架缺陷
FABRIC_UPDATE_UI_MAIN_THREAD_*synchronouslyUpdateViewOnUIThread 的同步更新区间所有 Fabric transaction 的主线程挂载总时长
CONTENT_APPEARED根节点加入 NodeContent像素上屏、关键内容可见或可交互
ROOT_VIEW_TTI当前实现中从 APP_STARTUP_START 到 CONTENT_APPEARED 的内部聚合值通用 TTI 或系统完成时延

8. 常见错误定界

  • 只看 JS FPS/UI FPS 就分责:浮层是采样指标,适合发现趋势,不足以证明一次异常的关键路径。
  • 只看一个超过帧预算的函数:它可能不在异常帧、可能运行在非关键线程,也可能被后续流水线吸收。
  • 应用和 RS 同时红就判系统问题:应用晚提交也会向 RS 传导,必须看最早异常点。
  • RNOH 调用栈长就判框架问题:需要结合输入规模、最小示例和版本对照。
  • 把 CONTENT_APPEARED 当完成时延终点:该标记早于实际上屏和业务内容完整。
  • 用 debug 数据和 release 基线比较:构建、日志、Inspector 和 bundle 差异会破坏对照条件。
  • 把平均值当全部结论:平均 FPS 会掩盖最大连续丢帧,平均时延会掩盖 P95/P99 尖峰。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐