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 个整数,用 ArrayListInt32Array,直接就是连续内存,省多了。
| 数据结构 | 内存特点 | 推荐场景 |
|---|---|---|
| ArrayList / Array | 连续内存,有扩容冗余 | 随机访问多,数据量不大 |
| LinkedList | 每个节点额外存储前后指针 | 频繁插入删除,不推荐大量使用 |
| HashMap | 数组+链表/红黑树,有负载因子 | 键值对查找,注意初始容量 |
| Int32Array / Float64Array | 原生类型,无装箱开销 | 大量数值计算,首选 |
| ArrayBuffer | 直接操作二进制内存 | 网络数据、文件读写 |
我个人习惯是:能用原生数组就别用包装类,能用固定大小就别用动态扩容。你想想看,一个 HashMap 默认负载因子是 0.75,意味着你存 75 个元素,它实际占了 100 个槽位。如果提前知道数据量,直接指定初始容量,能省不少内存。
总结一下:图片内存优化是「降分辨率、选格式、及时回收」;对象池化是「复用代替创建,但别忘了重置」;数据结构选择是「用对类型,避免装箱和冗余」。这三板斧砍下去,内存至少能省 30%-50%。
好了,这一章就聊到这儿。下一章咱们会深入讲讲「内存泄漏的检测与修复」,那才是真正考验功力的地方。到时候我会分享一个我亲手修过的、差点让应用崩溃的泄漏案例,保证让你有收获。