前两篇把工作台的基础框架和交互逻辑搭起来了,页面能跑了,但一加载真实数据就露馅——列表一滑就卡,掉帧掉到 30 以下,滚动的时候还有白屏闪烁。这篇专门讲长列表性能优化,也是鸿蒙开发里最容易出问题的地方。

我们项目中间内容区有个列表,数据量几百条起步,每条 item 还有封面图、标题、摘要、标签,一开始没做任何优化,在折叠屏上滑起来一顿一顿的。后来花了一周时间调优,才做到 60fps 流畅滚动。

问题出在哪:先定位再优化

不要一上来就改代码。先用 DevEco Studio 的 Profiler 工具看一下,到底是渲染慢、布局慢还是图片加载慢。我们当时用 Performance 面板录了一段滚动过程,发现两个问题:

第一,每次滚动都在重新创建列表项组件,没有复用。第二,每张封面图都是滚动到可视区域才开始加载,首屏渲染慢。

找到问题就好办了。接下来一步步改。

用 List 组件的懒加载和复用

ArkUI 的 List 组件本身是有懒加载机制的,但要用对。一开始我用 ForEach 循环渲染所有 item,结果一上来就把几百个组件都创建了,内存占用很高。

正确的做法是用 LazyForgen 或者直接用 List 的 itemGenerator。HarmonyOS 7 的 List 组件支持自动复用不在可视区域的 item,不需要手动管理。

下面是优化后的列表代码,文件位置在 components/ContentList.ets

@Component
export struct ContentList {
  @Prop items: ContentItem[];
  onItemClick: (item: ContentItem) => void = () => {};

  build() {
    List({ space: 12 }) {
      ForEach(this.items, (item: ContentItem) => {
        ListItem() {
          ContentCard({ item: item })
        }
        // 关键:设置 key 值,帮助 List 复用组件
      }, (item: ContentItem) => item.id.toString())
    }
    .width('100%')
    .layoutWeight(1)
    .padding({ left: 16, right: 16, top: 12 })
    // 预加载区域:可视区域外 2 个 item 就开始加载
    .cachedCount(2)
  }
}

@Component
struct ContentCard {
  @ObjectLink item: ContentItem;

  build() {
    Row({ space: 12 }) {
      // 封面图:设置宽度高度,避免布局抖动
      Image(this.item.coverUrl)
        .width(80)
        .height(80)
        .borderRadius(8)
        // 图片加载失败占位图
        .alt($r('app.media.placeholder'))

      Column({ space: 6 }) {
        Text(this.item.title)
          .fontSize(15)
          .fontWeight(FontWeight.Medium)
          .maxLines(2)
          .textOverflow({ overflow: TextOverflow.Ellipsis })

        Text(this.item.summary)
          .fontSize(13)
          .fontColor('#666666')
          .maxLines(2)
          .textOverflow({ overflow: TextOverflow.Ellipsis })

        Row({ space: 8 }) {
          ForEach(this.item.tags, (tag: string) => {
            Text(tag)
              .fontSize(11)
              .fontColor('#007aff')
              .padding({ left: 6, right: 6, top: 2, bottom: 2 })
              .backgroundColor('#e8f0fe')
              .borderRadius(4)
          }, (tag: string) => tag)
        }
      }
      .layoutWeight(1)
      .alignItems(ItemAlign.Start)
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#ffffff')
    .borderRadius(12)
  }
}

这里有几个关键点:ForEach 第二个参数传了 item.id 作为 key,这样列表滚动的时候,组件会根据 key 复用,不会每次都创建新的。还有 cachedCount(2),意思是可视区域外预渲染 2 个 item,滚动的时候不会出现白屏。

长列表懒加载原理图

图片加载优化

列表卡顿的另一个元凶是图片。每张封面图都是大图,直接加载原始分辨率会很卡。HarmonyOS 7 的 Image 组件支持自动降采样,但要注意几个点:

1. 明确设置宽高:不要让图片组件自己计算大小,否则每次布局都会重新测量,导致滚动卡顿。上面代码里 Image 直接写了 .width(80).height(80),布局性能提升很明显。

2. 使用占位图:.alt() 设置加载中的占位图,避免图片加载完成前布局跳动。

3. 图片缓存:同一篇文章的封面图不要重复加载,系统自带的图片缓存会自动处理,但要注意不要用不同的 URL 请求同一张图。

局部刷新,不要全量重绘

一开始我在列表 item 里用了 @State 存选中态,结果点了一个 item,整个列表都重新渲染了——因为父组件的 selectedItem 变了,所有 item 的 @Prop 都跟着变。

后来改成了 @ObjectLink + 状态管理类,每个 item 自己管理选中态,只有当前 item 的 UI 会更新,其他 item 不受影响。

核心思路:把频繁变化的状态拆到最小粒度的组件里,不要让一个状态变化触发大范围重绘。这在 ArkUI 里很重要,因为每次重绘都是有开销的。

性能分析工具的使用

调优的时候不要靠感觉,要用工具。DevEco Studio 的 Profiler 有几个面板很有用:

Performance 面板:看每一帧的耗时,哪一帧掉帧了,红色的条就是超标了。点进去能看到这一帧花了多少时间在布局、渲染、脚本上。

Memory 面板:看滚动的时候内存有没有暴涨。如果越滑内存越高,说明有内存泄漏,大概率是组件没复用或者图片缓存有问题。

App Inspection:能实时看布局层级,有没有不必要的嵌套。

我们当时调优的过程就是:录一段滚动 → 看哪帧掉帧 → 定位是布局还是渲染 → 针对性改代码 → 再录一遍对比。前后大概改了三轮,FPS 从 35 提到了稳定 60。

性能优化对比图

几个容易踩的坑

1. 不要在 build 里做计算:比如过滤数据、格式化时间,不要写在 build 方法里,每次重绘都会执行。用 @Computed 或者提前算好。

2. 减少布局层级:嵌套三四层 Row/Column 是常事,但层级越多布局开销越大。能用一个容器解决的就不要拆成多个。

3. 避免在 ForEach 里创建对象:比如 ForEach 里 new 一个对象,每次刷新都会创建新的,容易触发不必要的重绘。

小结

长列表优化说到底就是三件事:组件复用、图片优化、局部刷新。听起来都是老生常谈,但真到了项目里,每个细节没做到位,滑起来就是会卡。下一篇讲多设备适配,手机、折叠屏、大屏上怎么一套代码都跑通。

Logo

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

更多推荐