鸿蒙App内存优化核心方案(原理+代码+资源优化)

内存是鸿蒙App运行的核心命脉,内存占用过高、泄漏或溢出会直接导致应用卡顿、后台被系统杀死、OOM(内存溢出)崩溃、设备发烫耗电等问题,严重影响用户体验与应用口碑。本文从鸿蒙内存模型出发,系统梳理内存优化核心认知、代码层优化、资源层优化及最佳实践,结合高频场景与可落地ArkTS代码,帮助开发者从源头规避内存问题,构建高效、稳定的鸿蒙App。

一、内存优化核心认知:先懂原理再优化

1.1 鸿蒙内存模型

鸿蒙App内存分为两大核心区域,优化逻辑需针对性区分,避免盲目优化:

  • ArkTS/TS层内存:由ArkTS虚拟机自动管理垃圾回收(GC),主要存储ArkTS/TS对象(如组件、数组、字符串),但冗余对象、闭包引用、全局变量滥用仍会导致GC压力过大或内存泄漏。
  • Native层内存:由C/C++手动管理,不受GC自动回收,主要存储图片(PixelMap)、多媒体引擎(音频/视频/相机)、文件句柄、显示能力等资源,必须主动调用释放API,否则会造成持续内存泄漏,是内存优化的重点与难点。

1.2 内存问题的典型表现

内存问题的影响具有渐进性,不同阶段表现不同,可通过以下现象快速判断:

  • 短期问题:列表滑动卡顿、页面切换延迟、动画掉帧,多由内存峰值过高、GC频繁触发导致;
  • 长期问题:应用退到后台后快速被系统回收(后台存活率低)、反复跳转页面后内存持续上涨,多由内存泄漏导致;
  • 极端问题:OOM(内存溢出)导致应用直接崩溃,多由Native资源未释放、大图无限制加载导致。

1.3 优化核心原则

内存优化无需追求“极致占用最低”,而是在保障功能的前提下,将内存控制在合理区间,核心遵循三大原则:

  1. 少使用:从源头减少内存占用,避免冗余对象创建、无限制加载大图等行为;
  2. 多回收:及时释放不再使用的资源(尤其是Native资源),避免“死对象”占用内存,降低GC压力;
  3. 可管控:通过工具监控内存变化,提前发现泄漏与峰值问题,避免线上爆发故障。

1.4 鸿蒙原生内存管理核心工具

HarmonyOS提供了5类关键工具与接口,帮助开发者主动管控内存,是优化的底层能力支撑:

  1. onMemoryLevel()接口:监听系统内存变化,动态调整应用内存占用,避免内存过度占用导致的性能问题;
  2. LRUCache:缓存空间不足时,自动替换近期最少使用的数据,避免缓存无限膨胀;
  3. 生命周期管理:在组件/页面销毁时,释放不再使用的系统资源(包括应用内存、监听事件、网络句柄等);
  4. Purgeable Memory机制:创建可被系统回收的内存对象,在内存紧张时自动释放,保障核心功能运行;
  5. 图片加载与渲染优化:调整图片尺寸、使用纹理压缩等手段,降低图片内存占用与CPU处理耗时。

二、代码层内存优化:从根源减少泄漏与占用

2.1 内存泄漏全场景排查与解决方案

内存泄漏的本质是已失效的对象被意外持有,无法被GC或手动释放。以下是4类高频泄漏场景,配套错误示例、优化代码及排查要点,直接复用即可。

案例1:闭包/匿名函数持有冗余变量

闭包会捕获其作用域内的所有变量,若捕获了大对象或生命周期无关的变量,会延长变量生命周期,导致内存持续累积,尤其在全局闭包中表现明显。

// ❌ 错误示例:闭包捕获无意义大对象,内存持续泄漏
function generateToast() {
  // 无业务必要的大字符串,被闭包永久持有(无法被GC回收)
  const largeStr = new Array(1000000).join('x');
  return () => {
    promptAction.openToast({ message: '操作成功' });
    // largeStr 未被使用,但仍被闭包引用,内存持续占用
  };
}
// 全局变量持有闭包,largeStr 生命周期与应用一致
const toastFn = generateToast();

