网罗开发 (小红书、快手、视频号同名)

  大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!


前言

最近在做一个鸿蒙应用,列表一长或者页面动效一多,就感觉有点卡,帧率掉得比较明显。后来查了下发现,不少问题都出在 UI 渲染和主线程的计算上:要么是整页整页地重绘,要么是在 UI 线程里做了不该做的重活。ArkUI(方舟 UI)本身是声明式框架,理论上已经帮我们做了不少优化,但用不对姿势,照样会把 CPU 拉满、帧率拉低。

今天就从实际开发角度聊聊,怎么利用 ArkUI 的特性做渲染优化,减少不必要的 CPU 计算,把帧率稳住。

为什么渲染性能会成瓶颈

界面要流畅,一般需要稳定在 60 帧甚至更高,也就是一帧大约 16 毫秒内要完成布局、绘制和提交。一旦主线程在这段时间里做了太多事——比如复杂计算、频繁创建组件、列表项过于复杂——就会挤占渲染时间,造成掉帧。

ArkUI 是声明式的:你描述「界面长什么样」,框架负责把变化算出来再渲染。但如果你描述得「粒度太粗」——比如一个很大的状态对象一变就整页刷新,或者列表不用懒加载、一次性把几百个组件都建出来——框架也很难帮你省掉这些开销。所以优化的一大方向就是:让「需要重算、重绘」的范围尽量小,让主线程少干重活。

控制 UI 的更新范围

ArkUI 会根据状态变化做差量更新,但前提是状态拆得合理。如果整页就绑在一个大对象上,这个对象里任意一个字段变了,都可能触发整棵组件树参与比较甚至重绘,CPU 就会在「算 diff」和「重画」上花很多时间。

尽量把状态拆细,让「变什么就更新什么」。比如列表里每一项有自己的数据,那就用 @ObservedV2 包在单项数据上,而不是把整份列表塞进一个大的响应式对象里。这样改某一项时,主要只会更新对应的那一项组件,而不是整列表。在实际项目里,我们遇到过「勾选列表某一项就卡一下」的情况,把列表数据改成按项可观察、勾选只改当前项状态之后,帧率就稳多了。

// 不推荐:整列表一个状态,改一项容易触发大范围更新
@State list: Array<Item> = [...]

// 推荐:单项可观察,更新局限在单条
@ObservedV2
class Item {
  id: string
  title: string
  checked: boolean = false
  // ...
}

再比如一个页面里既有「和用户操作强相关」的局部状态(比如弹窗显隐、输入框内容),又有「全局共享」的数据,可以分开:局部用 @State 放在对应组件,全局用状态管理。这样点击某个按钮只改局部状态时,不会把整页都带进更新逻辑里,渲染范围小,自然就更省 CPU。

列表用懒加载和复用

长列表是性能问题的重灾区。如果不用懒加载,成百上千条数据会一次性建出成百上千个组件,布局和测量都会在主线程堆在一起,首屏就会卡,滚动也更吃内存。

ArkUI 提供了 LazyForEach,配合 List 使用,只对可见区域(以及少量缓冲)创建和布局组件,滚动时再按需创建和复用。这样列表再长,实际参与渲染的也只是屏幕上的那一小部分,CPU 和内存都会稳定很多。我们之前有一个「消息记录」页,从普通 ForEach 改成 LazyForEach 之后,进入页面的时间从明显卡顿降到几乎无感,长列表滚动帧率也稳定在 60 左右。

LazyForEach(this.dataSource, (item: Item) => {
  ListItem() {
    Text(item.title).fontSize(14)
  }
}, (item: Item) => item.id)

注意最后一个参数是 key 生成函数,保证每条数据有稳定唯一 id,这样在数据源增删改时,框架才能正确复用节点,避免整列表抖动或重复创建。key 不稳定会导致列表项错乱或多余重建,反而拖慢性能。

主线程少做重计算

渲染是在主线程完成的,如果在主线程里做复杂运算、大数组遍历、复杂对象构造,就会直接拖慢每一帧的完成时间,表现就是滑动不跟手、点击反应慢。

能放到异步的尽量放到异步:比如网络请求、大列表的过滤排序、复杂数据的预处理,用 async/awaitTask 在后台做完,再把结果写回状态。主线程只做「拿到结果 → 更新状态 → 交给 ArkUI 去差量更新」,这样界面就不会因为计算卡住。我们有个页面要在列表里做关键词高亮和分段,一开始在 build 里现场算,列表一长就卡;后来改成在数据层算好再塞进模型,列表只负责展示,帧率马上上来。

对于确实要在 UI 里用到的轻量计算,可以尽量放到 @Computed 或类似「按需计算」的机制里,避免在每次 build 里重复算同一份东西,也能减少无效重绘。

图片与资源别拖后腿

图片尺寸过大、数量过多,解码和绘制都会占 CPU 和内存。尽量用合适分辨率的资源,必要时在服务端或本地做缩略图,列表里先展示小图,再按需加载大图。

ArkUI 的图片组件支持按宽高加载和裁剪,避免把一张巨图直接塞进一个小 Image 里,由系统去做缩放,这样能减轻渲染压力。动图、大图能懒加载就懒加载,不要一屏堆几十张一起解一起画。

布局别写太复杂

层级过深、嵌套过重,布局测量和摆放的计算量都会上去。能扁平就扁平,能用一个容器搞定的就别多套几层。约束尽量简单明确,减少「先测一遍再又测一遍」的二次布局。

对重复使用的布局结构,可以抽成 @Builder 或自定义组件,既减少重复代码,也方便框架做节点复用和比较。

结合实际场景的几点习惯

  • 列表页:数据源用 LazyForEach,单项用 @ObservedV2,key 稳定唯一;避免在单项里放特别重的子视图或复杂动效,否则即使懒加载,单条渲染成本一高,快速滑动时还是会掉帧。
  • 详情页:大块内容可以按模块拆状态,首屏先渲染,次要内容稍后或懒加载,避免一打开页面就一次性算完、画完所有模块。
  • 动效:优先用 ArkUI 提供的动画 API,由框架在渲染管线里做,比自己在定时器里改属性再触发布局要省 CPU,也更顺滑。
  • 调试:利用系统提供的性能分析工具看主线程耗时和渲染耗时,找到具体是「布局多」「绘制多」还是「逻辑计算多」,再针对性地收窄更新范围或挪走重计算,比盲目改要高效得多。

把这些习惯和 ArkUI 的声明式、差量更新、懒加载机制结合起来,就能在不大改业务的前提下,明显减轻 CPU 负担、提高帧率,让鸿蒙应用的渲染更稳、更顺。性能优化是个持续过程,先抓住「更新范围、列表懒加载、主线程别算重活」这几块,往往就能解决大部分卡顿问题。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