本文面向 RNOH 应用开发者,说明如何定位点击、滑动、页面切换和 RN 页面加载过程中的响应时延与完成时延。分析前请先阅读RNOH 框架性能问题定界;滑动过程的连续丢帧问题见页面滑动卡顿丢帧问题定位。

1. 先定义测量口径

1.1 响应时延

响应时延描述用户输入到首个可见反馈之间的时间,例如按钮按下态、页面开始移动或首个新内容上屏。

响应时延 = 首个响应帧上屏时间 - 输入起点

系统工具的输入起点可能是点击事件上报而非物理触屏时刻,具体取决于工具和场景。RNOH 的 #RNOH::ArkUISurface::onTouchEvent::<ACTION> 只能证明触摸已进入 RN Surface,不能代替系统端到端起点。

1.2 完成时延

完成时延描述操作开始到目标页面达到约定完成状态之间的时间:

完成时延 = 完成终点 - 操作起点

“完成”不是天然唯一的概念。系统场景化工具、云测试和业务监控可能分别使用页面切换目标帧、动效结束、占位内容完成或关键业务内容可见作为终点。开始测试前必须写明:

  • 输入起点来自物理输入、系统事件上报还是业务打点;
  • 响应终点对应哪个可见变化;
  • 完成终点对应系统帧、动画结束还是业务内容;
  • 工具名称、版本和已知限制。

优化前后必须使用同一组起止点。HarmonyOS 工具口径和限制请参考性能分析与 HiSmartPerf Device 性能使用指导。

1.3 RNOH 内部指标不是系统完成时延

RNOH 当前实现中:

  • CONTENT_APPEARED 在根 ArkUI 节点加入 NodeContent 后记录;
  • ROOT_VIEW_TTI 由 APP_STARTUP_START 到 CONTENT_APPEARED 聚合;
  • APP_STARTUP 由 APP_STARTUP_START/STOP 聚合。

这些指标适合分解 RNOH 启动阶段,但 CONTENT_APPEARED 早于实际像素上屏,也可能早于数据、图片和关键业务内容完成;ROOT_VIEW_TTI 这个名称也不表示通用的“页面已经可交互”。报告中应写出 marker 的真实起止点,避免仅按名称推断。

2. 端到端时延链路

2.1 响应链路

物理输入/系统输入分发
  → 应用主线程收到 ArkUI 触摸事件
  → RNOH TouchEventDispatcher 命中测试与事件发射
  → RNOH_JS 取出事件并执行 JS handler
  → React render/commit/diff/layout
  → RNOH_MAIN 挂载 mutations
  → ArkUI 布局/绘制
  → Render Service 合成
  → 首个响应帧上屏

并非每次响应都经过完整链路。例如,ArkUI 原生按压态或原生滚动可以在 JS 更新前产生反馈。应从实际首个可见变化反向查找其生产者。

2.2 完成链路

操作开始
  → 路由/页面容器创建
  → 数据、网络、磁盘或缓存准备
  → JS render/React commit
  → Fabric diff/layout
  → RNOH native 挂载
  → ArkUI/Render Service 上屏
  → 动画、图片或业务关键内容达到完成条件

完成时延可能包含网络和业务异步阶段,不能把整个区间都归给渲染框架。应为数据请求开始/结束、JS render、挂载和业务完成分别打点。

3. 定位前准备

  • 使用 release 构建和 release bundle,关闭 Metro、HMR、Inspector、高频日志和正式测量时的性能浮层。
  • 固定设备、HarmonyOS/RNOH 版本、刷新率策略、网络、账号、缓存和测试数据。
  • 冷启动、热启动、首次进入页面、再次进入页面必须分别统计。
  • 每种操作重复至少 5 次;报告中至少给出中位数和最差样本,批量测试建议增加 P90/P95。
  • 为操作开始、请求完成、关键数据到达、动画结束和关键内容可见增加业务 trace。
  • 同时采集 App Frame、RS Frame、应用主线程、RNOH_JS、调度状态和调用栈。

性能浮层的 JS FPS/UI FPS 是 500 ms 轮询的趋势指标,适合快速观察持续负载,不适合直接定位一次几十毫秒的点击响应问题。

4. 总体分析流程

