• 关键信息:

画布:尝试使用回收的位图android.graphics.Bitmap@eff9b3e

  • 具体日志:
Fatal Exception: java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@eff9b3e
       at android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:77)
       at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:278)
       at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:91)
       at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:548)
       at android.view.View.getDrawableRenderNode(View.java:24265)
       at android.view.View.drawBackground(View.java:24170)
       at android.view.View.draw(View.java:23893)
       at android.view.View.updateDisplayListIfDirty(View.java:22776)
       at android.view.View.draw(View.java:23631)
  • 描述

该问题的本质原因就是关键信息中所说的:尝试使用一个已经被回收的bitmap

  • 分析

看注释描述,能提取的有用信息是,创建的bitmap可能是同一个对象。看下面的代码,更能说明次问题。

 /**
     * Returns an immutable bitmap from subset of the source bitmap,
     * transformed by the optional matrix. The new bitmap may be the
     * same object as source, or a copy may have been made. It is
     * initialized with the same density as the original bitmap.
     * 
     * If the source bitmap is immutable and the requested subset is the
     * same as the source bitmap itself, then the source bitmap is
     * returned and no new bitmap is created.
     */
    public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,
            Matrix m, boolean filter) {
        //...
         // check if we can just return our argument unchanged
        if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() &&
                height == source.getHeight() && (m == null || m.isIdentity())) {
            return source;
        }
        //...
        return bitmap;
    }
  • 结果

当满足上述的两个条件之后,创建了同一个bitmap, 在调用createBitmap之后 直接调用recycle(), 那后续操作的时候肯定会出问题

  • 重点场景

recyclerView 列表中,存在大量复用图片,该问题会更加明显

我代码中的一个场景

fun rotateBitmap(bitmap: Bitmap, rotation: Int): Bitmap? {
    if (rotation == 0) return bitmap
    val matrix = Matrix()
    matrix.setRotate(rotation.toFloat())
    try {
        val bmRotated =
            Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
        bitmap.recycle()
        return bmRotated
    } catch (e: OutOfMemoryError) {
        e.printStackTrace()
        return null
}
}

当两次调用的bitmap 相同时,我们第一次进行createBitmap, 然后将原bitmap 销毁了,此时刚好bitmap满足上述条件,那将会爆发此问题。

  • 处理方法
fun rotateBitmap(bitmap: Bitmap, rotation: Int): Bitmap? {
    if (rotation == 0) return bitmap
    val matrix = Matrix()
    matrix.setRotate(rotation.toFloat())
    try {
        val bmRotated =
            Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
        if (bitmap != bmRotated) { //判断两个bitmap 不相同时进行释放
            bitmap.recycle()
        }
        return bmRotated
    } catch (e: OutOfMemoryError) {
        e.printStackTrace()
        return null
}
}

总结

在使用Bitmap 相关时一定要仔细,它是一个直接关系内存,关系应用是否会卡顿的指标,其次,应用目前对于位图的使用可谓是重中之重,哪一个APP没有图片,真的是丝毫不敢出错啊