2. 图片内存模型:Bitmap 在内存中的占用计算方式
聊图片加载,第一个绕不开的问题就是:一张图片到底占多少内存?
我见过不少开发同学,图片加载 OOM 了,第一反应是「图片太大了,压缩一下」。但压缩的是文件体积,不是内存占用。这两者完全是两码事。文件体积是磁盘上的事,内存占用是解码后的事。
说白了,一张 1920×1080 的 JPEG,文件可能只有 200KB。但一旦解码成 Bitmap,它占的内存是固定的——跟文件大小没关系。
2.1 核心公式:宽 × 高 × 每像素字节数
Bitmap 的内存占用,就这一个公式:
内存占用 = 图片宽度(px) × 图片高度(px) × 每像素字节数(BPP)
举个例子:
- 一张 1080×1920 的图片
- 使用 ARGB_8888 格式(每像素 4 字节)
- 内存 = 1080 × 1920 × 4 = 8,294,400 字节 ≈ 7.9 MB
嗯,一张图就快 8MB 了。如果你的列表页同时加载 10 张,那就是 80MB。在低端机上,这已经接近极限了。
关键认知:图片的内存占用只取决于 像素尺寸 × 色彩格式,与文件格式(JPEG/PNG/WebP)无关。文件压缩只影响磁盘和网络传输,不影响解码后的内存。
2.2 两种主流格式:ARGB_8888 vs RGB_565
Android 中 Bitmap 的 Config 枚举定义了像素的存储方式。最常用的两个:
| Config | 每像素字节 | 颜色通道 | 透明度 | 典型场景 |
|---|---|---|---|---|
| ARGB_8888 | 4 字节 | A(8bit) + R(8bit) + G(8bit) + B(8bit) | 支持(256 级) | 照片、渐变、需要透明度 |
| RGB_565 | 2 字节 | R(5bit) + G(6bit) + B(5bit) | 不支持 | 无透明要求的图片、游戏素材 |
看到区别了吗?RGB_565 的内存只有 ARGB_8888 的一半。
同样一张 1080×1920 的图:
- ARGB_8888:7.9 MB
- RGB_565:3.95 MB
省了一半。代价是什么?颜色精度下降。RGB_565 只有 5 位红色、6 位绿色、5 位蓝色,总共 65536 种颜色。而 ARGB_8888 有 1600 万色以上。对于纯色块、UI 图标、游戏精灵来说,肉眼几乎看不出区别。但如果是照片,尤其是天空、皮肤这类渐变区域,RGB_565 会出现明显的色带。
我的建议:如果你确定图片不需要透明度,且对色彩精度要求不高(比如用户头像的缩略图、列表中的小图),可以考虑使用 RGB_565。我在做一款社交应用时,把列表中的头像从 ARGB_8888 切到 RGB_565,内存直接降了一半,用户完全没察觉差异。
2.3 其他格式一览
除了上面两个,Android 还支持其他格式,但用得少:
| Config | 每像素字节 | 说明 |
|---|---|---|
| ALPHA_8 | 1 字节 | 只有透明度通道,用于遮罩 |
| ARGB_4444 | 2 字节 | 已废弃,质量差,别用 |
| RGBA_F16 | 8 字节 | HDR 图片,每通道 16 位浮点 |
| HARDWARE | 视情况 | 硬件加速专用,不可直接读取像素 |
避坑:ARGB_4444 在 API 13 就已经被标记为废弃。我曾经在一个老项目里看到有人用这个格式来省内存,结果图片全是噪点。千万别用。
2.4 内存模型的核心逻辑
为了让你更直观地理解,我画了一张图:
2.5 实际项目中的选择策略
我在项目中总结了一套简单的选择规则:
- 默认用 ARGB_8888——兼容性最好,质量最高。除非你明确知道不需要透明度。
- 列表中的小图、头像、图标——可以考虑 RGB_565。尤其是缩略图,人眼对细节不敏感。
- 游戏中的精灵图、UI 九宫格——RGB_565 是首选,省内存且不影响视觉效果。
- 照片、大图、背景图——坚持用 ARGB_8888。RGB_565 的色带在渐变区域非常明显。
- 遮罩、形状裁剪——用 ALPHA_8,只存透明度,内存只有 1/4。
一个小技巧:如果你不确定某张图用 RGB_565 会不会翻车,可以在开发阶段做一个 A/B 对比。把两张图并排放在屏幕上,让设计师看一眼。大多数情况下,非照片类图片根本看不出区别。
2.6 别忘了屏幕密度
嗯,这里还有一个坑。上面说的「宽 × 高」是图片本身的像素尺寸。但如果你把图片放到不同密度的设备上,Android 的 BitmapFactory 会根据 inDensity 和 inTargetDensity 自动缩放。
举个例子:
- 一张 100×100 的图片,放在
mdpi的 drawable 文件夹 - 在
xxhdpi设备上加载 - Android 会把它放大到 100 × 3 = 300px
- 内存变成 300 × 300 × 4 = 360KB,而不是 100 × 100 × 4 = 40KB
我曾经排查过一个 OOM 问题,发现就是因为图片放在 mdpi 目录,而测试机是 xxxhdpi,内存膨胀了 9 倍。所以,图片资源要放在正确的密度目录下,或者用 inSampleSize 主动降采样。
避坑:不要把大图放在 drawable(无后缀)或 mdpi 目录下。否则在高密度设备上,内存会成倍增长。我见过一个案例,一张 500KB 的 PNG 放在 mdpi,在 xxxhdpi 设备上解码后占了 50MB 内存——这就是典型的「文件小、内存大」陷阱。
2.7 小结
记住这个公式就够了:内存 = 宽 × 高 × BPP。BPP 的选择直接影响内存占用和画质。ARGB_8888 是安全牌,RGB_565 是省内存利器,但各有适用场景。