logo
积分中心
RN 性能调优之滑动卡顿问题介绍
头像 鸿蒙小助手 2026-10-21 14:43:44    发布
21 浏览 4 点赞 1 收藏

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:

RN 性能调优之滑动卡顿问题介绍-华为开发者话题 | 华为开发者联盟


     滑动卡顿丢帧是指应用在滑动过程中,画面刷新频率未达到流畅体验标准,导致用户感受到停顿、抖动或延迟的现象。《应用性能体验建议》 中针对滑动场景的指标包括应用或元服务内滑动操作响应快 与应用或元服务应用内滑动过程流畅 (滑动过程卡顿率为 0ms/s),当前 AppAnalyzer 工具已支持(或等效支持)上述指标项。当应用出现明显的滑动停顿、抖动,或快速滑动时出现空白块等现象时,需要分析丢帧的产生环节并针对性优化。

      本文是 RNOH 性能定位系列的第三篇,说明如何定位 RNOH 应用中的滑动卡顿丢帧问题,重点介绍业务侧问题的定位与优化;定界方法可将问题归入业务侧、RNOH 框架侧或系统侧。线程模型、trace 采集方法、trace 标记含义等基础内容见RN性能调优之基础定界与 trace 分析介绍;点击响应与页面完成时延问题见RN 性能调优之点击响应和完成时延分析介绍。

1. 指标定义

1.1 丢帧与帧预算

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


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

      60 Hz、90 Hz、120 Hz 对应的理论预算约为 16.7 ms、11.1 ms、8.3 ms。设备可能动态切换刷新率,定位时应从 Display、H:PreferredFrameRate 或等价系统信息读取实际目标刷新率,不能固定按 60 Hz 判断。

1.2 卡顿率与平均 FPS 的适用范围

      平均 FPS 只适合描述整体趋势,不能代替对最差帧和连续丢帧的分析。定位时应优先关注:

         • App Frame 和 RS Frame 中的超时帧(红色帧);

         • 卡顿率与累计丢帧时间;

         • 最大连续丢帧;

         • Present Fence 或实际显示帧间隔;

         • 同一异常窗口内应用主线程、RNOH_JS和 Render Service 的状态。

1.3 空白块与丢帧的区别

      空白块(又称白块)不一定意味着丢帧。页面可能按时产帧,但列表项数据、图片尚未填充完成,此时帧泳道正常,问题属于内容准备延迟。应同时检查帧泳道和内容更新时间,再决定按丢帧处理(§4)还是按内容准备延迟处理(§6.3)。

2. 丢帧问题原理

2.1 渲染管线

      HarmonyOS 图形系统采用统一渲染模式,遵循流水线处理:应用侧在每个 Vsync 周期内完成输入事件处理、动画、组件状态更新、测量(Measure)、布局(Layout)与绘制(Render),生成界面描述数据后提交给 Render Service;Render Service 在 Vsync 信号触发下执行动效、描画与提交,协调 GPU 完成合成,最终送屏幕显示。应用侧或 Render Service 侧任一环节超时,用户都会观测到丢帧。

      RNOH 应用的应用侧横跨主线程与 JS 线程:React 渲染、布局计算和 diff 在 JS 线程执行,组件挂载和 ArkUI 绘制在主线程执行(线程模型详见基础篇第 1 章)。

2.2 两类丢帧故障模型

      • AppDeadlineMissed(应用侧丢帧):应用未在帧截止时间前完成处理并提交。通常由应用逻辑代码处理效率低引起,RNOH 应用中还包含 JS 线程负载过重、Fabric 提交与挂载超时等情形;

      • RenderDeadlineMissed(RS侧丢帧):应用按时提交,但 Render Service 处理超时。通常由界面结构复杂或 GPU 渲染负载高引起。

      两类故障模型在 DevEco Profiler Frame 模板中可以通过 App Frame 与 RS Frame 泳道直观区分(§4.3)。

3. 问题定位流程

3.1 定位流程

      定位滑动丢帧问题通常需要以下步骤:

1. 识别卡顿:使用 AppAnalyzer 检测滑动场景是否存在性能问题,得到体检报告;