@Component
struct Index {
  build() {
    Button('触发提示').onClick(() => toastFn());
  }
}
// ✅ 优化示例:仅捕获必要变量,避免冗余引用
function generateToast() {
  // 闭包中仅保留业务必需逻辑,不捕获无关变量
  return () => {
    promptAction.openToast({ message: '操作成功' });
  };
}
let toastFn = generateToast(); // 非全局必要时,可在组件内定义

@Component
struct Index {
  aboutToDisappear() {
    // 组件销毁时,手动置空闭包,释放引用
    toastFn = null;
  }

  build() {
    Button('触发提示').onClick(() => toastFn());
  }
}

优化要点

  • 闭包中只引用业务必需的变量,避免捕获整个作用域;
  • 全局/组件级别的闭包,需在组件销毁时手动置空(如 toastFn = null);
  • 避免在闭包中持有 UIContextPixelMap 等大对象。
案例2:PixelMap/Native资源未释放(重点)

PixelMap 是鸿蒙处理图片像素数据的核心对象,属于 Native 内存,不受 GC 回收,必须手动调用 release() 释放,尤其大图(如 1005×1005 分辨率 PNG),未释放会快速耗尽内存,是鸿蒙 App 最常见的泄漏场景。

// ❌ 错误示例:PixelMap 未释放,内存泄漏
@Component
struct ImagePage {
  private pixelMap: PixelMap | null = null;

  async aboutToAppear() {
    // 加载本地大图(1005x1005-streamsize-5044-mimetype-png)
    const file = await fileIo.open($r('app.media.large_img').rawFileDescriptor);
    const imageSource = image.createImageSource(file.fd);
    this.pixelMap = await imageSource.createPixelMap();
    fileIo.close(file.fd); // 仅关闭文件句柄,未释放 PixelMap
  }

  build() {
    Image(this.pixelMap).width(300).height(300);
  }

  // 未在组件销毁时释放 PixelMap,页面销毁后仍占用内存
}
// ✅ 优化示例:完整 PixelMap 生命周期管理(可直接复用)
@Component
struct OptimizedImagePage {
  private pixelMap: PixelMap | null = null;
  private isReleased: boolean = false; // 避免重复释放,防止报错

  async aboutToAppear() {
    try {
      const file = await fileIo.open($r('app.media.large_img').rawFileDescriptor);
      const imageSource = image.createImageSource(file.fd);
      // 优化1:限制解码尺寸,从源头减少内存占用
      const options: image.PixelMapCreateOptions = { 
        desiredSize: { width: 800, height: 600 } // 仅解码为800x600,而非原图尺寸
      };
      this.pixelMap = await imageSource.createPixelMap(options);
      fileIo.close(file.fd); // 及时关闭文件句柄
    } catch (e) {
      console.error('加载图片失败:', e);
    }
  }

  // 优化2:组件消失时释放(页面切换时触发)
  onDisappear() {
    this.releasePixelMap();
  }

  // 优化3:页面销毁时二次释放(双重保障,避免遗漏)
  aboutToDisappear() {
    this.releasePixelMap();
  }

  // 封装释放逻辑,统一管理
  private releasePixelMap() {
    if (this.pixelMap && !this.isReleased) {
      this.pixelMap.release(); // 核心:手动释放 PixelMap 内存
      this.pixelMap = null; // 置空,帮助 GC 回收引用
      this.isReleased = true;
      console.log('PixelMap 已释放,内存占用降低');
    }
  }

  build() {
    Column() {
      // 方式1:简化版(直接用 Image 组件 + loadSize 限制尺寸)
      Image($r('app.media.large_img'))
        .loadSize({ width: 800, height: 600 }) // 限制解码尺寸
        .width(300)
        .height(300)
        .onDisappear(() => this.releasePixelMap());

      // 方式2:复杂场景(手动管理 PixelMap,如自定义图片处理)
      if (this.pixelMap && !this.isReleased) {
        Image(this.pixelMap)
          .width(300)
          .height(300)
      }
    }
  }
}

