取件伙伴性能提升——长列表

取件伙伴性能提升------长列表

在移动应用开发中,List是最常见也是最容易出现性能瓶颈的场景之一。在 取件伙伴 项目中,取件列表页面需要展示可能多达数百条的包裹信息。如果不进行优化,随着数据量的增长,应用会出现滑动掉帧、内存占用过高甚至崩溃的问题,特别是最近我增加了在深色模式下的雪花效果,列表更是卡的不行!

本文将详细介绍我们如何利用 性能优化 "三剑客" ------ LazyForEach@ReusablecachedCount,将列表渲染性能提升至极致。


核心问题分析

在早期的开发中,如果直接使用 ForEach 渲染列表:

typescript 复制代码
// ❌ 性能较差的写法
List() {
  ForEach(this.packages, (item) => {
    PackageCard({ packageInfo: item })
  })
}

这种方式存在两个主要缺陷:

  1. 全量加载 :无论列表有多长,ForEach 都会一次性创建所有的数据对象和组件节点。如果有 1000 个包裹,就会瞬间创建 1000 个 PackageCard,导致内存激增。
  2. 频繁销毁与创建:当用户滑动列表时,移出屏幕的组件会被销毁,新进入屏幕的组件需要重新创建、布局和渲染。对于包含图片和复杂布局的卡片,这种开销是巨大的,直接导致滑动卡顿。

解决方案:性能优化 "三剑客"

1. LazyForEach:按需加载

LazyForEach 是专门为长列表设计的渲染控制语法。与 ForEach 不同,它只渲染屏幕可见区域的组件,并配合数据源(IDataSource)实现按需加载。

实现步骤:

首先,我们需要实现一个 IDataSource 接口的数据源类:

entry/src/main/ets/utils/BasicDataSource.ets

typescript 复制代码
// 通用数据源基类,实现了 IDataSource 接口
export class BasicDataSource<T> implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: T[] = [];

  // 获取数据的总条数
  public totalCount(): number {
    return this.originDataArray.length;
  }

  // 获取指定索引的数据
  public getData(index: number): T {
    return this.originDataArray[index];
  }

  // 注册/注销监听器(框架调用)
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener);
    }
  }
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      this.listeners.splice(pos, 1);
    }
  }

  // === 通知 LazyForEach 刷新 ===
  notifyDataReload(): void {
    this.listeners.forEach(listener => listener.onDataReloaded());
  }
  
  public setData(data: T[]) {
    this.originDataArray = data;
    this.notifyDataReload();
  }
}

2. @Reusable:组件复用

这是解决"滑动卡顿"的关键。通过 @Reusable 装饰器,我们可以让组件具备"复用"能力。当一个列表项滑出屏幕时,它的组件实例不会被销毁,而是被放入缓存池;当新数据滑入屏幕时,直接从缓存池取出实例并更新数据,跳过了昂贵的组件创建和布局计算过程

entry/src/main/ets/components/PackageCard.ets

typescript 复制代码
@Component
@Reusable // <--- 1. 标记为可复用组件
export struct PackageCard {
  @State packageInfo: PackageInfo | undefined = undefined;
  
  /**
   * 2. 复用生命周期回调
   * 当组件被复用时触发。在此处更新状态变量,驱动 UI 刷新。
   * 
   * @param params 上层传入的新参数
   */
  aboutToReuse(params: Record<string, Object>) {
    // 快速更新数据
    this.packageInfo = params.packageInfo as PackageInfo;
    
    // 更新其他状态
    if (params.compactModeEnabled !== undefined) {
      this.compactModeEnabled = params.compactModeEnabled as boolean;
    }
    // ...
  }

  build() {
    // 构建复杂的卡片布局...
    // 复用时,这里的节点结构保持不变,仅数据发生变化
  }
}

3. cachedCount:预加载

LazyForEach 默认只加载屏幕可见的项。为了让滑动更流畅,我们可以利用 cachedCount 属性,让列表在屏幕上下方预先加载几个项目。

entry/src/main/ets/pages/PackagesPage.ets

typescript 复制代码
List({ space: 12 }) {
  // 使用 LazyForEach + 自定义数据源
  LazyForEach(this.packagesDataSource, (packageInfo: PackageInfo, index: number) => {
    ListItem() {
      // 使用可复用组件
      PackageCard({
        packageInfo: packageInfo,
        // ...
      })
    }
  }, (item: PackageInfo) => `${item.id}_${item.updateTime}`) // 键值生成器
}
.width('100%')
.cachedCount(5) // <--- 设置缓存数量为 5
  • 原理cachedCount(5) 表示在屏幕视口之外,预先渲染并缓存 5 个列表项。
  • 收益:当用户快速滑动时,即将进入屏幕的卡片已经渲染好了,消除了白屏和闪烁,极大提升了跟手性。

优化效果对比

指标 优化前 (ForEach) 优化后 (LazyForEach + @Reusable) 提升原理
首屏加载时间 慢(加载所有数据) (仅加载首屏可见项) 按需渲染
内存占用 高(随数据量线性增长) 低且稳定(仅维持可见项+缓存项) 对象复用
滑动帧率 掉帧明显 满帧运行 (60/90/120Hz) 避免频繁创建销毁节点
CPU 占用 高(频繁 GC 和布局计算) 复用现有节点结构

总结

在开发复杂列表界面时,"LazyForEach + @Reusable + cachedCount" 是标准的高性能解决方案。

  1. LazyForEach 替代 ForEach,解决内存和首屏问题。
  2. @Reusable 改造子组件,解决滑动掉帧问题。
  3. cachedCount 调节预加载,进一步提升流畅度。

这套方案在 PickupPartner 项目中经受住了大量数据的考验,为用户提供了丝滑的操作体验。

相关推荐
冬奇Lab2 小时前
稳定性性能系列之二——ANR机制深度解析:从触发到上报
android·性能优化·debug
卓码软件测评3 小时前
第三方软件测试机构【Gatling源码的本地编译构建方法】
测试工具·性能优化·单元测试·测试用例
花开彼岸天~3 小时前
Flutter跨平台开发鸿蒙化日志测试组件使用指南
flutter·elasticsearch·harmonyos
妮妮分享3 小时前
Harmony NEXT 定位 SDK:开启鸿蒙原生应用精准定位新时代
华为·harmonyos
Hy行者勇哥4 小时前
JavaScript性能优化实战:从入门到精通
开发语言·javascript·性能优化
w139548564224 小时前
在鸿蒙平台使用 sqlite3.dart 插件
华为·sqlite·harmonyos
Kiyra4 小时前
八股篇(1):LocalThread、CAS和AQS
java·开发语言·spring boot·后端·中间件·性能优化·rocketmq
被风吹过的会不会要逝去5 小时前
Java后端开发性能优化排查思路及工具
java·性能优化
5 小时前
鸿蒙——布局——相对布局
华为·harmonyos·