定义输入、响应和完成三个点
  → 用系统帧/工具确认响应或完成异常
  → 锁定同一次操作的时间窗口
  → 把窗口分成输入、JS、Fabric、挂载、ArkUI/RS、业务异步阶段
  → 找到最先推迟后续阶段的环节
  → 用功能开关、负载缩放、版本 A/B 验证

4.1 建立统一时间轴

报告中同时保留绝对时间戳和相对操作起点的毫秒数,例如:

时间点绝对时间戳相对起点来源
输入起点ts00 ms系统输入 marker
RN Surface 收到 DOWNts1+x msRNOH trace
JS handler 开始ts2+x ms业务/JS trace
Fabric committs3+x msRNOH marker
native batch 结束ts4+x msRNOH marker
首个响应帧上屏ts5+x ms系统帧
业务完成ts6+x ms系统/业务 marker

如果 trace 中没有 JS handler 或业务完成点,应先补充打点,不要用名字相似但语义不同的切片代替。

4.2 找“最早变慢点”

将异常样本与正常样本逐段比较。第一个明显扩大的区间通常比“最长切片”更接近根因:

  • ts0 → ts1 变长:系统输入分发、应用主线程调度或前序阻塞;
  • ts1 → ts2 变长:事件交接、JS 排队或 JS 线程阻塞;
  • ts2 → ts3 变长:业务 handler、React render/commit;
  • ts3 → ts4 变长:Fabric diff/layout、跨线程调度或 native 挂载;
  • ts4 → ts5 变长:ArkUI 刷新、Render Service、GPU 或帧截止;
  • ts5 → ts6 变长:动效、网络、图片或业务内容完成策略。

5. 响应时延定位

5.1 系统输入到 RN Surface

RNOH 在 ArkUISurface.cpp 中记录:

#RNOH::ArkUISurface::onTouchEvent::DOWN
#RNOH::ArkUISurface::onTouchEvent::MOVE
#RNOH::ArkUISurface::onTouchEvent::UP
#RNOH::ArkUISurface::onTouchEvent::CANCEL

若系统输入锚点到该切片开始的间隔异常,先检查应用主线程是否被 ArkTS 业务、I/O、同步任务或系统调度阻塞。此时事件尚未进入 RNOH 触摸分发,不能直接归因 JS。

5.2 RNOH 命中测试与事件发射

TouchEventDispatcher 在应用主线程执行目标查找、坐标变换和 TouchEventEmitter 事件发射。复杂触摸树和变换可能增加耗时,但必须先在调用栈中证明该区间确实长,再做结构优化。

canIgnoreMoveEvent 会忽略所有触点位移不超过 1 px 的 MOVE;它是事件降噪,不是点击响应时延的通用优化开关。原生 Scroll 包裹 RN Surface 时,cancelTouches() 用于避免滚动和 RN 点击冲突,这属于事件正确性问题;只有误触发额外业务或阻塞关键路径时才会转化为性能问题。

5.3 JS 排队和事件处理

触摸通过 TouchEventEmitter 进入 React 事件系统,不应把 RNInstanceInternal::callJSFunction 当作所有触摸事件的固定交接点。建议给目标 JS handler 加自定义 trace,并检查:

  • RNOH_JS 在事件到达前是否被长计算、GC、同步 TurboModule 或上一批 React 工作占用;
  • handler 是否同步解析大数据、执行循环或触发大量状态更新;
  • handler 结束后 Fabric commit 是否及时开始;
  • 关闭 handler 中某段业务后,响应帧是否同步提前。

如果 RNOH_JS 长时间 Runnable 而非 Running,还要检查 CPU 竞争、QoS 和温控,而不是直接优化 JS 代码。

5.4 React、Fabric 与 native 挂载

重点关注:

  • FABRIC_COMMIT_*、FABRIC_DIFF_*、FABRIC_LAYOUT_*;
  • #RNOH::SchedulerDelegate::performTransaction;
  • #RNOH::MountingManager::doMount、didMount <count>;
  • FABRIC_BATCH_EXECUTION_*;
  • ArkUI FlushDirtyNodeUpdate、measure/layout/flush 等切片。

FABRIC_UPDATE_UI_MAIN_THREAD_* 当前只表示 synchronouslyUpdateViewOnUIThread,不能把 FABRIC_COMMIT_START → FABRIC_UPDATE_UI_MAIN_THREAD_END 定义为通用挂载完成时延。

