Glide 的坑沟蔑,Canvas: trying to use a recycled bitmap android.graphics.Bitmap

起因

我司軟件之前一直使用自定義的圖片加載器湿诊,為了順應(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();
  }

bitmapPoolclearMemory()方法中(實(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
}

還有其他解決方法的歡迎分享

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末潦俺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子徐勃,更是在濱河造成了極大的恐慌事示,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡毅待,警方通過查閱死者的電腦和手機(jī)拂到,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來法挨,“玉大人谁榜,你說我怎么就攤上這事》材桑” “怎么了窃植?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荐糜。 經(jīng)常有香客問我巷怜,道長,這世上最難降的妖魔是什么暴氏? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任延塑,我火速辦了婚禮,結(jié)果婚禮上答渔,老公的妹妹穿的比我還像新娘关带。我一直安慰自己,他們只是感情好沼撕,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布宋雏。 她就那樣靜靜地躺著,像睡著了一般务豺。 火紅的嫁衣襯著肌膚如雪磨总。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天笼沥,我揣著相機(jī)與錄音蚪燕,去河邊找鬼娶牌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛邻薯,可吹牛的內(nèi)容都是我干的裙戏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼厕诡,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼累榜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灵嫌,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤壹罚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后寿羞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猖凛,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年绪穆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辨泳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡玖院,死狀恐怖菠红,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情难菌,我是刑警寧澤试溯,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站郊酒,受9級(jí)特大地震影響遇绞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜燎窘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一摹闽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荠耽,春花似錦钩骇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至慢叨,卻和暖如春纽匙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拍谐。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工烛缔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留馏段,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓践瓷,卻偏偏與公主長得像院喜,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晕翠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容