文章目录
Glide导致的RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap分析(基于Glide 4.9.0)
Glide使用背景
Glide在系统内存不足或者BitmapPool缓存池占满时,会回收内存缓存中的一部分Bitmap对象,并且调用bitmap.recycle()
,
bitmap.recycle()
后,继续使用这个bitmap对象,会抛出异常
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@9025008
- Glide.with(mContext).load(imageUrl).apply(requestOptions).into(imageView);不会导致题中的问题。
- Glide.with(mContext).load(imageUrl).apply(requestOptions).into(new ViewTarget);这种方式可能会导致抛出异常。
使用方式一:into传入ImageView
RequestOptions requestOptions = new RequestOptions()
.override(20, 60)
.placeholder(ContextCompat.getDrawable(mContext, R.color.background));
Glide.with(mContext)
.load(imageUrl)
.apply(requestOptions)
.into(imageView);
分析1:scaleType=fitXY是否会导致trying to use a recycled bitmap异常?
Glide在使用override(int width, int height)
设置加载bitmap的大小时,会根据width和height中较小的值,和图片资源的width和height计算出缩放比例。举个例子:
-
情形一:override(120, 40)
当图片大小为 720 * 240,会根据height计算缩放比,即缩小6倍,那么加载到内存中bitmap宽高为 120 * 40
当图片大小为 720 * 360,会根据height计算缩放比,即缩小9倍,那么加载到内存中bitmap宽高为 80 * 40
-
情形二:override(20, 60)
当图片大小为 720 * 240,会根据width计算缩放比,即缩小36倍,那么加载到内存中bitmap宽高为 20 * 6
当图片大小为 720 * 360,会根据width计算缩放比,即缩小36倍,那么加载到内存中bitmap宽高为 20 * 10
通过上述分析,可以放心给ImageView设置scaleType=fitXY
分析2:into传入imageView是否会导致trying to use a recycled bitmap异常?
Glide在系统内存不足或者BitmapPool缓冲池占满时,会回收内存缓存中的一部分Bitmap对象,并且调用bitmap.recycle()
,
bitmap.recycle()
后,继续使用这个bitmap对象,会抛出异常
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@9025008
Glide使用into(imageView)
的方式加载图片,不会抛出异常。into(imageView)
中会调用到ImageViewTarget#onLoadCleared
或者ImageViewTarget#onLoadStarted
,这里面又调用了setResourceInternal(null)
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
setResourceInternal(null);
setDrawable(placeholder);
}
public void onLoadCleared(@Nullable Drawable placeholder) {
super.onLoadCleared(placeholder);
if (animatable != null) {
animatable.stop();
}
setResourceInternal(null);
setDrawable(placeholder);
}
setResourceInternal(null)
最终会调用ImageView.setImageBitmap(null)
,然后再通过setDrawable(placeholder)
设置默认占位图。因在into(imageView)
中,最终调用到了ImageView.setImageBitmap(null)
,所以在异步加载图片之前把原先的bitmap对象清除了,所以UI重新绘制时不会用到已回收的recycled的bitmap对象。
调用栈debug截图如下:
使用方式二:into传入ViewTarget
RequestOptions requestOptions = new RequestOptions()
.override(mScreenWidth, mScreenWidth / 3)
.placeholder(ContextCompat.getDrawable(mContext, R.color.background));
Glide.with(mContext)
.load(imageUrl)
.apply(requestOptions)
.into(new ViewTarget<View, Drawable>(imageView) {
@Override
public void onResourceReady(@NonNull Drawable drawable,
Transition<? super Drawable> transition) {
if (drawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
int height = bitmap.getHeight();
int width = bitmap.getWidth();
Log.d("width=" + width + ", height=" + height);
}
imageView.setBackground(drawable);
}
});
这种方式可能会导致抛出异常java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@9025008
异常原因分析
View有一个mBackground
对象属性,是Drawable
类型,这里的实际是BitmapDrawable
,里面保存着Bitmap对象,mBackground中的Bitmap对象,同时也会被Glide缓存着,在Glide中随时可能会回收掉,并调用bitmap.recycle()
。
场景复现:
把Glide的内存缓存设置的小一点。RecyclerView
来回滑动会在Adapter中重新绑定数据,使用Glide重新加载图片,而Glide加载图片是异步的,子View在Glide回调onResourceReady
回来之前就进行了绘制,子View调用draw(Canvas)
方法中会调用drawBackgroud
,此时使用的是原先的mBackground
,但是此时mBackground中的bitmap可能已经被回收,即调用了bitmap.recycle(),所以就抛出异常了。
解决方案
可以参考Glide的into(imageView)的处理方式,即在加载图片之前先调用该View的setBackground设置默认背景,imageView.setBackgroundResource(R.color.background)
// 加载之前先调用该View的setBackground设置默认背景
imageView.setBackgroundResource(R.color.background);
RequestOptions requestOptions = new RequestOptions()
.override(mScreenWidth, mScreenWidth / 3)
.placeholder(ContextCompat.getDrawable(mContext, R.color.background));
Glide.with(mContext)
.load(imageUrl)
.apply(requestOptions)
.into(new ViewTarget<View, Drawable>(imageView) {
@Override
public void onResourceReady(@NonNull Drawable drawable,
Transition<? super Drawable> transition) {
if (drawable instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
int height = bitmap.getHeight();
int width = bitmap.getWidth();
Log.d("width=" + width + ", height=" + height);
}
imageView.setBackground(drawable);
}
});
异常调用栈如下:
06-25 17:29:04.890 17001 17001 E AndroidRuntime: FATAL EXCEPTION: main
06-25 17:29:04.890 17001 17001 E AndroidRuntime: Process: com.r.demo, PID: 17001
06-25 17:29:04.890 17001 17001 E AndroidRuntime: java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@9025008
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:55)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.DisplayListCanvas.throwIfCannotDraw(DisplayListCanvas.java:226)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.RecordingCanvas.drawBitmap(RecordingCanvas.java:97)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:529)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.getDrawableRenderNode(View.java:19450)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.drawBackground(View.java:19386)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:19183)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:18142)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:18920)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:18133)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:18920)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.support.v7.widget.RecyclerView.drawChild(RecyclerView.java:4820)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:19195)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.support.v7.widget.RecyclerView.draw(RecyclerView.java:4219)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:18142)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:18920)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:19195)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:18142)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:18920)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:18133)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:18920)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:18133)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:18920)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:18133)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:18920)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:19195)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:18142)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:18920)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at com.r.demo.widget.swipebacklayout.SwipeBackLayout.drawChild(SwipeBackLayout.java:404)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:18133)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.draw(View.java:18920)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.View.updateDisplayListIfDirty(View.java:18133)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:669)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:675)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:783)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewRootImpl.draw(ViewRootImpl.java:3013)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2827)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2380)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1413)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6781)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.Choreographer.doCallbacks(Choreographer.java:723)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.Choreographer.doFrame(Choreographer.java:658)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:790)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:99)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.os.Looper.loop(Looper.java:164)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6523)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
06-25 17:29:04.890 17001 17001 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:819)