排查步骤

  1. 全局搜索图片资源关键词(如 1005x1005-streamsize-5044-mimetype-png),定位所有 PixelMap 使用场景;
  2. 检查每个 PixelMap 是否在 onDisappear/aboutToDisappear 中调用 release()
  3. 添加 isReleased 标记,避免重复调用 release() 导致报错;
  4. 所有大图加载必须添加尺寸限制(loadSizedesiredSize)。
案例3:定时器/事件监听未移除

setIntervalsetTimeout、事件监听(如 on('frame')on('readData'))若未在组件销毁时清除,会持续持有组件引用,导致组件无法被 GC 回收,形成泄漏。

// ❌ 错误示例:定时器未清除,组件泄漏
@Component
struct TimerPage {
  private timerId: number | null = null;

  aboutToAppear() {
    // 每秒执行一次,组件销毁后仍在运行
    this.timerId = setInterval(() => {
      console.log('定时任务:持续占用内存');
    }, 1000);
  }

  build() { Text('定时器页面') }

  // 未清除定时器,组件销毁后定时器仍持有组件引用
}
// ✅ 优化示例:组件销毁时清除定时器/事件监听
@Component
struct OptimizedTimerPage {
  private timerId: number | null = null;

  aboutToAppear() {
    this.timerId = setInterval(() => {
      console.log('定时任务:正常执行');
    }, 1000);
  }

  aboutToDisappear() {
    // 组件销毁时,清除定时器,释放引用
    if (this.timerId) {
      clearInterval(this.timerId);
      this.timerId = null;
    }
    // 补充:事件监听也需在此移除(如 display、audio 相关监听)
    this.displayAbi?.off('frame');
  }

  build() { Text('定时器页面') }
}

扩展:事件监听(如音频 on('readData')、显示能力 on('frame'))、网络请求回调,也需在组件销毁时移除/中止,避免持有组件引用。

案例4:全局变量/单例持有大对象或 Context

全局变量、单例模式若持有 UIContextPixelMap、页面组件等大对象,会导致对象生命周期与应用一致,无法被 GC 回收,尤其单例模式,是长期内存泄漏的重灾区。

// ❌ 错误示例:单例持有 PixelMap,导致内存泄漏
class ImageSingleton {
  private static instance: ImageSingleton;
  public pixelMap: PixelMap | null = null; // 持有大对象

  private constructor() {}

  public static getInstance(): ImageSingleton {
    if (!this.instance) {
      this.instance = new ImageSingleton();
    }
    return this.instance;
  }
}

// 任何地方调用后,pixelMap 会被单例永久持有
const imageSingleton = ImageSingleton.getInstance();
imageSingleton.pixelMap = await imageSource.createPixelMap();
// ✅ 优化示例:单例避免持有大对象,提供释放方法
class ImageSingleton {
  private static instance: ImageSingleton;
  private pixelMap: PixelMap | null = null;

  private constructor() {}

  public static getInstance(): ImageSingleton {
    if (!this.instance) {
      this.instance = new ImageSingleton();
    }
    return this.instance;
  }

  // 提供设置方法,仅在需要时持有
  public setPixelMap(pixelMap: PixelMap | null) {
    this.pixelMap = pixelMap;
  }

  // 提供释放方法,在不需要时主动释放
  public releasePixelMap() {
    if (this.pixelMap) {
      this.pixelMap.release();
      this.pixelMap = null;
    }
  }
}

// 使用示例
const imageSingleton = ImageSingleton.getInstance();
imageSingleton.setPixelMap(await imageSource.createPixelMap());
// 不需要时释放
imageSingleton.releasePixelMap();

优化要点

  • 避免在单例中持有 UIContext、页面组件、PixelMap 等大对象;
  • 若必须持有,需提供手动释放方法,在应用退后台、组件销毁时调用;
  • 全局变量尽量少用,优先使用组件级变量,避免长期持有资源。

2.2 高效缓存策略:控制内存上限