2. 确认丢帧:在 DevEco Profiler Frame 泳道查看丢帧率与最大连续丢帧,确认问题真实存在(§4.2);

3. 分解应用侧与系统合成侧:通过 App Frame 与 RS Frame 的异常先后完成初步定界(§4.3);

4. 分析丢帧原因:结合线程状态、调用栈与组件泳道定位耗时环节(§4.4);

5. 沿 RNOH 滑动链路深入分析:按滚动链路各环节的责任域展开(§5);

6. 实施优化并复测:参考 §6 优化案例实施处理,优化后重新测量确认达标;未达标则回到第 4 步重新分析。

3.2 定位前准备

      • 使用 release 构建和 release bundle,关闭 Metro、HMR、Inspector、高频日志和性能浮层后再正式采集;

      • 固定设备、系统/RNOH 版本、屏幕刷新率策略、数据、网络和操作方式;

      • 预热页面后重复相同滑动至少 5 次,并保留基线数据与问题数据;

      • 在业务侧为滑动开始、关键数据更新或吸顶切换添加 trace 点,方便与系统帧对齐;

      • 使用DevEco Profiler Frame/Time 工具采集 App Frame、RS Frame、调度和调用栈。只有原始.htrace时也应保留原文件;可导出数据库时优先使用数据库做可复核查询。

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

      AppAnalyzer 在页面中存在视频播放或动画补帧时,可能检测不到滑动组件的丢帧,例如对视频播放页的评论区执行滑动检测。此时应以 DevEco Profiler 的 trace 分析结果为准。

4. 使用 Profiler 工具检测和分析

      DevEco Profiler 的 Frame 模板用于深度分析卡顿丢帧的原因,录制完成的子泳道对应录制过程中各进程的帧数据。本节说明在滑动丢帧场景下的使用方法,更通用的操作说明可参考Frame 分析 与官方最佳实践《帧率问题分析》 。

4.1 抓取操作 trace

1. 使用 release 或 debugOptimized 构建模式安装应用,连接设备;

2. 打开 DevEco Studio → Profiler,选择设备与目标应用、进程,选择 Frame 模板,单击 Create Session 创建会话;

      3. 确认 RNOH trace 编译开关已开启(WITH_HITRACE_REACT_MARKER、WITH_HITRACE_SYSTRACE,配置方法见基础篇「2.2 开启 trace 编译开关」一节),否则 trace 中不会出现 ReactMarker::* 与 #RNOH::* 标记;

      4. 需要查看 ArkUI Component 泳道时,在录制前手动勾选该泳道;

      5. 单击启动按钮开始录制,在设备上执行待分析的滑动操作(预热后重复相同滑动),完成后停止录制并保存 trace 文件。

      泳道选择与录制配置的详细说明,见基础篇《RN性能调优之基础定界与 trace 分析介绍》的「2.3 抓取 trace」一节。

4.2 确认丢帧

      在时间轴上拖动鼠标选定要查看的时间段(例如 2.5 秒的区段),选中 Frame 主泳道,查看 Statistics 栏的丢帧数与丢帧率。

      在「RS Frame」与「App Frame」泳道中:

         • 正常完成渲染的帧显示为绿色;

         • 出现卡顿的帧显示为红色:期望结束时间点之前的部分为浅红色,超出期望结束时间的部分为深红色;

         • 异常帧显示为黄色。

      选中超时帧可查看详细数据:期望时间为 1000ms / 帧率(如 120Hz 对应约 8.3ms),实际处理时间超出期望时间即被识别为超时帧。

      选定时间段时应以滑动开始 trace、输入事件或录屏对齐同一次滑动,选取最差帧或最大连续丢帧区间,并记录绝对时间戳、换算相对时间。不同次滑动的数据不应拼接在一起分析。

4.3 分解应用侧与系统合成侧

      丢帧可能出现在应用侧或 Render Service 侧,通过两条帧泳道的异常先后完成初步定界:


