Glide trying to use a recycled bitmap android.graphics.Bitmap

现象

Canvas: trying to use a recycled bitmap android.graphics.Bitmap@67d0cbd
ndroid.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:55)
1 android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:55)
2 android.view.DisplayListCanvas.throwIfCannotDraw(DisplayListCanvas.java:226)
3 android.view.RecordingCanvas.drawBitmap(RecordingCanvas.java:97)
4 android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:529)
5 android.widget.ImageView.onDraw(ImageView.java:1352)
6 android.view.View.draw(View.java:19439)
7 android.view.View.updateDisplayListIfDirty(View.java:18335)
8 android.view.View.draw(View.java:19143)
9 android.view.ViewGroup.drawChild(ViewGroup.java:4457)
10 android.view.ViewGroup.dispatchDraw(ViewGroup.java:4236)
11 android.view.View.draw(View.java:19447)
12 android.view.View.updateDisplayListIfDirty(View.java:18335)
13 android.view.View.draw(View.java:19143)
14 android.view.ViewGroup.drawChild(ViewGroup.java:4457)
15 android.view.ViewGroup.dispatchDraw(ViewGroup.java:4236)
16 android.view.View.updateDisplayListIfDirty(View.java:18321)
17 android.view.View.draw(View.java:19143)
18 android.view.ViewGroup.drawChild(ViewGroup.java:4457)
19 android.view.ViewGroup.dispatchDraw(ViewGroup.java:4236)
20 android.view.View.updateDisplayListIfDirty(View.java:18321)
21 android.view.View.draw(View.java:19143)
22 android.view.ViewGroup.drawChild(ViewGroup.java:4457)
23 android.view.ViewGroup.dispatchDraw(ViewGroup.java:4236)
24 android.view.View.updateDisplayListIfDirty(View.java:18321)
25 android.view.View.draw(View.java:19143)
26 android.view.ViewGroup.drawChild(ViewGroup.java:4457)
27 android.view.ViewGroup.dispatchDraw(ViewGroup.java:4236)
28 android.view.View.updateDisplayListIfDirty(View.java:18321)
29 android.view.View.draw(View.java:19143)
30 android.view.ViewGroup.drawChild(ViewGroup.java:4457)
31 android.view.ViewGroup.dispatchDraw(ViewGroup.java:4236)
32 android.view.View.updateDisplayListIfDirty(View.java:18321)
33 android.view.View.draw(View.java:19143)
34 android.view.ViewGroup.drawChild(ViewGroup.java:4457)
35 android.view.ViewGroup.dispatchDraw(ViewGroup.java:4236)
36 android.view.View.draw(View.java:19447)
37 com.android.internal.policy.DecorView.draw(DecorView.java:985)
38 android.view.View.updateDisplayListIfDirty(View.java:18335)
39 android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:669)
40 android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:675)
41 android.view.ThreadedRenderer.draw(ThreadedRenderer.java:783)
42 android.view.ViewRootImpl.draw(ViewRootImpl.java:3194)

分析

表象是在View系统绘制ImageView的时候,ImageView的Bitmap被回收了,实际看不到具体崩溃在哪里,根据业务场景判断出可能是Glide造成的奔溃。
代码如下:

  Glide.with(activity).asBitmap().load(url)
    .into(object : CustomViewTarget<ImageView, Bitmap>(targetView) {
        override fun onLoadFailed(errorDrawable: Drawable?) {}
        override fun onResourceCleared(placeholder: Drawable?) {}
        override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
            targetView.setImageBitmap(resource)
        }

因此有两个猜想:

  1. onResourceReady给出的bitmap是已经被回收的。
  2. onResourceReady给出的bitmap是正常的,将Bitmap设置给ImageView之后,Glide又回收了该Bitmap。

Glide是一个非常通用的组件,猜想1应该不成立,就围绕猜想2进行分析Glide源代码。

原因说明

  • 问题直接原因:ImageView的图片被回收,在绘制ImageView的时候奔溃

  • 分析(一句话总结:新图片资源还没来,旧图片资源已被回收):
    Glide内存的缓存有几个: Active,Cache,Pool。
    当调用Glide的into的时候,当前的Target(包含Bitmap)进入Active,前一个Target(包含Bitmap)会从Active进入Cache。
    当内存紧张或者主动调用Glide.clearMemory时,会先执行Cache.clearMemory,会同步将Cache中的Target会进入Pool。然后会执行Pool.clearMemory,这时会出现调用Bitmap.recycle()。
    如果完成了clearMemory,而当前请求的图片资源因为网络速度慢或者被暂停,那么当前ImageView中的图片还是旧图片,如果发生重绘,就会因图片被回收而崩溃。

  • 我所遇到的情况:
    A界面使用Glide定时加载不同图片到ImageView中进行展示,这时跳转到B界面,Glide当前的Target会被clear,也会停止正在请求的图片(跟随了Activity生命周期)。
    在B界面发生了内存不足,发生了onLowMemory调用,由此引发上前面说明的Glide缓存回收的过程,图片被recycle,也就是A界面的ImageView的Bitmap被回收了,因为此时Activity处于onStop状态,不会发现绘制,所以不会有问题。
    但从预览界面再回来,首先会发生重绘,而此时ImageView的图片已经被回收,从而引发崩溃。
    除因为跳转到其他界面会发生这个问题外,如果在A界面网络很慢,也会发生该问题。

解决办法

办法1:在onResourceCleared给ImageView设置默认图片或者null.
办法2:直接使用into(ImageView)代替into(Target),如果需要在图片加载成功做一些事情,可以配合Listener来做。

关键点

Glide在内存紧张时候的回收行为会造成图片被回收。
Glide会随着Activity的stop会停止执行图片加载,在重新回到Activity的时候,会继续加载之前的请求。
新图片还未请求到,而ImageView中的旧图片已经被回收,ImageView绘制过程中崩溃。

结束

Glide的官方文档也有说明,在onResourceCleared的时候需要进行清理。

你可能感兴趣的:(Android)