HarmonyOS应用<节气通>开发第46篇:性能优化实战——让应用流畅如丝

引言

性能优化是应用从"能用"到"好用"的关键一步。在节气通这类内容型应用中,长列表滚动流畅度、图片加载速度、页面切换动画的顺滑程度、内存占用等直接影响用户体验。HarmonyOS ArkUI 提供了 LazyForEach 懒加载、CachedImage 缓存图片、@Reusable 组件复用等原生能力,配合合理的架构设计,可以打造出高性能的应用。

一个经过优化的应用应该具备以下特点:

  • 列表流畅:长列表(1000+项)滚动不掉帧(FPS≥55)
  • 启动快速:冷启动时间 < 2秒
  • 内存合理:长时间运行不出现内存持续增长
  • 响应及时:用户操作反馈 < 100ms
  • 省电省流:后台不消耗不必要的资源

本文为实战总结版本,涵盖 LazyForEach 列表优化、图片加载与缓存策略、内存管理、启动优化、以及性能监控工具使用。


学习目标

完成本文后,你将能够:

  • ✅ 使用 LazyForEach 实现高效的长列表渲染
  • ✅ 优化图片加载策略(缓存/压缩/懒加载)
  • ✅ 管理应用内存,避免泄漏
  • ✅ 优化应用启动速度
  • ✅ 使用 Profiler 工具定位性能瓶颈
  • ✅ 建立性能基线和回归防护

需求分析

性能优化维度

维度 优化目标 关键指标 核心手段
列表渲染 大数据量下保持流畅 FPS ≥ 55 LazyForEach + @Reusable
图片加载 快速显示且不卡顿 首图 < 500ms 多级缓存 + 占位图
内存管理 无泄漏、无膨胀 稳定 < 150MB 及时释放、避免循环引用
启动速度 快速进入可用状态 冷启 < 2s 异步加载、延迟初始化
包体积 下载安装快速 < 15MB 资源压缩、按需加载
电量消耗 后台低功耗 待机 < 1%/h 减少唤醒、批量任务

项目中的性能关键场景

场景 当前挑战 优化方向
节气列表页 24个节气卡片 + 详情展开 LazyForEach + 条件渲染
文章详情页 富文本 + 多张图片 图片懒加载 + 分段渲染
百科浏览页 分类筛选 + 搜索过滤 数据预处理 + 虚拟化
设置页 主题切换全局刷新 @StorageProp + 局部更新
启动流程 初始化服务 + 加载数据 并行化 + 延迟加载

核心实现

步骤1: LazyForEach ------ 高效列表渲染

LazyForEach 是 ArkUI 提供的懒加载列表组件,只渲染可视区域内的 item,大幅降低长列表的内存和渲染开销。

基础用法对比
typescript 复制代码
// ❌ ForEach:一次性创建所有子组件(数据量大时卡顿)
@State items: ItemData[] = []; // 假设有1000条数据

List() {
  ForEach(this.items, (item: ItemData) => {
    ListItem() {
      // 每个 item 都会被创建并加入渲染树
      Text(item.title)
    }
  })
}

// ✓ LazyForEach:按需创建,仅渲染可见区域
@State items: ItemData[] = [];
private dataSource: BasicDataSource = new BasicDataSource();

aboutToAppear(): void {
  this.dataSource.reloadData(this.items);
}

List() {
  LazyForEach(this.dataSource, (item: ItemData, index: number) => {
    ListItem() {
      Text(item.title)
    }
  }, (item: ItemData, index: number) => {
    // key生成器:确保每个item有唯一标识
    return `${item.id}_${index}`;
  })
}
DataSource 实现
typescript 复制代码
// models/ListDataSource.ts

/**
 * 基础数据源 - LazyForEach 必须继承自 IDataSource
 *
 * IDataSource 接口要求:
 * totalCount(): 返回总数据量
 * getData(index): 返回指定索引的数据
 * registerDataChangeListener / unregisterDataChangeListener: 监听器管理
 */
