4、内存优化实践:图片内存优化、对象复用与池化、数据结构选择

好,咱们直接进入正题。内存优化这件事,说白了就是跟系统「讨价还价」——你少占一点,系统就多给你留一点。我在鸿蒙项目里踩过不少坑,今天挑三个最核心的实践点,跟你好好聊聊。

4.1 图片内存优化:别让一张图吃掉整个堆

图片是内存消耗的大户。我见过一个应用,光图片就占了 200MB 内存,用户一滑动就卡顿。为什么会这样?因为很多人直接把原图丢进内存,根本没想过压缩。

核心原则:图片占用的内存 = 宽 × 高 × 每个像素的字节数。你想想看,一张 4000×3000 的图片,用 ARGB_8888 格式加载,那就是 4000×3000×4 ≈ 45MB。这还只是一张图。

我个人习惯的做法是:

  • 按需采样:用 BitmapFactory.Options 的 inSampleSize 参数,把图片缩放到实际显示尺寸。比如 ImageView 只有 200×200,那就没必要加载 4000×3000 的原图。
  • 格式选择:能不用 ARGB_8888 就别用。如果图片没有透明度,用 RGB_565 格式,每个像素只占 2 字节,内存直接砍半。
  • 及时回收:在鸿蒙里,Bitmap 用完记得调用 recycle()。虽然 GC 会处理,但主动回收能让内存压力立刻缓解。
// 示例:按需采样加载图片
let options = new image.SourceOptions();
options.inSampleSize = 4; // 缩小到 1/4
let pixelMap = await image.createPixelMap(source, options);

小技巧:我曾经在相册应用里遇到一个坑——用户连续快速滑动时,图片加载跟不上,内存暴涨。后来我加了一个「LRU 缓存 + 异步加载」的策略,只缓存当前屏幕可见区域的图片,滑动时预加载下一屏。效果立竿见影。

4.2 对象复用与池化:别让 GC 累死

频繁创建和销毁对象,是内存抖动的元凶。你想想看,一个列表滑动时,每帧都在创建几十个临时对象,GC 就得频繁触发,卡顿就这么来了。

对象池的思路很简单:用完了别扔,洗干净放回池子里,下次直接用。我在鸿蒙的列表组件里就用了这个模式。

// 对象池示例
class ViewHolderPool {
  private pool: ViewHolder[] = [];

  acquire(): ViewHolder {
    if (this.pool.length > 0) {
      return this.pool.pop();
    }
    return new ViewHolder();
  }

  release(holder: ViewHolder): void {
    holder.reset(); // 重置状态
    this.pool.push(holder);
  }
}

嗯,这里要注意:对象池不是万能的。如果对象本身很小,或者创建成本很低,那池化反而增加了复杂度。我一般只在以下场景用:

  • 频繁创建和销毁的对象(比如列表 Item)
  • 创建成本高的对象(比如数据库连接、网络请求封装)
  • 内存敏感的循环场景(比如动画每帧的临时对象)

避坑指南:我曾经在对象池里忘了做「重置」操作,结果复用时还带着上次的数据,导致界面显示错乱。所以,release 时一定要把对象状态清干净,不然就是给自己挖坑。

4.3 数据结构选择:选对了,内存省一半

很多人写代码时,随手就是一个 ArrayList 或者 HashMap。但你知道吗?不同的数据结构,内存开销天差地别。

我举个例子:如果你只需要存储 100 个整数,用 ArrayList 每个元素都要装箱成 Integer 对象,再加上 ArrayList 内部的数组扩容冗余,内存开销可能是原始数据的 3-5 倍。而用鸿蒙自带的 Int32Array,直接就是连续内存,省多了。

数据结构 内存特点 推荐场景
ArrayList / Array 连续内存,有扩容冗余 随机访问多,数据量不大
LinkedList 每个节点额外存储前后指针 频繁插入删除,不推荐大量使用
HashMap 数组+链表/红黑树,有负载因子 键值对查找,注意初始容量
Int32Array / Float64Array 原生类型,无装箱开销 大量数值计算,首选
ArrayBuffer 直接操作二进制内存 网络数据、文件读写

我个人习惯是:能用原生数组就别用包装类,能用固定大小就别用动态扩容。你想想看,一个 HashMap 默认负载因子是 0.75,意味着你存 75 个元素,它实际占了 100 个槽位。如果提前知道数据量,直接指定初始容量,能省不少内存。

总结一下:图片内存优化是「降分辨率、选格式、及时回收」;对象池化是「复用代替创建,但别忘了重置」;数据结构选择是「用对类型,避免装箱和冗余」。这三板斧砍下去,内存至少能省 30%-50%。

好了,这一章就聊到这儿。下一章咱们会深入讲讲「内存泄漏的检测与修复」,那才是真正考验功力的地方。到时候我会分享一个我亲手修过的、差点让应用崩溃的泄漏案例,保证让你有收获。