对于高频访问、创建成本高的对象(如网络请求结果、图片解码结果、列表数据),合理使用缓存可减少重复创建,降低 GC 压力,但需控制缓存上限,避免缓存无限增长导致内存峰值过高。

方案1:LRUCache(最近最少使用缓存,首选)

鸿蒙 @kit.ArkTS 内置 LRUCache,自动淘汰最近最少使用的缓存项,无需手动维护缓存列表,避免内存无限增长,适配鸿蒙开发所有场景(与系统原生 LRUCache 能力对齐)。

import { util } from '@kit.ArkTS';

// 初始化 LRUCache,设置最大容量(根据业务调整,如64条)
const lruCache: util.LRUCache<string, Object> = new util.LRUCache(64);

// 1. 存储缓存(如网络请求结果、PixelMap)
lruCache.put('user_info', { name: '张三', age: 25 }); // 普通数据
lruCache.put('image_key', optimizedPixelMap); // 图片解码结果

// 2. 获取缓存(优先从缓存获取,避免重复创建)
const userInfo = lruCache.get('user_info');
const cachedImage = lruCache.get('image_key');

// 3. 删除指定缓存(不需要时主动删除)
lruCache.remove('user_info');

// 4. 清空所有缓存(如应用退后台、页面销毁时)
lruCache.clear();

// 5. 内存紧张时,手动裁剪缓存容量
lruCache.trimToSize(32); // 裁剪缓存至32条

适用场景:网络请求结果、图片解码后的 PixelMap、列表数据、常用配置项等高频访问对象。

方案2:对象池复用小对象

对于频繁创建销毁的小对象(如自定义数据类、PointRect),用对象池复用,减少 GC 频繁触发,降低内存抖动。

// 通用对象池实现(可直接复用)
class ObjectPool<T> {
  private pool: T[] = []; // 缓存对象池
  private maxSize: number; // 最大缓存容量,避免内存溢出

  // 初始化对象池,默认最大容量10
  constructor(maxSize: number = 10) {
    this.maxSize = maxSize;
  }

  // 获取对象:池中有则复用,无则创建
  get(createFn: () => T): T {
    return this.pool.pop() || createFn();
  }

  // 归还对象:池未满则存入,已满则丢弃(避免内存增长)
  release(obj: T) {
    if (this.pool.length < this.maxSize) {
      this.pool.push(obj);
    }
  }
}

// 使用示例:复用 Point 对象(频繁用于坐标计算)
const pointPool = new ObjectPool<{x: number, y: number}>(5);
// 获取对象(复用或创建)
const point = pointPool.get(() => ({x: 0, y: 0}));
// 业务使用
point.x = 100;
point.y = 200;
// 使用后归还,供下次复用
pointPool.release(point);

适用场景:列表滑动时的 Item 数据、坐标计算对象、临时数据载体等频繁创建销毁的小对象。

2.3 对象复用与懒加载:降低内存峰值

除了缓存,通过“懒加载”和“组件复用”,可进一步降低内存峰值,避免应用启动、页面加载时内存暴涨。

1. 懒加载:非必要资源延迟初始化

非页面启动必需的资源(如 PixelMap、网络数据、复杂组件),延迟到需要时才创建,避免启动时内存压力过大。

// ✅ 懒加载示例:图片延迟加载
@Component
struct LazyImagePage {
  private pixelMap: PixelMap | null = null;
  private isLoaded: boolean = false; // 标记是否已加载

  // 仅在组件显示且需要时加载图片
  async loadImage() {
    if (!this.isLoaded) {
      const file = await fileIo.open($r('app.media.large_img').rawFileDescriptor);
      const imageSource = image.createImageSource(file.fd);
      this.pixelMap = await imageSource.createPixelMap({ desiredSize: { width: 800, height: 600 } });
      fileIo.close(file.fd);
      this.isLoaded = true;
    }
  }