export class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private data: any[] = [];

  /** 获取总数 */
  totalCount(): number {
    return this.data.length;
  }

  /** 获取指定位置的数据 */
  getData(index: number): any {
    return this.data[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);
    }
  }

  /** 通知数据已重载 */
  notifyDataReload(): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataReloaded();
    });
  }

  /** 通知某条数据变化 */
  notifyDataAdd(index: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataAdd(index);
    });
  }

  /** 通知某条数据被删除 */
  notifyDataDelete(index: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataDelete(index);
    });
  }

  /** 通知数据变化(通用) */
  notifyDataChange(index: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataChange(index);
    });
  }

  /** 通知范围变化 */
  notifyDataChangeRange(from: number, to: number): void {
    this.listeners.forEach((listener: DataChangeListener) => {
      listener.onDataChangeRange(from, to);
    });
  }

  /** 重新加载数据 */
  reloadData(data: any[]): void {
    this.data = data;
    this.notifyDataReload();
  }

  /** 在末尾添加数据 */
  addData(data: any): void {
    this.data.push(data);
    this.notifyDataAdd(this.data.length - 1);
  }

  /** 在指定位置插入 */
  insertData(index: number, data: any): void {
    this.data.splice(index, 0, data);
    this.notifyDataAdd(index);
  }

  /** 删除指定位置数据 */
  deleteData(index: number): void {
    this.data.splice(index, 1);
    this.notifyDataDelete(index);
  }
}
节气列表完整示例
typescript 复制代码
// pages/SolarTermList.ets

import { BasicDataSource } from '../models/ListDataSource';
import { SolarTermData } from '../models/SolarTermModel';

@Component
struct SolarTermListItem {
  @Prop term: SolarTermData;
  @Prop isExpanded: boolean = false;

  build() {
    Column() {
      // 卡片头部(始终渲染)
      Row() {
        Column({ space: 4 }) {
          Text(this.term.name)
            .fontSize(17)
            .fontWeight(FontWeight.Bold)

          Text(`${this.term.solarDate} · ${this.term.lunarDate}`)
            .fontSize(12)
            .fontColor('#888888')
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)

        // 展开指示箭头
        Image($r('app.media.icon_arrow_down'))
          .width(16)
          .height(16)
          .rotate({ angle: this.isExpanded ? 90 : 0 })
          .animation({ duration: 200 })
      }
      .width('100%')
      .padding(16)

      // 展开详情(条件渲染:未展开时不创建DOM节点)
      if (this.isExpanded) {
        Divider().margin({ left: 16, right: 16 })

        Text(this.term.description)
          .fontSize(14)
          .fontColor('#555555')
          .textAlign(TextAlign.Justify)
          .lineHeight(20)
          .margin({ left: 16, right: 16, bottom: 16 })

        // 习俗标签
        Flex({ wrap: FlexWrap.Wrap }) {
          ForEach(this.term.customs, (custom: string) => {
            Text(custom)
              .fontSize(11)
              .fontColor('#4A9B6D')
              .backgroundColor('#EDF5EE')
              .borderRadius(12)
              .padding({ left: 10, right: 10, top: 4, bottom: 4 })
              .margin({ right: 8, top: 4 })
          })
        }
        .margin({ left: 16, right: 16, bottom: 16 })
      }
    }
    .width('100%')
    .backgroundColor('#FFFFFF')
    .borderRadius(12)
    .shadow({ radius: 4, color: '#00000008', offsetX: 0, offsetY: 2 })
  }
}

@Entry
@Component
struct SolarTermListPage {
  private dataSource: BasicDataSource = new BasicDataSource();
  @State expandedIndex: number = -1;  // 当前展开的索引

  aboutToAppear(): void {
    // 加载24个节气数据
    const terms = this.loadSolarTerms();
    this.dataSource.reloadData(terms);
  }

