第2章:内存计算基础——Bitmap到底吃了多少内存?
好,咱们直接进入正题。上一章我讲了Bitmap的整个生命周期,这一章我们来算一笔账——Bitmap到底占了多少内存?
你可能觉得这很简单,不就是宽×高×每个像素的字节数吗?嗯,对了一半。但实际项目中,我见过太多人栽在这个“简单”的公式上。
2.1 核心公式:一张图的内存怎么算?
先上公式,这个你得刻在脑子里:
Bitmap内存 ≈ 宽度 × 高度 × 每个像素占用的字节数
等等,这里有个坑——宽度和高度是像素单位,不是dp,也不是英寸。我当年刚入行时,就犯过这个错,把布局里的dp值直接当像素用了,结果算出来的内存差了十万八千里。
举个例子:一张 1920×1080 的图片,用 ARGB_8888 格式加载:
内存 = 1920 × 1080 × 4 = 8,294,400 字节 ≈ 7.9 MB
一张图就吃掉将近8MB。你想想看,如果列表里加载20张这样的图,光图片就占160MB。嗯,这就是OOM的常见原因之一。
核心要点:Bitmap内存 = 像素总数 × 每像素字节数。这个公式是基础,但实际值还会受像素密度影响,下面细说。
2.2 宽高与像素密度的关系——这个坑我踩过
很多新手会问:我图片是100×100的,放到hdpi和xxhdpi设备上,内存一样吗?
不一样,而且差别很大。
Android系统在加载资源图片时,会根据设备密度自动缩放。具体规则是这样的:
- mdpi(160dpi):基准密度,缩放系数 = 1.0
- hdpi(240dpi):缩放系数 = 1.5
- xhdpi(320dpi):缩放系数 = 2.0
- xxhdpi(480dpi):缩放系数 = 3.0
- xxxhdpi(640dpi):缩放系数 = 4.0
实际加载时,Bitmap的宽高会被乘以这个系数。公式变成:
实际宽度 = 原始宽度 × (设备密度 / 资源所在文件夹的基准密度)
实际高度 = 原始高度 × (设备密度 / 资源所在文件夹的基准密度)
举个例子你就明白了:
一张 100×100 的图片,放在 drawable-mdpi 文件夹里。在 xxhdpi(480dpi)设备上加载:
实际宽度 = 100 × (480 / 160) = 300 像素
实际高度 = 100 × (480 / 160) = 300 像素
内存 = 300 × 300 × 4 = 360,000 字节 ≈ 351 KB
同样的图片,如果放在 drawable-xxhdpi 文件夹里:
实际宽度 = 100 × (480 / 480) = 100 像素
实际高度 = 100 × (480 / 480) = 100 像素
内存 = 100 × 100 × 4 = 40,000 字节 ≈ 39 KB
看到了吗?同一个图片,放在不同密度的文件夹里,内存差了将近9倍!
避坑指南:我曾经接手过一个项目,所有图片都扔在drawable-mdpi里,结果在高端机型上频繁OOM。后来一查,图片被放大了3倍,内存暴涨。所以,图片一定要放在对应密度的文件夹里,或者直接用 drawable-nodpi 禁止缩放。
2.3 不同格式的内存对比——选对格式省一半内存
Android中Bitmap支持多种格式,每种格式每个像素占用的字节数不同。我整理了一张表,你直接收藏:
| 格式 | 每像素字节数 | 支持透明度 | 适用场景 |
|---|---|---|---|
| ARGB_8888 | 4字节 | 是 | 默认格式,色彩丰富,质量最高 |
| RGB_565 | 2字节 | 否 | 无透明需求的图片,省一半内存 |
| ARGB_4444 | 2字节 | 是 | 已废弃,质量差,别用了 |
| ALPHA_8 | 1字节 | 仅透明度 | 只有透明度信息的图片,如蒙版 |
来算一笔账:一张 1920×1080 的图片:
- ARGB_8888:1920 × 1080 × 4 = 7.9 MB
- RGB_565:1920 × 1080 × 2 = 3.95 MB
直接省了一半!如果你的图片不需要透明度,比如JPEG格式的图片,用RGB_565完全够用。我做过一个相册App,所有缩略图都用RGB_565,内存占用直接砍半,用户体验提升明显。
小技巧:在Android 4.4(API 19)以上,BitmapFactory.Options 的 inPreferredConfig 可以指定加载格式。我习惯这样用:
options.inPreferredConfig = Bitmap.Config.RGB_565;
但要注意,如果原图有透明度,RGB_565会丢失透明通道,显示为黑色背景。所以,有透明需求的图片还是用ARGB_8888。
2.4 实战中的内存估算——别等OOM了才后悔
说了这么多理论,咱们来点实际的。你在开发时,怎么快速估算一张图的内存?
我个人习惯用这个公式:
内存(MB) ≈ (宽度 × 高度 × 每像素字节数) / (1024 × 1024)
举个例子,一张 2000×1500 的图片,ARGB_8888格式:
内存 ≈ (2000 × 1500 × 4) / 1,048,576 ≈ 11.4 MB
嗯,一张图就11MB。如果你的App可用内存只有64MB,那同时显示5张这样的图就危险了。
我建议你在开发阶段,就加上内存监控:
// 获取Bitmap的内存大小(API 12以上)
int size = bitmap.getByteCount();
// API 19以上推荐用这个
int size = bitmap.getAllocationByteCount();
这两个方法的区别在于:getByteCount() 返回的是图片实际占用的内存,而 getAllocationByteCount() 返回的是系统分配的内存。在复用Bitmap时,后者可能更大。我一般用 getAllocationByteCount(),因为它更接近真实的内存占用。
总结一下这一章的核心:
- Bitmap内存 = 宽 × 高 × 每像素字节数
- 像素密度会导致图片缩放,注意资源文件夹的选择
- RGB_565比ARGB_8888省一半内存,适合无透明度的图片
- 开发时用
getAllocationByteCount()监控内存
下一章,我会讲Bitmap的加载策略——如何优雅地加载大图,避免OOM。到时候我会分享一个我压箱底的加载框架设计思路,敬请期待。