BitmapFactory详解:Options、inSampleSize与inJustDecodeBounds
说到Bitmap加载,绕不开的就是BitmapFactory。这个类就像一把瑞士军刀,功能很全,但用不好也容易伤到手。今天我就把它的几个核心参数掰开揉碎了讲清楚。
BitmapFactory.Options——解码的“控制面板”
先说说Options这个类。它本质上就是一个配置容器,你往里面塞参数,BitmapFactory就按你的意思去解码。我刚开始接触时觉得它很啰嗦,后来才发现——没有Options,你根本没法控制内存。
来看一个最基础的用法:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.photo, options);
这里我指定了RGB_565格式,每个像素只占2字节。相比默认的ARGB_8888(4字节/像素),内存直接砍半。嗯,代价就是颜色精度下降,但很多场景下肉眼根本看不出来。
inSampleSize——缩放的魔法数字
这个参数太重要了。说白了,它就是告诉解码器:“别把原图全塞进来,给我按比例缩小”。
举个例子:一张1920x1080的图片,你ImageView只有480x270。如果不做采样,内存里会塞进一张全尺寸的Bitmap。但如果你设置inSampleSize = 4,解码出来的就是480x270,内存占用只有原来的1/16。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4; // 宽高各缩小为1/4
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/photo.jpg", options);
那怎么计算合适的采样值呢?我一般这么干:
public static int calculateInSampleSize(
BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
这段代码的逻辑很简单:不断尝试2倍缩放,直到宽高都小于目标尺寸。你想想看,如果原图4000x3000,目标800x600,那inSampleSize就是4,解码出来1000x750,稍微大一点但可以接受。
inJustDecodeBounds——先“偷看”再解码
这个参数是我个人最喜欢的。它就像一个侦察兵,先摸清敌情再决定怎么打。
设置inJustDecodeBounds = true时,解码器只读取图片的宽高、格式等信息,不加载像素数据。内存占用几乎为零。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.photo, options);
// 此时options里已经有宽高了
int imageWidth = options.outWidth;
int imageHeight = options.outHeight;
// 根据宽高计算采样值
options.inSampleSize = calculateInSampleSize(options, 200, 200);
// 正式解码
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.photo, options);
这个流程我几乎每个项目都用。先预读,再计算采样,最后正式解码。三步走,内存稳稳的。
完整的工作流
把上面几个知识点串起来,就是一个标准的Bitmap加载流程:
- 创建Options对象
- 设置inJustDecodeBounds = true,预读宽高
- 根据目标尺寸计算inSampleSize
- 设置inJustDecodeBounds = false
- 设置inPreferredConfig等参数
- 调用decode方法,拿到缩放过后的Bitmap
你想想看,如果不做这些预处理,直接加载一张4000x3000的图片,内存占用就是4000*3000*4 = 48MB。而经过采样后,可能只需要3MB。这差距,够明显吧?
其他实用参数
| 参数名 | 作用 | 我的建议 |
|---|---|---|
| inMutable | 是否允许修改Bitmap像素 | 不需要修改时设为false,性能更好 |
| inBitmap | 复用已有的Bitmap内存 | 配合缓存池使用,减少GC压力 |
| inDensity | 图片的原始密度 | 从资源加载时系统会自动处理 |
| inTargetDensity | 目标屏幕密度 | 用于自动缩放,注意别重复缩放 |
说实话,这些参数我平时用得不多。但inBitmap我强烈建议你了解一下——它能让Bitmap复用,避免频繁创建和回收。后面讲缓存策略时我会详细展开。
好了,BitmapFactory的核心用法就这些。说白了就是三件事:控制格式、控制尺寸、预读信息。把这三点吃透了,Bitmap加载这块你就掌握了八成。下一章我们聊聊缓存策略,那才是真正考验设计能力的地方。