Bitmap

Bitmap

参考:高效加载大型位图

参考:管理位图内存

不同 Android 版本时的Bitmap内存模型

API 级别 API 10- API 11 ~ API 25 API 26+
Bitmap 对象存放 Java heap Java heap Java heap
像素 (pixel data)数据存放 native heap Java heap native heap

API 10- 该方式有个缺点,就是 java 层已经被回收掉了,但是 native 层并不清楚,回收时机不明确,所以在 API 11 ~ API 25 将它放入到 java 层中,在 API 26+ 又做了优化,java 回收后能迅速通知到到 native

获取 Bitmap 占用内存
  • getByteCount 函数:直接返回 bitmap 占用的内存,但需要运行时动态算出来
缩小图片

方案一:

该方案得到的结果宽高比例并不一定与 inSampleSize 一致,加载完后还会根据密度等比放大或缩小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
val targetSize = 400
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(resources, R.mipmap.avatar, options)
options.inJustDecodeBounds = false
val (height: Int, width: Int) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (height > targetSize || width > targetSize) {
val halfHeight: Int = height / 2
val halfWidth: Int = width / 2
while (halfHeight / inSampleSize >= targetSize && halfWidth / inSampleSize >= targetSize) {
inSampleSize *= 2
}
}
options.inSampleSize = inSampleSize
val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.avatar, options)

方案二:

该方案会将图片按 targetSize/outWidth 的比例进行缩放

1
2
3
4
5
6
7
8
9
val targetSize = 400
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(resources, R.mipmap.avatar, options)
options.inJustDecodeBounds = false
options.inScaled = true
options.inDensity = options.outWidth
options.inTargetDensity = targetSize
val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.avatar, options)

【注意】上述两种方案得到的Bitmap密度并不一致

在 Android 3.0 及更高版本上管理内存

声明重用管理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class ImageCache {

private final Set<SoftReference<Bitmap>> mReusableBitmaps = Collections.synchronizedSet(new HashSet<>());
private final LruCache<String, BitmapDrawable> mMemoryCache = new LruCache<String, BitmapDrawable>(1024) {
@Override
protected void entryRemoved(boolean evicted, @NotNull String key,
@NotNull BitmapDrawable oldValue, BitmapDrawable newValue) {
Bitmap bitmap = oldValue.getBitmap();
if (bitmap.isMutable()) {
mReusableBitmaps.add(new SoftReference<>(bitmap));
}
}
};

/**
* 遍历可重复使用的Bitmap,查找出合适重用的对象
*/
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
Bitmap bitmap = null;
if (!mReusableBitmaps.isEmpty()) {
synchronized (mReusableBitmaps) {
final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator();
Bitmap item;
while (iterator.hasNext()) {
item = iterator.next().get();
if (item != null && item.isMutable()) {
// 判断InBitmap是否可用
if (canUseForInBitmap(item, options)) {
bitmap = item;
iterator.remove();
break;
}
} else {
iterator.remove();
}
}
}
}
return bitmap;
}

/**
* 是否允许重用Bitmap
*
* @param candidate 候选的Bitmap
* @param targetOptions 目标配置项
*/
public static boolean canUseForInBitmap(Bitmap candidate, BitmapFactory.Options targetOptions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
}
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}

/**
* 根据位图的配置返回位图每像素的字节使用率
*/
public static int getBytesPerPixel(Bitmap.Config config) {
if (config == Bitmap.Config.ARGB_8888) {
return 4;
} else if (config == Bitmap.Config.RGB_565) {
return 2;
} else if (config == Bitmap.Config.ARGB_4444) {
return 2;
} else if (config == Bitmap.Config.ALPHA_8) {
return 1;
}
return 1;
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class BitmapUtil {

public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight, ImageCache cache) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filename, options);
// 这里可能还需要做大小的缩放
addInBitmapOptions(options, cache);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filename, options);
}

private static void addInBitmapOptions(BitmapFactory.Options options,
ImageCache cache) {
if (cache != null) {
// 尝试查找可用的inBitmap位图。
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null) {
// inBitmap只适用于可变位图,因此强制解码器返回可变位图。
options.inMutable = true;
options.inBitmap = inBitmap;
}
}
}
}