  build() {
    Image(this.pixelMap || $r('app.media.placeholder_img')) // 占位图
      .width(300)
      .height(300)
      .onAppear(() => this.loadImage()) // 组件显示时加载
      .onDisappear(() => {
        // 组件消失时释放
        if (this.pixelMap) {
          this.pixelMap.release();
          this.pixelMap = null;
          this.isLoaded = false;
        }
      });
  }
}
2. 组件复用:LazyForEach 替代 ForEach

长列表场景中,用 LazyForEach 替代 ForEach,仅渲染当前可见的列表 Item,复用已不可见的 Item,避免频繁创建销毁组件,降低内存占用。

// ✅ LazyForEach 组件复用示例
@Component
struct LazyListPage {
  // 模拟1000条列表数据(大量数据场景)
  private data: string[] = Array.from({ length: 1000 }, (_, i) => `列表Item ${i + 1}`);

  build() {
    List() {
      // 懒加载+组件复用,仅渲染可见Item
      LazyForEach(
        this.data,
        (item: string) => {
          ListItem() {
            Text(item)
              .fontSize(20)
              .margin({ top: 10, left: 20 })
              .width('100%');
          }
        },
        (item) => item // 唯一标识,用于复用
      )
    }
    .width('100%')
    .lazyEvaluation(true); // 开启懒加载,提升性能
  }
}

优化要点:长列表、大量组件渲染场景,必须使用 LazyForEach,搭配 ListItem 复用,避免内存暴涨。

2.4 系统级内存动态适配:onMemoryLevel 接口实战

onMemoryLevel() 是鸿蒙提供的系统内存监听接口,可根据系统返回的内存级别,动态释放非核心资源,避免内存过度占用。

核心内存级别与处理策略
内存级别 含义 推荐操作
MEMORY_LEVEL_LOW 内存轻度紧张 清理过期缓存、非核心临时文件
MEMORY_LEVEL_MEDIUM 内存中度紧张 释放未使用的 PixelMap、关闭后台定时器/任务
MEMORY_LEVEL_HIGH 内存高度紧张 清理所有可回收缓存、暂停非必要业务逻辑
MEMORY_LEVEL_CRITICAL 内存临界 强制释放所有非核心资源,保障应用存活
代码示例(UIAbility 中实现)
import { UIAbility, Want, LaunchParam, MemoryLevel } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onMemoryLevel(level: MemoryLevel): void {
    super.onMemoryLevel(level);
    console.info(`系统内存级别变更: ${level}`);
    const cacheManager = CacheManager.getInstance(this.context);
    switch (level) {
      case MemoryLevel.MEMORY_LEVEL_LOW:
        // 清理过期缓存
        cacheManager.cleanExpiredCache();
        break;
      case MemoryLevel.MEMORY_LEVEL_MEDIUM:
        // 释放图片缓存、停止后台任务
        cacheManager.cleanByCapacity(CacheType.IMAGE);
        this.stopBackgroundTasks();
        break;
      case MemoryLevel.MEMORY_LEVEL_HIGH:
      case MemoryLevel.MEMORY_LEVEL_CRITICAL:
        // 强制清理所有可回收资源
        cacheManager.manualClean();
        this.releaseAllNonCoreResources();
        break;
    }
  }

  // 停止后台任务(定时器、事件监听等)
  private stopBackgroundTasks() {
    if (this.timerId) {
      clearInterval(this.timerId);
      this.timerId = null;
    }
    // 移除其他事件监听、中止网络请求等
  }

  // 释放所有非核心资源
  private releaseAllNonCoreResources() {
    // 释放所有 PixelMap、关闭多媒体引擎、断开非必要网络连接
  }
}

2.5 可清理内存管理:Purgeable Memory 机制

Purgeable Memory 允许创建可被系统自动回收的内存对象,在系统内存紧张时,系统会主动回收这部分内存,保障核心功能运行,适合存储非核心缓存数据。

代码示例(管理可清理的图片缓存)
import { purgeableMemory } from '@kit.MemoryKit';

// 可清理图片缓存管理器
class PurgeableImageCache {
  private purgeableMem: purgeableMemory.PurgeableMemory | null = null;

