一篇文档讲清 RNOH 响应时延与完成时延性能分析
本文面向 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 建立统一时间轴
报告中同时保留绝对时间戳和相对操作起点的毫秒数,例如:
| 时间点 | 绝对时间戳 | 相对起点 | 来源 |
|---|---|---|---|
| 输入起点 | ts0 | 0 ms | 系统输入 marker |
| RN Surface 收到 DOWN | ts1 | +x ms | RNOH trace |
| JS handler 开始 | ts2 | +x ms | 业务/JS trace |
| Fabric commit | ts3 | +x ms | RNOH marker |
| native batch 结束 | ts4 | +x ms | RNOH 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 已及时结束,但响应帧仍晚上屏:
- 检查 App Frame 是否已经异常;
- 检查应用主线程的 ArkUI measure/layout/flush;
- 比较 RS Frame 和应用提交时间;
- 检查 GPU、图片上传、复杂图层和系统资源竞争。
App Frame 正常而 RS Frame 异常时,优先定界到系统合成侧。两侧均异常时,需要通过时间顺序判断是应用晚提交传导,还是 RS 阻塞反压。
6. 完成时延定位
6.1 页面切换完成时延
将完成窗口至少分为:
| 阶段 | 主要观察点 | 常见责任域 |
|---|---|---|
| 操作到路由开始 | JS handler、原生路由调度 | RN/ArkTS 业务 |
| 容器创建 | RN Surface、原生页面和组件初始化 | RNOH/原生业务 |
| 数据准备 | 网络、数据库、缓存、序列化 | 业务/服务侧 |
| React 产出 | render、commit、diff、layout | RN 业务/三方组件 |
| native 挂载 | mutation 数量、组件创建、属性更新 | RNOH/组件实现/业务规模 |
| ArkUI/RS 上屏 | App Frame、RS Frame、flush/合成 | ArkUI/系统/资源 |
| 关键内容完成 | 图片、动画、二次请求、占位替换 | 业务侧 |
只有某阶段位于端到端关键路径并推迟完成终点,才应计入根因。后台并行工作即使耗时很长,也可能不影响用户看到的完成状态。
6.2 RN 首次加载和启动阶段
RNOH 提供以下内部 marker/聚合指标:
| 阶段 | marker/指标 | 用途 |
|---|---|---|
| 应用启动 | APP_STARTUP_START/STOP、APP_STARTUP | RNOH 记录的启动范围 |
| JS 运行时 | INIT_JS_RUNTIME_START/STOP | Hermes/JSVM 初始化 |
| bundle 下载 | DOWNLOAD_START/END、SCRIPT_DOWNLOAD | 远端 bundle 获取 |
| bundle 执行 | RUN_JS_BUNDLE_START/STOP、SCRIPT_EXECUTION | JS bundle 执行 |
| RN 实例初始化 | REACT_INSTANCE_INIT_START/STOP | React 实例初始化 |
| 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.cpp | onTouchEvent trace、CONTENT_APPEARED |
| 触摸分发 | cpp/RNOH/arkui/TouchEventDispatcher.cpp | 命中测试、坐标转换、MOVE 降噪、EventEmitter |
| TurboModule | cpp/RNOH/ArkTSTurboModule.cpp | call、callSync、目标线程同步执行 |
| Fabric 调度 | cpp/RNOH/SchedulerDelegate.cpp | transaction、telemetry marker、并行/切片 |
| native 挂载 | cpp/RNOH/MountingManagerCAPI.cpp | doMount、didMount、batch marker |
| 同步视图更新 | cpp/RNOH/RNInstanceCAPI.cpp | FABRIC_UPDATE_UI_MAIN_THREAD_* 的真实范围 |
| 启动指标 | cpp/RNOH/Performance/PerformanceMetricsRegistry.h | 各指标起止 marker |
| marker 输出 | cpp/RNOH/Performance/HiTraceRNOHMarkerListener.cpp | marker 到 HiTrace 的映射 |
| 性能浮层 | ets/RNOH/RNPerfMonitorView.ets | 轮询周期与展示项 |
12. 相关文档
更多推荐



所有评论(0)