Android Bitmap 回收问题

01.recycle()方法

  • 如何调用这个recycle()方法

    1
    2
    3
    4
    if (bitmap != null && !bitmap.isRecycled()) {
    bitmap.recycle();
    bitmap = null;
    }
  • 思考以下,为何调用recycle()需要做非空判断?这里可以引出bitmap系统回收功能。小杨我如果分析不对,欢迎反馈。

    • 首先看看源码……顺便翻一下该方法的注释!我是用有道翻译的,大意如下:释放与此位图关联的本机对象,并清除对像素数据的引用。这将不会同步释放像素数据;如果没有其他引用,它只允许垃圾收集。位图被标记为“死”,这意味着如果调用getPixels()或setPixels(),它将抛出异常,并且不会绘制任何东西。此操作不能反转,因此只有在确定没有进一步使用位图的情况下才应调用该操作。这是一个高级调用,通常不需要调用,因为当没有对此位图的引用时,普通GC进程将释放此内存。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public 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
    }