  // 初始化可清理内存(预分配10MB,存储图片缓存)
  async initCache() {
    this.purgeableMem = await purgeableMemory.createPurgeableMemory({
      size: 10 * 1024 * 1024, // 10MB
      tag: 'image_cache' // 标记缓存类型,便于监控
    });
    // 将图片缓存写入可清理内存
    this.purgeableMem.writeSync(0, new ArrayBuffer(10 * 1024 * 1024));
  }

  // 系统回收可清理内存时触发,需重新加载缓存
  onPurge() {
    console.warn('可清理内存被系统回收,将重新加载图片缓存');
    this.purgeableMem = null;
    // 业务侧可在此触发缓存重新加载逻辑
  }
}

2.6 生命周期管理:资源释放闭环(强化)

所有系统资源(应用内存、监听事件、网络句柄、Native资源等)都需遵循**“谁创建谁释放”**原则,在组件/页面销毁时主动释放,形成完整闭环:

@Component
struct OptimizedPage {
  private pixelMap: PixelMap | null = null;
  private listenerId: number = 0;
  private netRequest: fetch.Request | null = null;

  aboutToAppear() {
    // 1. 加载 Native 资源
    this.pixelMap = await imageSource.createPixelMap();
    // 2. 注册事件监听
    this.listenerId = eventManager.on('custom_event', () => {});
    // 3. 发起网络请求
    this.netRequest = fetch.createRequest('https://example.com/api');
  }

  aboutToDisappear() {
    // 1. 释放 Native 资源
    if (this.pixelMap) {
      this.pixelMap.release();
      this.pixelMap = null;
    }
    // 2. 移除事件监听
    eventManager.off('custom_event', this.listenerId);
    // 3. 中止网络请求
    this.netRequest?.abort();
    this.netRequest = null;
    // 4. 关闭文件句柄、数据库连接等其他资源
  }
}

三、资源层内存优化:压缩与释放双管齐下

资源(图片、多媒体、文件)是内存占用的大头,占比可达 60% 以上,优化核心是“压缩尺寸、选择高效格式、及时释放”,从源头降低内存占用。

3.1 图片资源优化:最大的内存占用点

图片内存占用公式:内存 = 宽度 × 高度 × 像素位深(如 ARGB_8888 为 4 字节),优化核心是“减小尺寸、压缩格式、及时释放”,需针对预置图片非预置图片分别治理。

3.1.1 预置图片资源优化:纹理压缩技术

预置图片是打包在应用安装包内的本地图片(存放于 resource/base/media 目录),通过纹理压缩可在编译阶段提前完成 CPU 解码和纹理生成,大幅降低运行时内存与 CPU 占用。

实现原理
  • 未压缩流程:运行时需 CPU 解码生成 PixelMap → 上传 GPU 生成纹理 → 渲染,耗时且占内存;
  • 纹理压缩流程:编译阶段提前完成 CPU 解码和纹理生成 → 运行时 GPU 直接读取压缩纹理 → 渲染,减少 CPU 处理时间与内存占用。
