拓冰建站拓冰建站
首页 / 资讯中心 / 正文

OpenHarmony Flutter长列表性能优化:从卡顿到满帧

1. 同一套列表代码为什么在OpenHarmony上比Android更容易露怯去年年中我接到一个挺头疼的活儿把一套已经跑在 Android 上的 Flutter 应用迁到 OpenHarmony 设备上。App 里最核心的页面是一屏聊天消息记录用的是最常规的ListView.builder每条消息有头像、昵称、时间、文本内容偶尔带一张小图。这页面在 Android 中端机上滑起来能到 58~60 帧基本感知不到卡顿。结果换到 OpenHarmony 的开发板上同样的代码我往下一滑帧率直接掉到 30 帧上下伴随着明显的顿挫感甚至松手后还有一小段“惯性之后突然停住”的迟滞。那段时间我翻了不少资料发现很多人都在问“Flutter for OpenHarmony 上长列表为什么不流畅”“ListView.builder 是不是在鸿蒙上失效了”。其实不是失效而是ListView.builder 只在“减少 Widget 构建数量”这个层面做了优化它解决不了布局、绘制、图片解码、内存回收等一系列下游开销。而这些下游开销在 OpenHarmony 的 Flutter 适配版本上表现得比 Android 上更明显。这篇文章就是从那段时间的踩坑里整理出来的。我不打算堆概念只讲三件事搞清楚长列表为什么会卡、哪些优化手段真正有效、以及如何在 OpenHarmony 设备上量化和验证效果。适合正在做 Flutter 跨端适配、或者刚开始在 OpenHarmony 上做 Flutter 应用的开发者哪怕你对 OpenHarmony 还不熟按着步骤也能把列表优化到能用的程度。先说个反直觉的结论在 OpenHarmony 上ListView.builder本身带来的构建开销通常不是最大瓶颈真正的耗时大头往往出在你每个 ListItem 内部那些不起眼的属性上——边框圆角、多层文本、图片解码、甚至一个忘记加 const 的构造函数。这些单看都不严重但叠加到每秒滚动几十个 item 的场景里就会被放大。1.1 Flutter for OpenHarmony 的适配现状决定了我们不能照搬经验Flutter 官方一直没把 OpenHarmony 作为一等平台来支持目前能在 OpenHarmony 上跑起来的 Flutter基本来自开放原子开源基金会下面的 flutter_flutter、flutter_engine 等适配仓库。这意味着几个现实约束第一引擎版本通常会落后 Flutter 官方主干一个身位。你在官方博客里看到的新特性、新渲染优化不一定立刻能在 OpenHarmony 适配版里用上。我遇到过的例子是某个版本对 Impeller 的支持就不完整切到 Impeller 后部分列表出现异常色块最后还是回退到了 Skia 后端。第二平台通道和插件生态要区别对待。很多在 Android/iOS 上直接用的插件在 OpenHarmony 上需要找对应的 OpenHarmony 实现甚至自己写 Platform Channel。这个问题在长列表里会间接影响性能——比如图片加载插件如果用不了你退回用Image.network或自研加载压力全落在 Dart 侧和 CPU 侧滑动性能自然变差。第三设备硬件差异非常大。我手上那台测试板的 CPU 性能大概只有主流手机的一半不到GPU 更是薄弱。同样的列表在手机上“恰好压在 16ms 线附近”到开发板上就可能翻车。所以本文的优化思路多少是“吴下阿蒙”式的土办法但恰恰是这些土办法在弱设备上最管用。1.2 一次原始列表的帧耗时统计先别猜先量化拿到卡顿反馈后我第一件事不是找代码猜原因而是打开 Flutter 自带的性能调试统计了三组数据帧间耗时Frame time、构建耗时Build time、绘制渲染耗时Rasterizer time。在跑满 500 条消息的列表页滑动手势稳定滚动的过程中指标数值说明平均帧耗时32~40ms明显超出 16ms/帧的预算最坏帧耗时78ms出现在图片开始加载的瞬间Build 平均耗时6~8ms不算极端但也不低Rasterizer 平均耗时20~26ms这是真正的大头内存占用持续增长至 500MB图片缓存和 Widget 树膨胀一看这个分布就明白了卡顿的主要来源不在 Dart 的 build 阶段而在于光栅化Rasterizer耗时过高。换句话说屏幕上大量的圆角裁剪、阴影、半透明层叠逼着渲染引擎反复做复杂的像素合成。OpenHarmony 适配版引擎的合成性能偏弱这个问题被放大了。1.3 先分清你是哪种“卡”三种典型症状对应完全不同的优化方向长列表的“卡”从来不是一种病。我会先让团队把问题归类再决定用什么药症状A滑动时明显掉帧但静止后页面清晰稳定。这类问题大概率出在绘制与合成阶段比如 RepaintBoundary 缺失、大量 saveLayer、图片解码峰值。优化重点放在减少绘制复杂度上。症状B滚动速度不均匀划快了会“冲过头”停住时偶现白屏。这类多数是 lazy 加载的时机和 cacheExtent 配合不当或者数据解析阻塞了 UI isolate。重点在 compute/isolate 和滚动节流上。症状C长时间滑动后内存持续上涨最后触发 GC 卡顿。这类往往是 Widget 和图片缓存没有释放或者列表项内部持有了大对象。重点在缓存管理和 item 复用管控。三个症状经常同时出现但总有一个主导因素。我的建议是先在现有代码上做一轮最小化改造分别验证而不是一次性上一堆优化手段否则你根本不知道哪个起了作用、哪个反而拖慢了性能。2. ListView.builder 的真实开销分布不仅仅是 build() 的锅很多人一提到ListView.builder就说“它只会构建可视区内的 item所以性能没问题”。这句话方向上没错但它掩盖了三个更深层的开销来源。我当时也是对团队成员原样转述这句话结果被现实打脸——builder 确实只构建可视区附近的 item但每一个 item 从 widget 到像素中间要经历 build、layout、paint 三条流水线builder 只砍掉了“非可视区 widget 的构建”却没法帮你砍掉可视区内 item 的 layout 和 paint 耗时。2.1 视图复用机制和它解决不了的“下游问题”ListView.builder的核心是懒加载它维护一个可视窗口加上前后各一段cacheExtent默认是 250 逻辑像素作为预加载区。滑动时滚出预加载区的 item 会被销毁滚入预加载区的 item 会由 builder 重新创建 widget。所以严格说它是一个**“有限重建”机制够不上传统的 ViewHolder 复用**。Viewport cacheExtent 这个组合决定了屏幕外 item 不会无缘无故占用 Dart 内存但也决定了每一个新滚入的 item都必须完整走一遍“构建控件树 → 计算布局 → 生成绘制指令”。如果你的 item 内部有大量圆角裁剪、阴影、渐变或者文本结构很深光这一遍流程就能吃掉几毫秒到十几毫秒不等。在 OpenHarmony 适配版上这个通路还比 Android 侧更“吃”硬件性能因为底层渲染后端把绘制指令转成实际像素的合成阶段对 CPU/GPU 的占用更高。所以只靠ListView.builder的懒加载是远远不够的item 本身的“造价”必须尽量做得便宜。2.2 一个小实验把 item 内所有“视觉装饰”都拆掉性能立刻回升我做过一个非常粗暴的实验把 item 里的头像圆形裁剪、消息气泡圆角阴影、时间文本的样式层叠全部简化成纯色Container其他逻辑不动。结果显示配置Rasterizer 帧耗时掉帧情况原始 item圆角阴影头像裁剪20~26ms明显卡顿简化 item纯色无阴影8~11ms极少掉帧进一步简化连文本样式都扁平化6~8ms基本满帧这组数据说明真正压垮列表的是绘制复杂度而不是 Flutter 框架的 Widget 数量。当然生产环境不可能阉割 UI 来保性能但至少告诉我们优化方向——尽量用低成本的绘制方案实现同样的视觉效果才是长列表优化的本质。2.3 最容易忽略的三个隐性开销图片解码、文本测量与 saveLayer 链除了 build/layout/paint 三件套还有三个隐性开销特别容易在长列表里爆发第一个是图片解码峰值。Image.network默认会先从网络拿压缩格式再在 UI isolate 上解码成位图。如果图片尺寸虚大比如列表头像地址直接用了 1000x1000 的原图解码耗时可能达到几十到上百毫秒滑动时就会在图片即将进入可视区的那一刻产生一次瞬间掉帧。前面我测得 78ms 的最坏帧就是这里来的。第二个是文本测量。Flutter 里的 Text 在 layout 阶段要做文本编排文本越长、样式越丰富、最大行数约束越复杂测量耗时越高。消息列表这种场景每条消息长度不同、可能带链接、可能折行几乎每一条都在消耗排版算力。数据量大时这会均匀地推高平均帧耗时。第三个是saveLayer 链。Flutter 里只要出现“圆角裁剪 阴影 透明度叠加 复杂子层合成”的组合引擎就可能触发 saveLayer——即把一块内容先离屏渲染到临时缓冲再合并到父图层。这个操作代价非常高相当于让 GPU 额外开辟一块离屏缓冲并做完整合成。在 OpenHarmony 的弱 GPU 设备上一个列表页里十几个 item 同时触发 saveLayer会直接把 GPU 拖垮。3. 七个按收益排序的优化动作照着改就行这一章是全文的核心干货。我把踩过坑之后留存下来的优化动作按“性价比从高到低”排列你不需要全部做优先做前三项就能解决大部分问题。3.1 给列表一个固定高度特征itemExtent 的威力如果你的列表 item 高度固定或在一段范围内固定务必设置itemExtent。这是我在 OpenHarmony 上做长列表优化时性价比最高的一个改动没有之一。itemExtent的作用是直接告诉列表框架“每一项的滚动范围是固定值”于是框架在 layout 阶段就不需要对 item 做精确测量也不需要反向推算滚动偏移量对应的索引。它省掉的是每一个 item 的布局测量和滚动位置计算这块在消息列表这类规则结构里能省下非常可观的 CPU 开销。ListView.builder( itemExtent: 72.0, // 每条消息固定高度 itemBuilder: (context, index) { return MessageCell(message: messages[index]); }, )需要注意itemExtent不是万能的。如果你的消息高度不固定、文本可能从一行膨胀到十行硬设固定高度反而会导致内容溢出/截断。折中方案是给 item 一个“估算高度动态修正”比如先按两行文本预置高度在文本构建完成后再通过一个上报机制修正但那个复杂度就高了。我的建议是UI 允许的情况下优先把列表做成固定高度这里省下的性能远比做动态高度后花一堆技巧去优化更划算。顺带一提如果 item 高度统一还可以把itemExtent和prototypeItem同时考虑。OpenHarmony 的 Flutter 适配版本对prototypeItem的支持也正常它比itemExtent更适合“高度相同但无法给死数字”的场景二选一即可。3.2 让每个 item 的 Widget 尽可能“免重建”const 与不变子树Flutter 的 Widget 是不可变的配置对象理论上同一个位置如果前后两次 build 传入的 Widget 对象是同一个实例就可以跳过那部分 diff 和重建。这就是const关键字的价值。我最初看到消息 item 代码时发现大量TextStyle、EdgeInsets、BoxDecoration都是每次 build 时新建的复合对象完全没有常量化。改造成常量后不仅 build 耗时降低了内存里也少了一堆临时小对象GC 压力随之下降。class MessageCell extends StatelessWidget { const MessageCell({super.key, required this.message}); override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(12)), ), child: Text( message.content, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 16, color: Colors.black87), ), ); } }但我得提醒一下const不是银弹。它只对“完全由编译期常量构成”的 Widget 树生效。一旦某个 item 需要展示动态数据比如消息内容那这个 Text 的 child 就必然每次不同const就只能用在样式、装饰、间距这些与数据无关的部分。改造时不要为了追求全 const 而扭曲代码结构重点是把不变的部分提取成常量减少重复创建。更实际的做法是把 item 内部的“静态外壳”和“动态内容”分层。外壳组件用const内容组件通过参数传入。这样每次 item rebuild 时外壳的 diff 成本远低于动态内容的构建成本。3.3 RepaintBoundary把“重绘隔离”用在对的地方而不是越加越好RepaintBoundary的工作原理是在 widget 树里插入一个独立的图层边界引擎会优先把这个边界内的内容离屏渲染成纹理之后页面其他区域发生变化时这个边界内不会被迫重新绘制而是直接复用已有纹理。在长列表里给每个 item 套一层RepaintBoundary的典型收益是当某个 item 内部状态变化比如网络图片加载完成、点赞动画时它不会导致整个可视区内其他 item 一起重绘。ListView.builder( itemBuilder: (context, index) { return RepaintBoundary( child: MessageCell(message: messages[index]), ); }, )但这里有一个很容易踩的坑每个 item 都加RepaintBoundary并不总是好事。它等于把每个 item 都做成一张独立纹理合成时 GPU 要处理更多的纹理层叠。在 OpenHarmony 的弱 GPU 设备上图层数量过多会显著增加合成压力反而让滑动帧率下降。我的实际经验是只在内容重绘成本高、且会频繁变化的 item 上使用RepaintBoundary。比如图片消息、带状态的控件使用它隔离重绘纯文本的静态消息完全没必要套。另外一个技巧是只在 item 树中“成本最高的那一两支”上套而不是在 item 根部套一层管全部。比如把Text包一层防止气泡其它区域重绘时连带排文本。3.4 图片的像素级控制decodeWidth/decodeHeight 与缩略图策略图片是长列表里最容易被低估的性能杀手。一张列表页假如同时存在 6~8 张网络图如果不做任何处理开启滑动的一瞬间会同时发起解码任务那一帧的光栅化耗时能飙到 80ms 以上。优化方向有三个层层递进第一服务端做缩略图。如果服务端能直接下发 100x100 的带头像缩略图和 480x360 的聊天图片缩略图压根不要让客户端去拉原图这是最彻底的方案。第二客户端强制指定解码尺寸。Image.network支持cacheWidth和cacheHeight参数让底层在解码阶段就只解出目标尺寸的位图而不是解出全尺寸再缩小。我在消息头像上设置了 96x96聊天图片设置了 4:3 且最大宽度 480 的缩略尺寸之后Rasterizer 耗时直接下降一大截。Image.network( message.imageUrl, cacheWidth: message.isAvatar ? 96 : 480, cacheHeight: message.isAvatar ? 96 : null, fit: BoxFit.cover, )第三给图片加载加一个“可见再加载”的开关。图片只有在滚动停止、且 item 真正进入可视区时才开始解码。这个结合第 3.6 小节的滚动节流使用效果更佳。这里额外提醒一个 OpenHarmony 相关的点如果项目里用到了原生图片加载插件比如 image_picker、cached_network_image 在 OpenHarmony 上的适配版本务必确认它们是否真的生效。我有一次发现 cached_network_image 在 OpenHarmony 设备上访问网络缓存目录时路径异常导致每次重新解码最后只能自己写了个简单的磁盘缓存管理器。3.5 把非 UI 计算请出 UI isolatecompute 与 isolate 的正确用法Flutter 的 UI 线程既是 build/layout 的执行者也是平台通道和用户交互的主线程。任何超过 1ms 的同步计算比如千条消息里按关键词过滤、把 JSON 字符串解析成对象、给消息做时间分组、计算撤回消息的状态等都会直接卡掉 UI。正确的做法是把这些计算放进后台 isolate。最简单的是用compute函数ListMessage parseMessages(String jsonStr) { // 这里做 JSON 解析和消息模型映射 return MessageListParser().parse(jsonStr); } // 在 UI isolate 里这样调用 final messages await compute(parseMessages, jsonStr);但注意compute传参是按值拷贝的。如果你把一整个巨大的 List 传过去拷贝成本可能超过计算本身。我的经验是传最精简的数据结构让后台 isolate 只做纯计算再返回精简结果。比如只传 ListMap 的原始字段而不是把整个 Message 对象图传过去。另外长列表的最佳实践不只是把“单次解析”放后台而是把整个数据管道的管理都考虑成异步流。比如列表初次加载先让 UI 用空态/骨架屏展示再拉后台解析数据数据就绪后一次性更新列表。千万别在每一帧里同步去过滤某个列表。3.6 滑动过程中的加载调度让敏感操作学会“等一等”列表滑动时用户的眼睛对掉帧极其敏感但对“延迟 200ms 出现一张图”却感知很弱。所以一个非常有效的策略是滑动过程中暂停一切重量级操作滑动停止后再批量处理。我用了一个极简的监听器实现这个效果class SlidingLoadManager { Timer? _debounce; void init(ScrollController controller) { controller.addListener(() { _debounce?.cancel(); _debounce Timer(const Duration(milliseconds: 120), () { // 滑动停止开始加载可视区图片、触发分页预取 _onIdle(); }); }); } void _onIdle() { // 通知列表项开始加载图片/展开完整内容 } }同时在滑动期间可以主动降低图片解码优先级或者干脆不发起新请求。有一个细节要注意停止计时器不能太短小于 80ms 会导致还没真正停下就触发了也不能太长长于 300ms 会让人感觉内容加载迟缓。我实测 120~150ms 是个比较合适的区间。除此之外cacheExtent也可以适当调小。默认 250 逻辑像素在弱设备上意味着提前加载了很多看不见的 item。我在开发板环境中把cacheExtent调成 80~100明显减少了无谓的 item 构建ListView.builder( cacheExtent: 80, itemExtent: 72.0, itemBuilder: (context, index) MessageCell(message: messages[index]), )3.7 数据层配合分页、增量更新与本地缓存列表性能从来不只是前端的事。如果数据层一次性下发 10000 条消息再强的渲染也扛不住——就算 UI 不卡内存里躺着上万个模型对象也会让 GC 频繁触发。所以工程上必须做分页每次只加载 20~50 条滑动到接近底部时自动触发下一页。服务端尽量支持增量更新避免全量替换列表。对已经渲染过的消息模型做轻量化缓存比如只保留渲染所需字段不需要完整保留所有元数据。更进阶一点可以在数据层维护一个“列表条目是否已渲染”的标志。当 item 滑出很远再回来时如果数据没有变化直接复用之前构建好的 RenderObject 缓存。这听起来复杂但 Flutter 的PageStorage和AutomaticKeepAliveClientMixin能在一定程度上帮你做这件事只是后者用不好会造成内存泄漏还是得配合 item 的销毁机制一起调。4. OpenHarmony 设备上的专项适配引擎、多线程与资源路径前面的手段在 Android、iOS 上同样适用。但如果你要交付的是 OpenHarmony 应用真的有几点和别的平台不太一样需要单独调配。4.1 先搞清楚设备上的 Flutter 引擎是哪条分支Flutter for OpenHarmony 的适配版引擎与官方 Flutter 引擎相比在渲染流水线、平台通道实现上都有差异。我在项目初期吃过一个亏把 Android 上的 Flutter 版本和 OpenHarmony 适配版混用了结果部分平台通道直接静默失败列表数据加载不出来还以为是列表卡顿。所以第一件事锁定你的 OpenHarmony SDK 版本和对应 flutter_flutter/flutter_engine 适配仓库版本保证引擎分支匹配最好在 CI 配置里固定版本号不要用latest。在性能和稳定性出现矛盾时优先选择“官方/适配仓库明确支持的稳定版”不要盲目追 Flutter 新版本。4.2 使用 OpenHarmony 的异步能力taskpool 与多线程协作OpenHarmony 应用侧提供了 taskpool、worker 等多线程能力可以处理比 Dart isolate 更底层的计算和 IO。在实际项目中我发现 OpenHarmony 文件 IO 和网络请求的一些回调如果直接在 Flutter 的 UI isolate 里等待会有额外时延。更好的做法是网络请求走 OpenHarmony 侧原生能力通过自定义 Platform Channel在原生侧完成解析、缓存甚至缩略图裁剪最后把精简后的 JSON 结构交给 Dart。不过这里要小心Platform Channel 的通信是异步且有拷贝开销的。数据量大时不要整包传设计好接口粒度。比如一次只传一页 20 条消息的轻量字段而不是把整份数据库查询结果往 Dart 侧灌。4.3 资源加载路径的差异Asset 和文件缓存不能想当然OpenHarmony 的资源管理方式和 Android 不一样路径获取方式、应用沙箱目录、Asset 打包规则都各自独立。如果你在列表里用了大量本地资源强烈建议单独写一个资源加载抽象层屏蔽底层差异。我在项目中碰到的坑是OpenHarmony 的 Asset 资源加载速度比 Android 慢不少列表里直接引用大量小图标会出现闪白。解决方案是把列表 icon 从单个 Asset 文件改为内存常驻的图标字体或统一绘制到一张雪碧图上减少 IO 次数。另外图片缓存目录的位置在 OpenHarmony 上也有自己的规范使用path_provider的 OpenHarmony 适配版本时一定要测试缓存目录的可写性和空间配额。否则会出现“明明磁盘有空间写入却失败”的诡异情况。4.4 档位适配低端设备弱 GPU 场景下的绘制降级OpenHarmony 设备覆盖范围极广既有高性能平板也有 RK 系列开发板、低端电视盒子。对于长列表我建议做一个简单的设备档位分级档位判定标准列表策略高性能GPU 支持高刷且内存充足完整 UI图片原图正常动画中端能跑 60 帧但余量有限缩略图适当削减阴影减少 saveLayer低端帧率长期低于 40禁用模糊/阴影图片强制极低清晰度必要时关闭 item 动画这个分级不需要很精确运行时查一次设备型号或根据历史帧耗时动态判断即可。目的是在低端设备上宁可使用朴素一点的 UI也要保证基本滑动流畅度。5. 用数据验证优化结果帧耗时、内存与工具链优化做完了怎么判断到底有没有效果不能靠感觉必须靠数据。我把项目中用过的一套验证方法列出来照做即可。5.1 Flutter 自带的性能工具DevTools 里的 Timeline 与 Rasterizer 统计在跑 OpenHarmony 应用时可以通过 Flutter DevTools 连接调试中的 App。进入 Performance 面板后录制一段稳定滑动的过程重点看两个数据UI Thread 耗时对应 build/layout 以及 Dart 逻辑。Raster Thread 耗时对应 engine 的光栅化、纹理合成。如果 Raster 显著高于 UI说明问题集中在绘制侧回到第 3.3、3.4 节的方案优化如果 UI 侧高说明问题集中在 widget 构建和数据计算回到 3.2、3.5、3.6 节。另外不要只盯着平均帧耗时。长列表性能优化更应关注 P95 或最坏帧耗时。平均 16ms 但每隔几百毫秒跳一次 120ms 的体验依然很难受。5.2 鸿蒙侧工具hdc 命令与性能数据采集OpenHarmony 设备可以通过 hdc 命令连接。想从系统层面看性能可以这样# 连接设备 hdc list targets # 进入 shell 查看 CPU 占用需根据实际工具调整 hdc shell配合top、dumpsys等命令观察进程 CPU、内存占用。尤其要注意 GC 引起的周期性卡顿如果在 logcat/hilog 里看到频繁的 Dart GC 日志说明内存里的临时对象过多需要回头检查是不是有大量重复创建的 Widget 或图片位图没有释放。5.3 三组关键指标与验收标准我通常给团队定三组验收标准达到后才会认为列表优化完成指标低端设备目标中高端设备目标滑动平均帧耗时≤ 20ms≤ 12ms最坏帧耗时剔除首次加载≤ 60ms≤ 30ms内存增长持续滑动 5 分钟≤ 100MB≤ 150MBGC 触发频率持续滑动 5 分钟≤ 5 次≤ 3 次注意最坏帧耗时很难彻底消灭我们允许首次图片加载、首次页面进入时有一次明显的掉帧但正常滑动过程中尽量避免。把“偶发卡顿”控制在一定范围内比盲目追求“绝对 16ms”更现实。6. 一个聊天列表的优化全记录从 40ms 掉帧到稳定 60 帧最后分享一个真实 case就是开头说的那个聊天消息列表。我会给出一组完整的“优化前 → 逐步优化 → 验证结果”的记录方便你对照自己的项目。6.1 第一现场平均帧耗时 38ms松手后画面还需 200ms 才稳定当时的页面结构是一个ListView.builder每条消息包含头像圆角裁剪、昵称、时间、文本气泡圆角阴影、偶尔的图片缩略图。数据一次性加载 500 条没有分页。第一次测试数据如下指标优化前平均帧耗时38msUI 线程平均耗时7msRaster 线程平均耗时24ms最坏帧耗时96ms内存占用410MB 左右看到数据后我判断重点在 Raster 和内存于是按下面的顺序逐项优化。6.2 分步优化与每一步的数据变化第一步给列表加 itemExtent 给静态样式加 const。消息高度当时设计为固定 72px直接设置 itemExtent。这一步改动很小但效果明显指标优化后第一步平均帧耗时23msUI 线程平均耗时4msRaster 线程平均耗时18ms最坏帧耗时62ms第二步给图片消息设置解码尺寸并给头像和气泡分别包上 RepaintBoundary。这一步把图片解码峰值压了下来最坏帧从 62ms 降到 35ms指标优化后第二步平均帧耗时14msRaster 线程平均耗时12ms最坏帧耗时35ms内存占用330MB第三步消息 JSON 解析与时间分组计算移入 compute。这一步主要降低了 UI 线程的峰值占用偶尔的“卡一下”几乎消失指标优化后第三步平均帧耗时10msUI 线程平均耗时2ms最坏帧耗时28ms内存占用280MB第四步滚动节流 cacheExtent 调小搭配分页加载。最后把一次 500 条改成每页 40 条、触底加载下一页。这下内存不再持续膨胀长时间滑动后的 GC 卡顿也基本消失指标优化后第四步平均帧耗时8ms最坏帧耗时20ms内存占用160MB稳定后滑动 5 分钟 GC 频率2 次这个案例给我的启发是优化动作应该有节奏地一个个上并且每上一个就测一次数据。看似不起眼的itemExtent和const往往比花哨的缓存组件更管用而图片这种重型资源必须在前端限制解码尺寸否则再强缓存也无法挽救峰值卡顿。6.3 优化完成后还要做的“护城河”工作性能优化完成不算完项目还在迭代谁也不能保证后续加需求不会把列表重新拖垮。我会在代码里埋两个小工具一个是在列表滑动时周期性计算帧耗时如果连续几帧超过阈值就自动上报一条性能日志方便线上发现回归。另一个是给列表的“复杂装饰能力”做一个开关一旦设备档位判定较低可以手动或自动关闭头像圆角裁剪、阴影等视觉效果。这两个工具不复杂但能让你在下一次性能问题出现时不必从零开始排查。最后再分享一个小技巧优化长列表时永远先做“减法”再做“加法”。先把 item 里所有不必要的装饰、动态效果、对象创建减到最少跑通了再逐步把视觉效果加回来并在每一档上加完立刻测一次性能。这样做既能保住视觉保真又能卡住性能红线。我在多个 OpenHarmony 设备上试过这个流程比一次性套用所有优化方案要靠谱得多。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门