一、图片加载与内存管理概述
说起移动端图片加载,我第一个想到的,就是几年前那个崩溃的电商首页。
用户滑几下就闪退,后台日志全是 OutOfMemoryError。那会儿我才明白——图片优化不只是调个压缩参数那么简单,它本质上是内存管理问题。你想想看,一张 1920x1080 的高清图,不解码就有 6MB 原始像素数据,要是列表里同时加载十几张…… 嗯,手机吃不消的。
1.1 为什么图片加载是性能瓶颈?
我归纳了三个核心原因:
- 内存占用巨大:一张 2K 屏的 24 位图片,像素数据约 12MB。滑动列表时视图回收不及时,显存瞬间爆炸。
- OOM 风险高:Android 每个 App 有堆内存上限(通常 64MB~256MB),iOS 虽然用虚拟内存 + ARC,但图片解码依然占用物理内存。我见过一个案例,轮播图用 WebP 但解码后没缓存,用户快速切换时内存峰值冲到 180MB,直接闪退。
- 解码与渲染耗时:位图解码是 CPU/GPU 密集型操作。早期项目用 ImageIO 同步解码,列表滚动掉帧严重。
1.2 内存管理基础:引用计数、ARC、GC
不同平台管理内存的方式不一样,但核心都是「谁引用谁负责释放」。我整理了一张对比表:
| 平台 | 机制 | 特点 | 图片常见问题 |
|---|---|---|---|
| iOS (ObjC/Swift) | ARC (自动引用计数) | 编译期插入 retain/release,运行时无暂停 | 循环引用导致图片视图无法释放;大图片未 weak 持有 |
| Android (Java/Kotlin) | GC (分代回收) | STW 暂停;低端设备回收延迟 | Bitmap 对象虽置 null 但 native 内存未及时释放;解码后软引用被回收 |
| Flutter (Dart) | GC + 异步 | Isolate 独立堆;图片缓存由 ImageCache 管理 | 大量 GIF 或超大图导致 isolate 堆暴涨 |
说白了,不管是引用计数还是垃圾回收,图片对象一旦被视图树引用,内存就收不回来。所以图片加载框架的核心职责,就是「主动管理引用,按需回收」。我个人习惯的做法:在 View 生命周期结束时,强引用置 null,再调用框架的释放缓存接口。
1.3 图片缓存策略:两级缓存
移动端最经典的方案是「内存缓存 + 磁盘缓存」。我早期参与的一个社交 App,直接裸用 SDWebImage 的默认策略,用户上传 4800 万像素的照片,缩略图列表就崩了。后来改成了两级控制:
- 内存缓存:LRU 策略,限制最大条目(如 50 张)或最大字节数(如 32MB)。使用弱引用队列作为二级后备。
- 磁盘缓存:按文件系统存储,过期时间 7 天。图片的原始数据和缩略图分开存放。
这里我建议一个关键细节:解码后的图片缓存进内存,未解码的元数据缓存进磁盘。为什么?因为磁盘 I/O 慢,内存缓存快的代价是占用宝贵 RAM。如果磁盘里存已解码的位图文件,下次读取其实没啥用,还得解压一次。更聪明的做法是存 WebP 或 JPEG 原始数据,读出来再按需解码。
1.4 常见内存优化手段
- 采样率/缩略图:根据 ImageView 尺寸,用 BitmapFactory.Options.inSampleSize 或 ImageIO 的 downscale 避免加载原始分辨率。
- 复用 Bitmap 池:RecyclerView 复用 item 时,把旧 ImageView 的 Bitmap 回收或放入缓存池。
- 图片格式选择:WebP 无损压缩比 PNG 小 26%,有损小 25%-34%;JPEG 的渐进式加载对用户体验友好。
- 使用 LRU 缓存 + 软引用二级缓存:Android 中组合 LruCache 和 WeakReference 作为背垫,避免图片反复解码。
- 避免大图直接进入列表:Glide 或 Picasso 的 override(width, height) 是好习惯。
- 监控内存使用:通过 Profiler 或 LeakCanary 定期检查。
嗯,说到监控,我记得有一次线上问题:用户反馈首页卡顿,我打开 Android Profiler 一看,堆内存里居然有 5 个 1920x1080 的 Bitmap,占用了 60MB。排查下来是 ViewPager 预加载时没有销毁旧页面的图片。后来加上了 onPageSelected 里主动清理不可见页面的缓存,内存瞬间掉到 25MB。
1.5 本章知识体系
下面这张图总结了本章的核心脉络,从内存管理基础,到缓存策略,再到实际优化手段与监控工具。你可以照着它构建自己的知识地图:
好了,本节的内容就是这些。记住一个核心观点:图片优化本质是内存管理,而内存管理的命脉在于引用与缓存策略。后面的章节我们会逐一深入每个细节,包括 Bitmap 复用池、WebP 编解码优化、自定义 LRU 实现等实战内容。