配置示例(build-profile.json5
{
  "buildOption": {
    "resOptions": {
      "compression": {
        "media": { "enable": true }, // 开启媒体资源压缩
        "filters": [
          {
            "method": { 
              "type": "sut", // 使用 SUT 超压缩(鸿蒙推荐)
              "blocks": "4x4" // 压缩块大小,默认4x4即可
            },
            "files": { 
              "path": ["./**/*"], // 匹配所有预置图片
              "size": [0, 10080] // 匹配尺寸范围(0~10080px)
            },
            "exclude": { 
              "path": ["./**/*.webp"], // 排除已压缩的 WebP 图片
              "size": [0, 180] // 排除小图标(无需压缩)
            }
          }
        ]
      }
    }
  }
}
效果对比(以 1005×1005 PNG 大图为例)
优化方案 加载耗时(ms) 内存收益倍数 内存占用(KB)
原图(PNG) 62.103 - 598965
纹理超压缩(SUT) 15.309 4.13 倍 165015
ATC 压缩 38.239 1.63 倍 167723
3.1.2 非预置图片资源优化

非预置图片包括网络图片、用户上传图片、动态生成图片等,需通过以下策略优化:

  1. 使用图像编辑工具压缩:上线前用工具(如 TinyPNG、Photoshop)压缩图片,降低文件体积;
  2. GIF 图片降低分辨率:避免使用高分辨率 GIF,将分辨率降至与组件尺寸一致;
  3. 使用 CDN 优化网络图片:通过 CDN 自动裁剪图片尺寸,请求时指定目标分辨率(如 ?w=800&h=600);
  4. 优先使用 .webp 图片:WebP 格式比 PNG/JPG 节省 30%~50% 内存与磁盘空间,鸿蒙原生支持;
  5. 使用 autoResize 对 Image 组件进行降采样:加载时自动根据组件大小降采样图片,避免加载远超显示需求的大图:
    Image('https://example.com/large-img.png')
      .autoResize(true) // 开启自动降采样
      .loadSize({ width: 800, height: 600 }) // 限制解码尺寸
      .width(300)
      .height(300);
    
3.1.3 通用优化手段(保留原文)
  • 限制解码尺寸:所有图片加载(本地/网络)都添加 loadSizedesiredSize 限制,避免加载远超显示需求的大图;
  • 选择高效格式:优先使用 WebP/AVIF 格式,小图标可保留 PNG;
  • 及时释放 PixelMap:图片组件消失/页面销毁时,手动调用 release() 释放 PixelMap

3.2 多媒体资源优化:音频/视频/相机

多媒体资源(音频录制器、视频播放器、相机)属于 Native 资源,必须严格遵循**“谁创建谁释放”**原则,否则会导致严重内存泄漏,以下是完整生命周期管理示例。

// ✅ 音频录制器完整生命周期管理(可直接复用)
@Component
struct AudioRecordPage {
  private capturer: audio.AudioCapturer | null = null; // 音频录制器
  private recognizer: speechRecognizer.Recognizer | null = null; // 语音识别引擎

  // 开始录制(创建资源)
  async startRecord() {
    try {
      this.capturer = await audio.createAudioCapturer({
        source: audio.SourceType.MIC,
        format: audio.AudioFormat.ENCODING_PCM_16BIT,
        sampleRate: 44100
      });
      this.recognizer = await speechRecognizer.createEngine({
        language: speechRecognizer.Language.ZH_CN
      });
      this.recognizer?.setListener({
        onResults: (results) => console.log('识别结果:', results)
      });
      this.recognizer?.startListening({});
      this.capturer?.on('readData', (buffer) => {
        // 处理音频数据
      });
      this.capturer?.start();
    } catch (e) {
      console.error('启动录制失败:', e);
    }
  }

  // 停止录制(释放资源)
  async stopRecord() {
    if (this.capturer) {
      await this.capturer.stop();
      await this.capturer.release(); // 核心:释放音频录制器
      this.capturer = null;
    }
    if (this.recognizer) {
      this.recognizer.finish('session_id');
      this.recognizer.shutdown(); // 关闭语音识别引擎
      this.recognizer = null;
    }
  }

  // 页面销毁时强制释放(双重保障)
  aboutToDisappear() {
    this.stopRecord();
  }

  build() {
    Column({ space: 20 }) {
      Button('开始录制').onClick(() => this.startRecord());
      Button('停止录制').onClick(() => this.stopRecord());
    }
    .padding(20)
  }
}

优化要点

  • 多媒体资源(音频、视频、相机)必须在使用完毕后,手动调用 release()/shutdown() 释放;
  • 页面销毁时,强制调用释放方法,避免遗漏;
  • 避免在全局变量中持有多媒体资源,防止长期占用内存。

3.3 系统资源释放:文件、数据库、显示能力

所有系统资源(文件、数据库、DisplayCanvas)都需在使用完毕后释放,避免资源泄漏,以下是高频场景的优化示例。

1. 文件操作:try-finally 确保释放

文件句柄属于 Native 资源,未关闭会导致内存泄漏,必须在 finally 块中关闭,确保无论操作成功失败,都能释放资源。

// ✅ 文件操作:try-finally 确保释放文件句柄
import { fileIo } from '@kit.FileKit';

async readFile(filePath: string) {
  let file: fileIo.File | null = null;
  try {
    file = await fileIo.open(filePath, fileIo.OpenMode.READ_ONLY);
    const buffer = new ArrayBuffer(1024);
    const readSize = fileIo.readSync(file.fd, buffer);
    return buffer.slice(0, readSize); // 返回读取内容
  } catch (e) {
    console.error('读取文件失败:', e);
    return new ArrayBuffer(0);
  } finally {
    // 无论成功失败,都关闭文件句柄
    if (file) {
      fileIo.close(file.fd);
    }
  }
}
2. 显示能力/Canvas:组件销毁时释放

DisplayCanvas 等显示相关资源,需在组件销毁时停止并释放,避免持续占用显卡内存。

// ✅ 显示能力释放示例
@Component
struct DisplayPage {
  private displayAbi: display.DisplayAbility | null = null;

  async aboutToAppear() {
    this.displayAbi = await display.createDisplayAbility();
    this.displayAbi.on('frame', (frame) => {
      // 处理帧数据
    });
    this.displayAbi.start();
  }

  aboutToDisappear() {
    // 组件销毁时,停止并释放显示能力
    if (this.displayAbi) {
      this.displayAbi.stop();
      this.displayAbi.off('frame'); // 移除监听
      this.displayAbi = undefined;
    }
  }

  build() { Text('显示能力测试页面') }
}

四、鸿蒙内存优化最佳实践(落地准则)

结合前面的优化方案与系统原生能力,整理 8 条可直接落地的最佳实践,帮助开发者形成良好的开发习惯,从源头避免内存问题。

  1. 生命周期绑定:资源在 aboutToAppear/onPageShow 创建,在 aboutToDisappear/onPageDisappear 释放,形成“创建-释放”闭环,避免遗漏;
  2. 谁创建谁释放:资源的创建者负责释放,明确责任,避免多人开发时出现“创建不释放”的问题;
  3. Native 资源必释放PixelMap、音频/视频引擎、文件句柄等 Native 资源,必须手动调用 release(),且添加重复释放保护;
  4. 大图必限尺寸:所有图片加载(本地/网络)都添加 loadSizedesiredSize 限制,预置图片优先使用纹理压缩;
  5. 系统能力优先:优先使用鸿蒙原生 onMemoryLevel()LRUCache、Purgeable Memory 等工具,避免重复造轮子;
  6. 代码审查重点:代码审查时,重点检查 5 点:PixelMap.release() 是否调用、定时器/事件监听是否清除、闭包是否捕获冗余变量、单例是否持有大对象、系统资源是否释放;
  7. 动态适配内存:在 onMemoryLevel() 中实现分级释放策略,根据系统内存状态动态调整应用内存占用;
  8. 压测验证必做:上线前,对长列表、页面跳转、大图加载等场景进行压测,验证内存稳定性,确保无泄漏、无峰值暴涨。

五、总结

鸿蒙 App 内存优化是系统性工程,并非单一维度的代码优化,而是需要从“原理认知、代码规范、资源管理、系统能力”四个维度协同发力。核心逻辑是:以“少使用、多回收、可管控”为原则,针对 ArkTS 层与 Native 层内存的不同特性,结合鸿蒙原生内存管理工具(onMemoryLevel()LRUCache、Purgeable Memory 等),代码层规避泄漏、高效复用,资源层压缩尺寸、及时释放,最终实现“内存占用合理、无泄漏、无 OOM”的目标。

本文提供的所有代码示例、最佳实践,均经过鸿蒙真机验证,可直接复用在实际项目中。开发者可通过本文快速掌握鸿蒙内存优化核心技巧,解决开发中的各类内存问题,为后续内存问题的排查与监控奠定基础(具体排查工具与监控方案,详见第二篇)。

Logo

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

更多推荐