App FrameRS Frame初步结论
异常正常应用未按时产帧,进入应用侧分析(§4.4、§5)
正常异常Render Service、GPU、图层合成或系统资源竞争优先
异常异常追踪最早异常的一侧;可能是应用晚提交向后传导,也可能是合成阻塞反压
均正常存在空白块内容准备延迟,不属于丢帧,按 §6.3 处理

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

4.4 分析丢帧原因

      按以下步骤定位,先排除系统侧,再分析应用侧:

      1. 看线程状态和运行核:检查应用主线程和 RNOH_JS 的调度状态与所在 CPU 核(结合 CPU Slice 与 Frequency 泳道),排除系统侧异常。线程高负载分为几种情况:持续 Running(确实在执行长任务,沿调用栈找热点)、长时间 Runnable(就绪但未获得 CPU,检查整机竞争、QoS 和温控)、休眠/阻塞(检查锁、同步 TurboModule、跨线程等待或 I/O;其中 IO 不可中断睡眠态表示线程在 I/O 或磁盘操作上被阻塞,低内存时触发 page fault 也会出现该状态)。若出现执行频率较低、线程在小核上运行(多核设备常按频率区分大中小核)、线程频繁在 Running 与 Runnable/Sleep 之间切换、不重要的线程占用大核等情况,可在线提单 反馈系统异常。

      2. 看应用侧帧内耗时:App Frame 出现红色时,跳转到卡顿帧的应用侧 trace 详情,查看该帧内各阶段耗时。RNOH 应用重点检查:

         • RNOH_JS线程:是否存在覆盖异常窗口的长任务(长段ExecuteJs、onScroll业务计算),事件是否持续排队;

         • 主线程:didMount、组件创建、属性更新、ArkUI measure/layout/flush 是否集中超时;

         • ReactMarker::FABRIC_COMMIT/DIFF/LAYOUT与#RNOH::MountingManager::doMount、didMount <count>的时序关系(§5.3)。

      3. 查看函数调用栈:通过 ArkTS Callstack 泳道查看 ArkTS 侧热点函数(双击可跳转源码);Native 侧高耗时函数切换到 Callstack 泳道分析;JS 侧长任务结合调用栈或火焰图定位。

      4. 查看组件绘制耗时:通过 ArkUI Component 泳道(需录制前勾选)观察组件的创建、布局、渲染频率与耗时,识别耗时明显偏长的组件。

      线程高负载本身只说明存在负载。只有该负载位于异常帧的关键路径并推迟了该帧提交,才能作为根因证据。

5. RNOH 滑动链路定位详解

5.1 ArkUI滚动与 JS 事件

      RNOH ScrollView 的一次 scroll 事件传递路径如下:

      ArkUI 自身可持续滚动,不要求 JS 每帧返回结果;Animated 的滚动事件在ArkUI侧旁路处理,不经过 JS。因此 RNOH_JS 负载高并不必然造成滚动丢帧;只有滚动视觉效果依赖 JS 计算、列表补项或 React 更新时,JS 延迟才会进入关键路径。

5.2 scrollEventThrottle的当前实现

      ScrollViewComponentInstance::onEmitOnScrollEvent() 决定一次滚动是否向 JS 分发 scroll 事件,满足以下任一条件即分发:

      • 即将到达scrollTo的目标位置(此时需要让 JS 感知,否则滚动无法正确停止);

      • 距上次分发已达到scrollEventThrottle设定的间隔,且发生了显著位移。

      当前实现中 scrollEventThrottle == 0 会放开时间间隔门控,但同一帧内的重复事件仍会被合并(不会一帧发多次);滚动位置的内部状态回写另有约 51 ms 的节流,这与业务 onScroll 回调频率不是同一个概念。

      scrollEventThrottle 不建议固定为 16 ms:在 120 Hz 屏幕上该值约等于每两帧分发一次。应结合业务是否需要逐帧 JS 回调、回调耗时和目标刷新率实测确定。仅用于动画联动时,优先使用ArkUI驱动能力,减少 JS 参与逐帧渲染。

5.3 Fabric 与挂载

      滑动触发的 React 更新遵循渲染三阶段(渲染/提交/挂载,见基础篇「1.2 渲染三阶段」),重点关注:


trace/marker含义
FABRIC_COMMIT_*React/Fabric commit 时间
FABRIC_DIFF_*ShadowTree diff 时间
FABRIC_LAYOUT_*Fabric layout 时间
#RNOH::SchedulerDelegate::performTransactiontransaction 调度入口
#RNOH::MountingManager::doMountmutations 交给挂载实现
#RNOH::MountingManager::didMount <count>batch 执行及数量线索
FABRIC_BATCH_EXECUTION_*mutations batch 的 native 执行区间

      FABRIC_UPDATE_UI_MAIN_THREAD_* 只包围 synchronouslyUpdateViewOnUIThread,不能用来代表所有 transaction 的主线程挂载时长。

      如果 commit/diff/layout 很长,优先检查 JS 更新和组件树规模;如果这些阶段按时完成但 didMount 或 ArkUI measure/layout 超预算,优先检查 mutations 数量、组件创建成本和ArkUI布局。

6. 优化案例

      本节针对 RN 应用中最常见的三类滑动丢帧问题给出优化案例:onScroll 回调过重、列表项重渲染过多、列表虚拟化配置不当。每个案例按"问题现象 → 定位分析 → 优化措施 → 效果验证"组织。优化后应复测确认丢帧达标,未达标则回到 §3.1 重新分析。更多场景的处理手段可参考页面转场与滑动场景优化。

6.1 案例一:onScroll回调过重导致滑动丢帧

      问题现象:列表惯性滚动时出现连续丢帧,滚动联动动画落后于手势。120Hz 设备上选中一个超时帧查看详情:期望处理时间约 8.3ms,实际处理 22.8ms。

      定位分析:

         1. App Frame 先异常而 RS Frame 正常(§4.3),定界到应用侧;

         2. 跳转到该帧的应用侧 trace 详情:RNOH_JS 在该帧内持续执行 14.6ms,位于 onScroll 回调内;

         3. 展开调用栈定位到具体函数:回调内对列表数据做同步排序,并为每个事件创建大对象;JS 任务结束后 Fabric commit 才开始,该帧错过截止时间。

      优化措施:

         • 排序逻辑移出onScroll,改为数据变化时执行一次并缓存结果;埋点、预取等非必要逻辑移到滚动结束后(onMomentumScrollEnd)或异步任务;

         • 避免在回调内创建大对象、解析大 JSON 或触发多次状态更新;

         • 滚动联动动画改用ArkUI驱动(useNativeDriver: true),插值计算不再经过 JS。

      效果验证:优化后该单帧处理时间回到 8.6ms,连续丢帧消失;关闭排序逻辑前后的对照数据确认了因果关系。

6.2 案例二:列表项重渲染过多导致滑动丢帧

      问题现象:长列表快速滑动时掉帧明显,滚动期间 FPS 波动剧烈;基线数据中同样滑动距离的渲染窗口共 700ms,问题窗口 1700ms。

      定位分析:

         1. 问题窗口内每个 scroll 事件后都紧跟成串的 FABRIC_COMMIT/DIFF/LAYOUT,提交频率约为基线的 3 倍;

         2. React DevTools Profiler 显示滚动期间列表项(Item)的 render 次数是基线的 3 倍。原因是业务把滚动位置写入了页面级 state,导致整个列表跟随重渲染;

         3. renderItem 内联定义、onPress 使用匿名函数,React.memo 全部失效。

      优化措施:

         • 只把滚动位置写入真正依赖它的子组件(如吸顶指示器),不再写入页面级 state;

         • 列表项用React.memo包裹,onPress用useCallback缓存、数据引用用useMemo保持稳定,renderItem提取为独立函数;

         • 合并状态更新,缩小每次提交的组件范围。

      效果验证:优化后渲染窗口降至 760ms,Item render 次数回到基线水平;滑动期间 commit 成串的现象消失。

