Glide导致的RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap


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截图如下:
onLoadStarted调用过程
onLoadCleard调用过程

使用方式二: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
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值