Android Bitmap 回收问题
01.recycle()方法
如何调用这个recycle()方法
1
2
3
4if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}思考以下,为何调用recycle()需要做非空判断?这里可以引出bitmap系统回收功能。小杨我如果分析不对,欢迎反馈。
- 首先看看源码……顺便翻一下该方法的注释!我是用有道翻译的,大意如下:释放与此位图关联的本机对象,并清除对像素数据的引用。这将不会同步释放像素数据;如果没有其他引用,它只允许垃圾收集。位图被标记为“死”,这意味着如果调用getPixels()或setPixels(),它将抛出异常,并且不会绘制任何东西。此操作不能反转,因此只有在确定没有进一步使用位图的情况下才应调用该操作。这是一个高级调用,通常不需要调用,因为当没有对此位图的引用时,普通GC进程将释放此内存。
1
2
3
4
5
6
7
8
9
10
11
12public void recycle() {
if (!mRecycled && mNativePtr != 0) {
if (nativeRecycle(mNativePtr)) {
// return value indicates whether native pixel object was actually recycled.
// false indicates that it is still in use at the native level and these
// objects should not be collected now. They will be collected later when the
// Bitmap itself is collected.
mNinePatchChunk = null;
}
mRecycled = true;
}
}通常不需要调用?这是为啥?
在Android3.0以后Bitmap是存放在堆中的,只要回收堆内存即可。官方建议我们3.0以后使用recycle()方法进行回收,该方法可以不主动调用,因为垃圾回收器会自动收集不可用的Bitmap对象进行回收。
大概就是移除最少使用的缓存和使用最久的缓存,先说出结论,下来接着分析!
02.Bitmap缓存原理
- LruCache原理
- LruCache是个泛型类,内部采用LinkedHashMap来实现缓存机制,它提供get方法和put方法来获取缓存和添加缓存,其最重要的方法trimToSize是用来移除最少使用的缓存和使用最久的缓存,并添加最新的缓存到队列中。
03.Bitmap的复用
- Android3.0之后,并没有强调Bitmap.recycle();而是强调Bitmap的复用。
- 使用LruCache对Bitmap进行缓存,当再次使用到这个Bitmap的时候直接获取,而不用重走编码流程。
- Android3.0(API 11之后)引入了BitmapFactory.Options.inBitmap字段,设置此字段之后解码方法会尝试复用一张存在的Bitmap。这意味着Bitmap的内存被复用,避免了内存的回收及申请过程,显然性能表现更佳。
- 使用这个字段有几点限制:
- 声明可被复用的Bitmap必须设置inMutable为true;
- Android4.4(API 19)之前只有格式为jpg、png,同等宽高(要求苛刻),inSampleSize为1的Bitmap才可以复用;
- Android4.4(API 19)之前被复用的Bitmap的inPreferredConfig会覆盖待分配内存的Bitmap设置的inPreferredConfig;
- Android4.4(API 19)之后被复用的Bitmap的内存必须大于需要申请内存的Bitmap的内存;
- Android4.4(API 19)之前待加载Bitmap的Options.inSampleSize必须明确指定为1。
04.Bitmap如何复用
Bitmap复用的实验,代码如下所示,然后看打印的日志信息
- 从内存地址的打印可以看出,两个对象其实是一个对象,Bitmap复用成功;
- bitmapReuse占用的内存(4346880)正好是bitmap占用内存(1228800)的四分之一;
- getByteCount()获取到的是当前图片应当所占内存大小,getAllocationByteCount()获取到的是被复用Bitmap真实占用内存大小。虽然bitmapReuse的内存只有4346880,但是因为是复用的bitmap的内存,因而其真实占用的内存大小是被复用的bitmap的内存大小(1228800)。这也是getAllocationByteCount()可能比getByteCount()大的原因。
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@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void initBitmap() {
BitmapFactory.Options options = new BitmapFactory.Options();
// 图片复用,这个属性必须设置;
options.inMutable = true;
// 手动设置缩放比例,使其取整数,方便计算、观察数据;
options.inDensity = 320;
options.inTargetDensity = 320;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg_autumn_tree_min, options);
// 对象内存地址;
Log.i("ycBitmap", "bitmap = " + bitmap);
Log.i("ycBitmap", "ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
// 使用inBitmap属性,这个属性必须设置;
options.inBitmap = bitmap; options.inDensity = 320;
// 设置缩放宽高为原始宽高一半;
options.inTargetDensity = 160;
options.inMutable = true;
Bitmap bitmapReuse = BitmapFactory.decodeResource(getResources(), R.drawable.bg_kites_min, options);
// 复用对象的内存地址;
Log.i("ycBitmap", "bitmapReuse = " + bitmapReuse);
Log.i("ycBitmap", "bitmap:ByteCount = " + bitmap.getByteCount() + ":::bitmap:AllocationByteCount = " + bitmap.getAllocationByteCount());
Log.i("ycBitmap", "bitmapReuse:ByteCount = " + bitmapReuse.getByteCount() + ":::bitmapReuse:AllocationByteCount = " + bitmapReuse.getAllocationByteCount());
//11-26 18:24:07.971 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmap = android.graphics.Bitmap@9739bff
//11-26 18:24:07.972 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmap:ByteCount = 4346880:::bitmap:AllocationByteCount = 4346880
//11-26 18:24:07.994 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmapReuse = android.graphics.Bitmap@9739bff
//11-26 18:24:07.994 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmap:ByteCount = 1228800:::bitmap:AllocationByteCount = 4346880
//11-26 18:24:07.994 15470-15470/com.yc.cn.ycbanner I/ycBitmap: bitmapReuse:ByteCount = 1228800:::bitmapReuse:AllocationByteCount = 4346880
}