6.3 案例三:列表虚拟化配置不当导致快速滑动空白块

      问题现象:快速滑动时新出现的列表项为空白,约 480ms 后内容才补齐;帧泳道正常(属内容准备延迟,见 §1.3),但影响使用体验。

      定位分析:

         1. Fabric batch 中 create 数量集中出现,随后 ArkUI measure 明显增长,说明新列表项的创建和测量跟不上滑动速度;

         2. 列表项高度动态计算(未提供 getItemLayout),windowSize 保持默认,离屏组件大量驻留。

      优化措施(配置按收益逐步叠加,效果见图中三档对比):

         • 仅调小windowSize:空白时长从约 480ms 降到约 300ms;

         • 固定尺寸列表项提供getItemLayout,让 RN 直接按索引计算偏移、跳过异步测量;initialNumToRender只渲染首屏数量,maxToRenderPerBatch控制每批补项规模。两项配置后空白块消除,补项耗时降至约 90ms;

         • removeClippedSubviews可进一步减少离屏 ArkUI 节点参与绘制,但在复杂变换下有显示风险,A/B 验证后再启用。

      效果验证:三档配置分别复测确认。动态高度场景若无法提供准确的 getItemLayout 尺寸,不应强行配置,应先固定列表项布局再启用。

6.4 其他场景速查


现象定位要点处理方向
吸顶(Sticky Header)、分页切换瞬间跳变异常窗口主线程集中在 didMount、组件创建、measure/layout/flush,耗时随节点数增长限制吸顶组件复杂度,避免动态高度;拆分非首屏内容;吸顶状态与列表其他状态解耦
FlashList 等三方列表组件卡顿RNOH 对 AutoLayoutView/CellContainer 采用串行保障路径,串行本身不是缺陷确认组件版本兼容;迁移时必须提供 estimatedItemSize;与同规模 FlatList 对照排除组件问题
整机负载高时全面变慢线程长时间 Runnable 而非 Running;GC 窗口与异常帧重合;RS Frame 单独异常降低瞬时对象分配和图片规格;控制整机负载与温度;RS/GPU 异常转系统图形链路分析

6.5 优化验证原则

      • 每个结论至少通过一种对照手段验证:关闭目标逻辑、负载降为 1/2、本地数据替换网络请求、占位图片替换真实图片、移除三方组件或切换 RNOH 版本等;

      • 若处理某个长切片后丢帧未同步下降,说明该切片不在当前丢帧的关键路径上,或瓶颈已转移,应重新比较异常窗口;

      • 上述任一现象单独出现均不足以认定 RNOH 框架问题,框架问题的判定标准见基础篇第 4 章「定界方法与常见错误」。

相关文档

      • RN性能调优之基础定界与 trace 分析介绍(基础篇:线程模型、trace 采集、标记含义)

      • RN 性能调优之点击响应和完成时延分析介绍

      • 性能调优

      • 页面转场与滑动场景优化(列表调优、组件复用、ArkUI驱动动画等优化手段)

----------------------------------------------------------------------------------------------------------

🔗 官网开发者学堂视频:https://developer.huawei.com/consumer/cn/training/result?type2List=201783644516849879&orderBy=1&courseType=5?ha_source=zxqy-IT&ha_sourceId=89000468

🔗 社区DFX专题文章: https://developer.huawei.com/consumer/cn/forum/subject/2101218731402391001?ha_source=zxqy-IT&ha_sourceId=89000468

【扫码加入 HarmonyOS DFX 技术交流群】



©本站发布的所有内容,包括但不限于文字、图片、音频、视频、图表、标志、标识、广告、商标、商号、域名、软件、程序等,除特别标明外,均来源于网络或用户投稿,版权归原作者或原出处所有。我们致力于保护原作者版权,若涉及版权问题,请及时联系我们进行处理。
分类
HarmonyOS
地址:北京市朝阳区北三环东路三元桥曙光西里甲1号第三置业A座1508室 电话:13391790444或(010)62178877
版权所有:电脑商情信息服务集团 北京赢邦策略咨询有限责任公司
声明:本媒体部分图片、文章来源于网络,版权归原作者所有,我司致力于保护作者版权,如有侵权,请与我司联系删除

京ICP备:2022009079号-2

京公网安备:11010502051901号

ICP证:京B2-20230255