Canvas: trying to use a recycled bitmap android.graphics
又又遇到祖传问题,网上一搜6年前都有这个问题,看来从历史吸取经验教训是最快的成长!
1、异常捕获
复现很容易:写一个Bitmap,调用Bitmap的recycle()方法将其回收掉,再设置给ImageView。
mBitmap.recycle();
mImageView.setImageBitmap(mBitmap);
捕获到下面的异常:
Process: com.easyicon.learnglide, PID: 8337
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@4337e91
at android.graphics.Canvas.throwIfCannotDraw(Canvas.java:1270)
at android.graphics.Canvas.drawBitmap(Canvas.java:1404)
at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:544)
at android.widget.ImageView.onDraw(ImageView.java:1246)
at android.view.View.draw(View.java:16214)
at android.view.View.updateDisplayListIfDirty(View.java:15211)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)
at android.view.View.updateDisplayListIfDirty(View.java:15171)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)
at android.view.View.updateDisplayListIfDirty(View.java:15171)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)
at android.view.View.updateDisplayListIfDirty(View.java:15171)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)
at android.view.View.updateDisplayListIfDirty(View.java:15171)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)
at android.view.View.updateDisplayListIfDirty(View.java:15171)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)
at android.view.View.updateDisplayListIfDirty(View.java:15171)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:296)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:302)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:337)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:2883)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2699)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2328)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1340)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6616)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:606)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5554)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:935)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:726)
2、分析
异常堆栈一大长串,前面的是ActivityThread主线程Looper处理事件,通过Handler分发。接着是Choreographer图像绘制。直接看重点ImageView的onDraw()方法:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
...
mDrawable.draw(canvas);
canvas.restoreToCount(saveCount);
}
}
接着调用Drawable的draw(Canvas )方法,Drawable是个抽象类,BitmapDrawable继承自Drawable(还有很多其它类也继承自Drawable),实现draw(Canvas canvas)方法:
@Override
public void draw(Canvas canvas) {
final Bitmap bitmap = mBitmapState.mBitmap;
if (bitmap == null) {
return;
}
...
canvas.drawBitmap(bitmap, null, mDstRect, paint);
...
}
接着调用Canvas类的drawBitmap()方法,Canvas继承自BaseCanvas类,该方法定义在BaseCanvas类中:
public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,
@Nullable Paint paint) {
if (dst == null) {
throw new NullPointerException();
}
throwIfCannotDraw(bitmap);
throwIfHasHwBitmapInSwMode(paint);
final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
...
}
可以看见在绘制Bitmap方法中,调用了throwIfCannotDraw(Bitmap bitmap)方法:
protected void throwIfCannotDraw(Bitmap bitmap) {
if (bitmap.isRecycled()) {
throw new RuntimeException("Canvas: trying to use a recycled bitmap " + bitmap);
}
if (!bitmap.isPremultiplied() && bitmap.getConfig() == Bitmap.Config.ARGB_8888 &&
bitmap.hasAlpha()) {
throw new RuntimeException("Canvas: trying to use a non-premultiplied bitmap "
+ bitmap);
}
throwIfHwBitmapInSwMode(bitmap);
}
如果Bitmap已经被回收isRecycled()就抛出异常:RuntimeException("Canvas: trying to use a recycled bitmap " + bitmap)
3、处理
无非就是异常捕获,另外一个就是严谨的时序逻辑(安卓中很难!)
3.1、继承ImageView捕获
既然异常抛出在ImageView的onDraw()阶段,那么就在ImageView的onDraw()方法中捕获它:
private class SafeImageView extends ImageView {
public SafeImageView(Context context) {
super(context);
}
public SafeImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public SafeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SafeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
//捕获可能的异常
@Override
protected void onDraw(Canvas canvas) {
try {
super.onDraw(canvas);
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
}
3.2、使用时严谨的判断
好在Bitmap提供了isRecycled()方法,使用Bitmap的时候判断是否回收了。
if (null != mBitmap && !mBitmap.isRecycled()) {
mImageView.setImageBitmap(mBitmap);
}
参考资料:
Canvas: trying to use a recycled bitmap android.graphics.Bitmap@XXX
Android手动回收bitmap,引发Canvas: trying to use a recycled bitmap处理
trying to use a recycled bitmap android.graphics.Bitmap@af903b
Bitmap回收—Canvas: trying to use a recycled bitmap android.graphics
关于Glide出现trying to use a recycled bitmap错误的问题分析