  build() {
    Column() {
      // 顶部标题栏
      Row() {
        Text('二十四节气')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .padding(16)
      .justifyContent(FlexAlign.Center)

      // 关键:LazyForEach 替代 ForEach
      List({ space: 12 }) {
        LazyForEach(this.dataSource, (term: SolarTermData, index: number) => {
          ListItem() {
            SolarTermListItem({
              term: term,
              isExpanded: this.expandedIndex === index
            })
            .onClick(() => {
              // 点击切换展开/收起
              animateTo({ duration: 200, curve: 'easeInOut' }, () => {
                this.expandedIndex =
                  this.expandedIndex === index ? -1 : index;
              });
            })
          }
        }, (term: SolarTermData, index: number) => {
          return term.name;  // 用名称作为唯一key
        })
      }
      .width('100%')
      .layoutWeight(1)
      .listDirection(Axis.Vertical)
      .edgeEffect(EdgeEffect.Spring)
      .scrollBar(BarState.Off)
      .padding({ left: 16, right: 16 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  private loadSolarTerms(): SolarTermData[] {
    // 从数据服务或本地资源加载
    return [
      { name: '立春', pinyin: 'Li Chun', solarDate: '2026-02-04',
        lunarDate: '正月十七', description: '立春,二十四节气之首。',
        customs: ['迎春', '咬春'] },
      { name: '雨水', pinyin: 'Yu Shui', solarDate: '2026-02-19',
        lunarDate: '正月初二', description: '雨水节气表示降雨开始。',
        customs: ['接寿', '拉保保'] },
      // ... 其余22个节气
    ];
  }
}

步骤2: 图片优化策略

图片是移动应用中最大的资源消耗者之一。合理的图片策略可以显著减少内存占用和网络流量。

多级缓存策略
typescript 复制代码
// utils/ImageCacheManager.ets

import image from '@ohos.image';
import fs from '@ohos.file.fs';

/** 图片缓存级别 */
enum CacheLevel {
  MEMORY = 'memory',     // 内存缓存(最快)
  DISK = 'disk'          // 磁盘缓存(次快)
}

/** 图片缓存管理器 */
class ImageCacheManager {
  private static instance: ImageCacheManager;
  private memoryCache: Map<string, image.PixelMap> = new Map();
  private readonly MAX_MEMORY_CACHE = 50;   // 最多缓存50张图片在内存中
  private diskCacheDir: string = '';       // 磁盘缓存目录

  static getInstance(): ImageCacheManager {
    if (!ImageCacheManager.instance) {
      ImageCacheManager.instance = new ImageCacheManager();
    }
    return ImageCacheManager.instance;
  }

  /**
   * 获取图片(三级查找)
   * 1. 内存缓存 → 2. 磁盘缓存 → 3. 网络加载
   */
  async getImage(url: string): Promise<image.PixelMap | null> {
    // Level 1: 内存缓存
    const memCached = this.memoryCache.get(url);
    if (memCached) {
      console.info(`[ImageCache] 命中内存缓存: ${url}`);
      return memCached;
    }

    // Level 2: 磁盘缓存
    const diskCached = await this.loadFromDisk(url);
    if (diskCached) {
      console.info(`[ImageCache] 命中磁盘缓存: ${url}`);
      this.putMemoryCache(url, diskCached);
      return diskCached;
    }

    // Level 3: 网络加载
    console.info(`[ImageCache] 网络加载: ${url}`);
    const loaded = await this.loadFromNetwork(url);
    if (loaded) {
      this.putMemoryCache(url, loaded);
      this.saveToDisk(url, loaded);
    }
    return loaded;
  }

  /** 放入内存缓存(LRU淘汰) */
  private putMemoryCache(key: string, pixelMap: image.PixelMap): void {
    // 超过容量时移除最早缓存的
    if (this.memoryCache.size >= this.MAX_MEMORY_CACHE) {
      const firstKey = this.memoryCache.keys().next().value;
      this.memoryCache.delete(firstKey);
    }
    this.memoryCache.set(key, pixelMap);
  }

  private async loadFromDisk(_url: string): Promise<image.PixelMap | null> {
    // TODO: 从文件系统读取缓存图片
    return null;
  }

  private async saveToDisk(_url: string, _pixelMap: image.PixelMap): Promise<void> {
    // TODO: 将图片保存到磁盘缓存目录
  }

  private async loadFromNetwork(_url: string): Promise<image.PixelMap | null> {
    // TODO: 通过HTTP请求下载图片并解码为PixelMap
    return null;
  }

  /** 清除所有缓存 */
  clearAll(): void {
    this.memoryCache.clear();
    // 同时清除磁盘缓存...
  }
}
图片组件封装(带占位、错误处理)
typescript 复制代码
// components/OptimizedImage.ets

@Component
export struct OptimizedImage {
  @Prop imageUrl: string = '';
  @Prop placeholderRes: Resource = $r('app.media.img_placeholder');
  @Prop errorRes: Resource = $r('app.media.img_error');
  @Prop borderRadius: number = 8;

  @State loadStatus: 'loading' | 'success' | 'error' = 'loading';

  build() {
    Stack({ alignContent: Alignment.Center }) {
      // 实际图片
      if (this.imageUrl && this.loadStatus !== 'error') {
        Image(this.imageUrl)
          .objectFit(ImageFit.Cover)
          .width('100%')
          .height('100%')
          .borderRadius(this.borderRadius)
          .onComplete(() => {
            this.loadStatus = 'success';
          })
          .onError(() => {
            this.loadStatus = 'error';
          })
          .visibility(this.loadStatus === 'success' ? Visibility.Visible : Visibility.Hidden)
      }

      // 占位图(加载中)
      if (this.loadStatus === 'loading') {
        Image(this.placeholderRes)
          .width('60%')
          .height('60%')
          .objectFit(ImageFit.Contain)
          .opacity(0.6)
      }

      // 错误图(加载失败)
      if (this.loadStatus === 'error') {
        Image(this.errorRes)
          .width('40%')
          .height('40%')
          .objectFit(ImageFit.Contain)
          .opacity(0.5)
      }
    }
    .width('100%')
    .height(180)
    .backgroundColor('#F8F8F8')
  }
}
图片优化要点总结
优化点 具体措施 效果
尺寸适配 服务端返回适合屏幕尺寸的图片 减少70%+传输量
格式选择 WebP > JPEG > PNG WebP比JPEG小25-35%
懒加载 进入视口才加载 首屏速度提升明显
内存缓存 最近查看的图片常驻内存 二次打开瞬间显示
磁盘缓存 已下载图片持久化存储 离线可用
渐进式加载 先模糊后清晰 用户感知更快
占位图 加载前显示统一占位 防止布局抖动

步骤3: 内存管理

常见内存问题与解决方案

问题1: 循环引用导致无法释放

typescript 复制代码
// ❌ 错误:闭包持有 this 引用
@Component
struct LeakExample {
  @State timerId: number = -1;

  aboutToAppear(): void {
    // setInterval 的回调捕获了 this
    this.timerId = setInterval(() => {
      console.info(this.toString());  // this 引用导致组件无法被GC
    }, 1000);
  }

  aboutToDisappear(): void {
    clearInterval(this.timerId);  // 即使清除定时器,闭包仍可能阻止回收
  }
}

// ✓ 正确:使用局部变量避免闭包引用
@Component
struct FixedExample {
  private timerId: number = -1;

  aboutToAppear(): void {
    let count = 0;  // 局部变量,不影响组件生命周期
    this.timerId = setInterval(() => {
      count++;
      console.info(`tick: ${count}`);
    }, 1000);
  }

  aboutToDisappear(): void {
    if (this.timerId !== -1) {
      clearInterval(this.timerId);
      this.timerId = -1;
    }
  }
}

问题2: 大对象未及时释放

typescript 复制代码
// ❌ 问题:大数组长期持有
@State allArticles: Article[] = [];  // 可能包含数百篇文章全文

// ✓ 方案:分页加载 + 按需获取详情
@State articleIds: string[] = [];       // 只存ID列表(轻量)
@State currentArticle: Article | null = null;  // 只存当前查看的一篇

async loadArticleDetail(id: string): Promise<void> {
  this.currentArticle = await apiService.getArticle(id);
}

// 页面销毁时释放
aboutToDisappear(): void {
  this.currentArticle = null;
}

问题3: PixelMap 未释放

typescript 复制代码
// ❌ 问题:PixelMap 是 native 对象,需要手动释放
let pixelMap: image.PixelMap | null = null;
pixelMap = await image.createPixelMap(source);

// ✓ 正确:使用完毕后释放
try {
  pixelMap = await image.createPixelMap(source);
  // 使用 pixelMap ...
} finally {
  if (pixelMap) {
    pixelMap.release();  // 释放native内存!
    pixelMap = null;
  }
}
内存监控工具类
typescript 复制代码
// utils/MemoryMonitor.ets

import process from '@ohos.process';
import hilog from '@ohos.hilog';

class MemoryMonitor {
  private static instance: MemoryMonitor;
  private lastLogTime: number = 0;
  private readonly LOG_INTERVAL_MS = 5000;  // 至少间隔5秒打印一次

  static getInstance(): MemoryMonitor {
    if (!MemoryMonitor.instance) {
      MemoryMonitor.instance = new MemoryMonitor();
    }
    return MemoryMonitor.instance;
  }

  /** 记录当前内存使用情况 */
  logMemoryUsage(tag: string = ''): void {
    const now = Date.now();
    if (now - this.lastLogTime < this.LOG_INTERVAL_MS) return;
    this.lastLogTime = now;

    const info = process.getInfo();
    const mb = (bytes: number) => (bytes / 1024 / 1024).toFixed(1);

    hilog.info(
      0x0000, 'MemoryMonitor',
      `[${tag}] RSS=${mb(info.rss)}MB | ` +
      `VSize=${mb(info.vsize)}MB`
    );
  }

  /** 检查是否接近内存警戒线 */
  isMemoryHigh(): boolean {
    const info = process.getInfo();
    const rssMB = info.rss / 1024 / 1024;
    // 手机应用通常超过200MB就需要关注
    return rssMB > 200;
  }
}

步骤4: 启动速度优化

冷启动流程与优化点
复制代码
用户点击图标
    │
    ▼
┌─────────────────────────────────┐
│ 1. 系统加载进程 & Ability       │ ← 无法优化(系统行为)
└──────────────┬──────────────────┘
               │
               ▼
┌─────────────────────────────────┐
│ 2. aboutToAppear 执行           │ ← 优化重点区域
│    ├─ 初始化 Service 单例        │ → 延迟到首次使用时初始化
│    ├─ 读取 Preferences 配置     │ → 并行读取多个key
│    ├─ 加载首屏数据               │ → 只加载必要字段
│    └─ 创建 UI 组件树             │ → 减少首屏组件数量
└──────────────┬──────────────────┘
               │
               ▼
┌─────────────────────────────────┐
│ 3. 首帧渲染完成                  │ ← 用户看到的第一帧
└─────────────────────────────────┘
优化策略代码示例
typescript 复制代码
// pages/IndexPage.ets --- 启动优化版

@Entry
@Component
struct IndexPage {
  @State welcomeText: string = '';
  @State isLoading: boolean = true;
  @State bannerData: BannerItem[] = [];

  aboutToAppear(): void {
    // ====== 优化1: 并行执行独立任务 ======
    const tasks = [
      this.loadWelcomeText(),       // 任务1: 加载欢迎语
      this.loadBannerData(),        // 任务2: 加载轮播数据
      this.initServicesLazy()       // 任务3: 延迟初始化非必要服务
    ];

    // 不需要 await 全部完成,各自独立更新UI
    // Promise.allSettled(tasks);

    // ====== 优化2: 先显示骨架屏,数据到了再替换 ======
    // isLoading = true 时显示骨架屏
  }

  /** 延迟初始化非关键服务 */
  private async initServicesLazy(): Promise<void> {
    // 延迟 2 秒后再初始化统计服务等非关键服务
    setTimeout(async () => {
      // await AnalyticsService.getInstance().init();
      // await PushService.getInstance().init();
      console.info('[Startup] 延迟服务初始化完成');
    }, 2000);
  }

  private async loadWelcomeText(): Promise<void> {
    // 从缓存或默认值立即返回
    this.welcomeText = '欢迎使用节气通';  // 默认值,瞬间显示
    // 后台异步更新真实值
    // const realText = await I18nService.getString('welcome');
    // if (realText) this.welcomeText = realText;
  }

  private async loadBannerData(): Promise<void> {
    try {
      // this.bannerData = await ApiService.getBanners();
      this.isLoading = false;
    } catch (e) {
      this.isLoading = false;
    }
  }

  build() {
    Stack({ alignContent: Alignment.Top }) {
      if (this.isLoading) {
        // 骨架屏:让用户感知"正在加载"
        this.buildSkeleton()
      } else {
        Scroll() {
          Column({ space: 16 }) {
            // 欢迎语
            Text(this.welcomeText)
              .fontSize(18)
              .fontWeight(FontWeight.Medium)

            // 轮播区
            Swiper() {
              ForEach(this.bannerData, (item: BannerItem) => {
                // ...
              })
            }
            .autoPlay(true)
            .interval(3000)

            // 其他内容区块...
          }
          .padding(16)
        }
        .scrollBar(BarState.Off)
      }
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  buildSkeleton(): void {
    Column({ space: 14 }) {
      // 标题骨架
      Row().width('60%').height(20).backgroundColor('#F0F0F0').borderRadius(4)
      // 轮播骨架
      Row().width('100%').height(160).backgroundColor('#F0F0F0').borderRadius(8)
      // 内容块骨架
      ForEach([1, 2, 3], (_: number) => {
        Column({ space: 8 }) {
          Row().width('80%').height(14).backgroundColor('#F0F0F0').borderRadius(4)
          Row().width('100%').height(14).backgroundColor('#F0F0F0').borderRadius(4)
          Row().width('90%').height(14).backgroundColor('#F0F0F0').borderRadius(4)
        }
      })
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
  }
}

步骤5: 性能监控与工具

HiLog 性能埋点
typescript 复制代码
// utils/PerfTracker.ets

import hilog from '@ohos.hilog';

class PerfTracker {
  private static instance: PerfTracker;
  private markers: Map<string, number> = new Map();

  static getInstance(): PerfTracker {
    if (!PerfTracker.instance) {
      PerfTracker.instance = new PerfTracker();
    }
    return PerfTracker.instance;
  }

  /** 开始计时 */
  startMark(name: string): void {
    this.markers.set(name, Date.now());
    hilog.info(0x0000, 'PerfTracker', `[START] ${name}`);
  }

  /** 结束计时并打印耗时 */
  endMark(name: string): void {
    const start = this.markers.get(name);
    if (start) {
      const cost = Date.now() - start;
      hilog.info(0x0000, 'PerfTracker',
        `[END] ${name} 耗时: ${cost}ms`);

      // 超过阈值告警
      if (cost > 3000) {
        hilog.warn(0x0000, 'PerfTracker',
          `[WARN] ${name} 耗时过长: ${cost}ms (>3000ms阈值)`);
      }

      this.markers.delete(name);
    }
  }

  /** 包装异步函数自动计时 */
  async trackAsync<T>(name: string, fn: () => Promise<T>): Promise<T> {
    this.startMark(name);
    try {
      return await fn();
    } finally {
      this.endMark(name);
    }
  }
}

// 使用方式
const perf = PerfTracker.getInstance();

perf.startMark('loadSolarTerms');
const data = await loadData();
perf.endMark('loadSolarTerms');

// 或用 trackAsync 自动包裹
const result = await perf.trackAsync('fetchArticles', () =>
  ApiService.fetchArticles()
);

架构总览

复制代码
┌──────────────────────────────────────────────┐
│                 性能优化体系                    │
│                                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │ 渲染优化  │  │ 内存优化  │  │ 加载优化  │  │
│  │          │  │          │  │          │  │
│  │ LazyFor  │  │ PixelMap │  │ 图片缓存  │  │
│  │ Each     │  │ release  │  │ 分页加载  │  │
│  │ @Reusable│  │ 循环引用  │  │ 延迟初始化 │  │
│  │ 条件渲染  │  │ 大对象释放 │  │ 骨架屏   │  │
│  └──────────┘  └──────────┘  └──────────┘  │
│                                              │
│  ┌──────────────────────────────────────┐   │
│  │         PerfTracker 监控层             │   │
│  │  startMark → endMark → 日志输出        │   │
│  └──────────────────────────────────────┘   │
│                                              │
├──────────────────────────────────────────────┤
│              DevEco Profiler                  │
│  CPU / 内存 / GPU / 网络 / 能耗 可视化分析     │
└──────────────────────────────────────────────┘

优化优先级矩阵

复制代码
         影响大                          影响小
           ↑                               ↑
    ┌──────────────┐              ┌──────────────┐
高  │ LazyForEach  │              │ 代码精简     │
实  │ 图片多级缓存 │              │ 日志优化     │
施  │ 启动延迟加载 │              │              │
难  │              │              │              │
度  ├──────────────┤              ├──────────────┤
    │ PixelMap释放 │              │ 字符串优化   │
低  │ 内存监控     │              │ 条件编译     │
    │ 骨架屏       │              │              │
    └──────────────┘              └──────────────┘
    先做左上角(影响大+实施易)的项目

关键注意事项

1. LazyForEach 的 key 必须稳定唯一

typescript 复制代码
// ❌ 错误:用 index 做 key,增删数据时会导致复用错乱
(term, index) => `${index}`

// ✓ 正确:用数据的唯一标识做 key
(term, index) => `${term.id}`

// ✓ 如果没有 id,用稳定的业务字段组合
(term, index) => `${term.name}_${term.solarDate}`

2. @State 变更触发全量重建要谨慎

@State 装饰的变量变更会导致整个组件重新执行 build()。对于频繁更新的变量:

typescript 复制代码
// ❌ 频繁更新的计数器放在父组件
@State second: number = 0;  // 每秒更新 → 整个页面 rebuild

// ✓ 将频繁更新的部分拆成子组件,用 @Prop 或 @Link 传递
// 子组件内部的变化不会触发父组件 rebuild

3. 图片不要全部用原图尺寸

手机屏幕宽度约 360-414dp(逻辑像素),图片实际需要:

  • 缩略图:~360px 宽即可
  • 详情大图:~750px 宽足够(2x Retina)
  • 原图(2000px+)纯属浪费带宽和内存

4. aboutToDisappear 中必须清理资源

typescript 复制代码
aboutToDisappear(): void {
  // 清理清单:
  // 1. 定时器 (clearInterval / clearTimeout)
  // 2. 事件订阅 (off / removeEventListener)
  // 3. PixelMap (.release())
  // 4. 动画控制器 (.stop())
  // 5. 大数组 / 大对象 (置 null)
}

5. 避免在 build() 中进行计算

build() 方法会在每次状态变更时调用,应保持轻量:

typescript 复制代码
// ❌ 在 build 中做复杂计算
build() {
  Text(this.computeHeavyResult())  // 每次 rebuild 都重算
}

// ✓ 在数据变更时预计算好
@State displayValue: string = '';

someMethod(): void {
  this.displayValue = this.computeHeavyResult();  // 只算一次
}

build() {
  Text(this.displayValue)  // 直接读取
}

最佳实践清单

在进行性能优化时,请逐项检查:

  • 长列表使用 LazyForEach + 自定义 DataSource
  • LazyForEach 的 key 生成函数使用稳定唯一的标识符
  • 不在可视区域的内容使用条件渲染 (if) 延迟创建
  • 图片使用 OptimizedImage 组件(带占位/错误态/缓存)
  • 图片尺寸适配屏幕(不超过 2x 设备分辨率)
  • PixelMap 使用后在 finally 块中 release()
  • 定时器和事件监听在 aboutToDisappear 中清理
  • 频繁更新的状态变量放在最小范围的组件内
  • build() 方法中不含复杂计算或耗时操作
  • 非关键服务和数据延迟到首屏渲染后加载
  • 首屏使用骨架屏替代空白等待
  • PerfTracker 埋点覆盖关键路径(启动/加载/切换)

相关链接