現象
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)
}
因此有兩個猜想:
- onResourceReady給出的bitmap是已經被回收的。
- 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的時候需要進行清理。