如果首个反馈需要创建庞大组件树,即使总完成时延可接受,响应时延也会变差。应先提交轻量即时反馈,再加载非关键内容。

5.5 ArkUI 与 Render Service

若 native batch 已及时结束,但响应帧仍晚上屏:

  1. 检查 App Frame 是否已经异常;
  2. 检查应用主线程的 ArkUI measure/layout/flush;
  3. 比较 RS Frame 和应用提交时间;
  4. 检查 GPU、图片上传、复杂图层和系统资源竞争。

App Frame 正常而 RS Frame 异常时,优先定界到系统合成侧。两侧均异常时,需要通过时间顺序判断是应用晚提交传导,还是 RS 阻塞反压。

6. 完成时延定位

6.1 页面切换完成时延

将完成窗口至少分为:

阶段主要观察点常见责任域
操作到路由开始JS handler、原生路由调度RN/ArkTS 业务
容器创建RN Surface、原生页面和组件初始化RNOH/原生业务
数据准备网络、数据库、缓存、序列化业务/服务侧
React 产出render、commit、diff、layoutRN 业务/三方组件
native 挂载mutation 数量、组件创建、属性更新RNOH/组件实现/业务规模
ArkUI/RS 上屏App Frame、RS Frame、flush/合成ArkUI/系统/资源
关键内容完成图片、动画、二次请求、占位替换业务侧

只有某阶段位于端到端关键路径并推迟完成终点,才应计入根因。后台并行工作即使耗时很长,也可能不影响用户看到的完成状态。

6.2 RN 首次加载和启动阶段

RNOH 提供以下内部 marker/聚合指标:

阶段marker/指标用途
应用启动APP_STARTUP_START/STOP、APP_STARTUPRNOH 记录的启动范围
JS 运行时INIT_JS_RUNTIME_START/STOPHermes/JSVM 初始化
bundle 下载DOWNLOAD_START/END、SCRIPT_DOWNLOAD远端 bundle 获取
bundle 执行RUN_JS_BUNDLE_START/STOP、SCRIPT_EXECUTIONJS bundle 执行
RN 实例初始化REACT_INSTANCE_INIT_START/STOPReact 实例初始化
TurboModule 装配TURBO_MODULE_SETUP_START/STOP模块注册/装配
根节点附着CONTENT_APPEARED根 ArkUI 节点加入 NodeContent
内部 root 指标ROOT_VIEW_TTI当前实现为 APP_STARTUP_START → CONTENT_APPEARED

用这些 marker 找出 RNOH 内部最长阶段后,还要继续检查 CONTENT_APPEARED 到首帧上屏、数据到达和关键内容完整之间的时间。否则只能说明“根节点已附着”,不能说明完成时延正常。

6.3 数据和图片晚于首帧

如果页面壳先出现,文本或图片随后补齐,应分别记录:

  • 请求发出和返回;
  • 数据转换、状态更新和 JS render;
  • React/Fabric transaction;
  • 图片下载、解码、上传;
  • 最终内容上屏。

文本和图片都晚通常优先检查数据绑定或 transaction 时机;文本已显示而图片晚,优先检查图片链路。不要仅凭图片 decode 切片解释“文本和图片都空”的现象。

7. 常见根因与处理

7.1 即时反馈缺失

特征:输入和业务逻辑都及时执行,但在重页面完成前没有任何视觉变化。

处理:将按钮按压态、骨架屏、导航开始等轻量反馈放在关键路径前部;这能改善响应时延,但不能掩盖完成时延,应分别统计。

7.2 JS 长任务或同步 TurboModule

特征:事件进入 RN 后,RNOH_JS 长时间不处理目标 handler;或 #RNOH::ArkTSTurboModule::call/callSync 覆盖延迟窗口。

处理:拆分 JS 长任务,减少同步序列化;仅对不依赖 UI 线程的 TurboModule 考虑迁移到 worker。同步调用会让调用线程等待目标线程返回,不能只看目标线程的执行耗时。

7.3 首次加载工作集中

特征:bundle 执行、TurboModule 装配、首屏 React render 或大量 create mutations 集中在一个窗口。

处理:缩小首屏 bundle 和组件树,延后非关键模块与内容,使用骨架屏;验证预加载、预分配和并行路径是否真正命中,并比较冷/热场景。

7.4 native 挂载和 ArkUI 布局过重

