起因
我司軟件之前一直使用自定義的圖片加載器湿诊,為了順應(yīng)時(shí)代的潮流,決定引入Glide圖片加載框架瘦材,然而自從上線之日起就一直收到這個(gè)異常,雖然量不大厅须,但是不能忍啊。
Canvas: trying to use a recycled bitmap android.graphics.Bitmap
分析
- Glide 返回一個(gè)被回收的bitmap
- Glide 在某種情境下自動(dòng)回收了bitmap
Glide作為一個(gè)非常成熟的框架宇色,應(yīng)該不會(huì)存在返回一個(gè)被回收的bitmap的bug九杂,那么最有可能的應(yīng)該是Glide自動(dòng)回收了bitmap(討論Glide到底應(yīng)不應(yīng)該自動(dòng)回收bitmap
)
Glide 加載bitmap 模擬代碼。
val target = Glide.with(this)
.asBitmap()
.load("https://pics6.baidu.com/feed/8718367adab44aedf2d6d8842904c104a08bfbd0.jpeg?token=65aad28f9a5456d50e9e0b19bb19f2c1&s=3B994587400DC9431003B4EE0300F019")
.into(object : SimpleTarget<Bitmap>() {
override fun onLoadFailed(errorDrawable: Drawable?) {
LogUtils.logd("失敗")
}
override fun onResourceReady(
resource: Bitmap,
transition: Transition<in Bitmap>?
) {
bitmap = resource
iv_test.setImageBitmap(resource)
}
})
Glide在內(nèi)存緊張的時(shí)候會(huì)調(diào)用 clearMemory()
方法
public void clearMemory() {
// Engine asserts this anyway when removing resources, fail faster and consistently
Util.assertMainThread();
// memory cache needs to be cleared before bitmap pool to clear re-pooled Bitmaps too. See #687.
memoryCache.clearMemory();
bitmapPool.clearMemory();
arrayPool.clearMemory();
}
而bitmapPool
的clearMemory()
方法中(實(shí)際使用的是LruBitmapPool
)遍歷了緩存池中的bitmap宣蠕,進(jìn)行了removed.recycle();
操作例隆,頁面進(jìn)行重繪的時(shí)候引起上述bug
private synchronized void trimToSize(int size) {
while (currentSize > size) {
final Bitmap removed = strategy.removeLast();
// TODO: This shouldn't ever happen, see #331.
if (removed == null) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Size mismatch, resetting");
dumpUnchecked();
}
currentSize = 0;
return;
}
tracker.remove(removed);
currentSize -= strategy.getSize(removed);
evictions++;
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed));
}
dump();
removed.recycle();
}
}
bitmap 又是在什么時(shí)機(jī)進(jìn)入bitmaPool
中的呢?
首先加載圖片的請(qǐng)求會(huì)被包裝成SingleRequest
,當(dāng)生命周期結(jié)束會(huì)調(diào)用
public synchronized void clear() {
assertNotCallingCallbacks();
stateVerifier.throwIfRecycled();
if (status == Status.CLEARED) {
return;
}
cancel();
// Resource must be released before canNotifyStatusChanged is called.
if (resource != null) {
releaseResource(resource);
}
if (canNotifyCleared()) {
target.onLoadCleared(getPlaceholderDrawable());
}
status = Status.CLEARED;
}
進(jìn)行釋放資源抢蚀,將圖片緩存到memoryCache
中镀层,memoryCache
的具體實(shí)現(xiàn)類為LruResourceCache
。緩存代碼在Engine
類的onResourceReleased
方法中皿曲。
@Override
public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
activeResources.deactivate(cacheKey);
if (resource.isCacheable()) {
cache.put(cacheKey, resource);
} else {
resourceRecycler.recycle(resource);
}
}
當(dāng)系統(tǒng)內(nèi)存緊張或主動(dòng)調(diào)用Glide.get(this).clearMemory()
時(shí)唱逢,首先會(huì)回調(diào)Engine
類的onResourceRemoved
方法,最終走到ResourceRecycler
類的
synchronized void recycle(Resource<?> resource) {
if (isRecycling) {
// If a resource has sub-resources, releasing a sub resource can cause it's parent to be
// synchronously evicted which leads to a recycle loop when the parent releases it's children.
// Posting breaks this loop.
handler.obtainMessage(ResourceRecyclerCallback.RECYCLE_RESOURCE, resource).sendToTarget();
} else {
isRecycling = true;
resource.recycle();
isRecycling = false;
}
}
resource
實(shí)際是BitmapResource
的實(shí)現(xiàn)類,recycle()
方法中將bitmap放入bitmapPool
中屋休。
@Override
public void recycle() {
bitmapPool.put(bitmap);
}
順序執(zhí)行Glide.get(this).clearMemory()
坞古,進(jìn)行bitmapPool
清空操作,回收bitmap劫樟,引發(fā)崩潰痪枫。
模擬復(fù)現(xiàn)代碼
val target = Glide.with(this)
.asBitmap()
.load("https://pics6.baidu.com/feed/8718367adab44aedf2d6d8842904c104a08bfbd0.jpeg?token=65aad28f9a5456d50e9e0b19bb19f2c1&s=3B994587400DC9431003B4EE0300F019")
.into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(
resource: Bitmap,
transition: Transition<in Bitmap>?
) {
bitmap = resource
iv_test.setImageBitmap(resource)
}
})
iv_test.postDelayed({
Glide.with(this).clear(target)
iv_test.postDelayed({
Glide.get(this).clearMemory()
iv_test.postDelayed({
LogUtils.logd(bitmap?.isRecycled)
iv_test.invalidate()
}, 3000)
}, 3000)
}, 3000)
解決
- 升級(jí) Glide到
4.9.0
版本织堂,使用CustomTarget
,onLoadCleared
方法會(huì)在資源回收時(shí)進(jìn)行回調(diào),這個(gè)時(shí)候的bitmap實(shí)際上已經(jīng)不是安全的了奶陈∫籽簦回調(diào)參見SingleRequest
類的clear()
方法的target.onLoadCleared(getPlaceholderDrawable());
這一行代碼
Glide.with(this)
.asBitmap()
.load("")
.into(object : CustomTarget<Bitmap>(){
override fun onLoadCleared(placeholder: Drawable?) {
}
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
}
})
- 加載bitmap成功之后,不使用加載出來的bitmap吃粒,使用copy出來的新的bitmap返回
(copy方法有可能返回null,config也有可能為空)
var copy = resource.copy(resource.config, true)
if (copy == null){
copy = resource
}