特征:Fabric 已按时产出,主线程集中在 didMount、组件创建、属性更新、measure/layout/flush,且耗时随节点规模变化。

处理:减少一次提交的节点数和层级,拆分非首屏内容,优化自定义组件创建/属性更新,避免同步 I/O 或图片处理进入主线程。

7.5 GC、图片和资源竞争

特征:JS VM/PSS 快速增长后出现 JS 停顿;图片 decode/upload 位于关键路径;应用线程 Runnable 但未获 CPU;RS Frame 单独异常。

处理:降低瞬时对象分配和图片规格,调整缓存;控制整机负载与温度;RS/GPU 异常交由系统图形链路继续分析。这些证据不足以直接认定 RNOH 框架问题。

8. 验证根因

每个结论至少做一种对照:

  • 关闭目标 JS handler 的非必要逻辑;
  • 把首屏节点或数据量降为 1/2、1/4;
  • 用本地数据替换网络请求;
  • 用占位图片替换真实图片;
  • 移除三方组件或切换为最小 RNOH 组件;
  • 同设备、同应用仅切换 RNOH 版本;
  • 对比纯 ArkUI 页面或静态 RN 页面。

如果处理某个长切片后端到端时延没有同步下降,该切片不是当前完成时延的主关键路径,或瓶颈已经转移,应重新比较时间轴。

9. 根因报告示例

口径:输入事件上报到首个新页面帧上屏为响应时延;
      输入事件上报到关键列表占位全部替换为完成时延。

异常样本:响应 240 ms,完成 1380 ms;正常样本分别为 72 ms、610 ms。
锚点:输入 ts=...。

证据链:
1. 输入 +18 ms 已进入 ArkUISurface,系统输入分发正常;
2. RNOH_JS 到 +171 ms 才开始目标 handler,此前被同步数据转换占用;
3. handler 结束后 +14 ms 内出现 Fabric commit,native batch 未超当前帧预算;
4. 首个响应帧在 JS 提交后上屏,RS Frame 正常;
5. 移除同步数据转换后响应降至 78 ms,完成降至 650 ms。

结论:RN 业务 JS 长任务导致响应和完成时延,不是 RNOH 挂载或 RS 问题。

10. 排查清单

  • 已明确输入、响应、完成三个点及工具口径
  • 冷/热、首次/再次进入页面已分开统计
  • 已用同一锚点对齐系统帧、应用线程、JS 和业务 trace
  • 已比较正常/异常样本的分段时长,并找到最早变慢点
  • 未把 callJSFunction 当作通用触摸交接点
  • 未把 FABRIC_UPDATE_UI_MAIN_THREAD_* 当作通用挂载总时长
  • 未把 CONTENT_APPEARED 或 ROOT_VIEW_TTI 直接当作系统完成时延
  • 已检查 Running/Runnable/阻塞状态,而非只看 CPU 或 FPS
  • 已区分数据、图片、React、挂载、ArkUI 和 RS 阶段
  • 已通过功能开关、负载缩放或版本 A/B 验证根因

11. 关键代码索引

以下路径均位于 packages/tester/harmony/react_native_openharmony/src/main/:

模块文件关注点
RN Surface 触摸入口cpp/RNOH/arkui/ArkUISurface.cpponTouchEvent trace、CONTENT_APPEARED
触摸分发cpp/RNOH/arkui/TouchEventDispatcher.cpp命中测试、坐标转换、MOVE 降噪、EventEmitter
TurboModulecpp/RNOH/ArkTSTurboModule.cppcall、callSync、目标线程同步执行
Fabric 调度cpp/RNOH/SchedulerDelegate.cpptransaction、telemetry marker、并行/切片
native 挂载cpp/RNOH/MountingManagerCAPI.cppdoMount、didMount、batch marker
同步视图更新cpp/RNOH/RNInstanceCAPI.cppFABRIC_UPDATE_UI_MAIN_THREAD_* 的真实范围
启动指标cpp/RNOH/Performance/PerformanceMetricsRegistry.h各指标起止 marker
marker 输出cpp/RNOH/Performance/HiTraceRNOHMarkerListener.cppmarker 到 HiTrace 的映射
性能浮层ets/RNOH/RNPerfMonitorView.ets轮询周期与展示项

12. 相关文档

Logo

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

更多推荐