Glide 緩存模塊

說一下Glide緩存的大概流程


Glide會(huì)在開始一個(gè)新的圖片請(qǐng)求之前檢查以下多級(jí)緩存:

  • Active Resources: this image displayed in another View right now(該圖片正在被其它view展示)

  • Memory Cache: this image recently loaded and still in memory(該圖片最近被加載到內(nèi)存中并且還未釋放)

  • Resource: this image been decoded, transformed, and written to the disk cache before(該圖片已經(jīng)被解碼,轉(zhuǎn)換過,并且寫到了磁盤緩存中)

  • Data: the data this image was obtained from written to the disk cache before(該圖片的原始數(shù)據(jù)已經(jīng)被寫入磁盤緩存中)

ActiveResources

ActiveResources是第一級(jí)緩存。當(dāng)資源加載成功或者通過其它緩存命中,都會(huì)加到ActiveResources 中,當(dāng)資源釋放時(shí)再移除。ActiveResources 用一個(gè) Map 來保存資源的 WeakReference:

final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

使用 WeakReference 的原因是防止內(nèi)存泄漏。但由于是 WeakReference,因此隨時(shí)有可能被系統(tǒng)GC,因此ActiveResources還有一個(gè) ReferenceQueue 來跟蹤資源被 GC 的情況:

private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();

每當(dāng) activeEngineResources 中添加一個(gè) WeakReference 都會(huì)把它和這個(gè) ReferenceQueue 關(guān)聯(lián)起來,這個(gè)體現(xiàn)在 ActiveResources#activate 方法中:

  synchronized void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }

相關(guān)問題:

1. 具體說一下 ActiveResources 是怎樣利用 ReferenceQueue 去跟蹤 WeakReference 是否被GC 的?
先說一下為什么ReferenceQueue 可以跟蹤 WeakReference 的GC情況。
WeakReference 有一個(gè)包含2個(gè)參數(shù)的構(gòu)造方法:

    /**
     * Creates a new weak reference that refers to the given object and is
     * registered with the given queue.
     *
     * @param referent object the new weak reference will refer to
     * @param q the queue with which the reference is to be registered,
     *          or <tt>null</tt> if registration is not required
     */
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

其中一個(gè)就是 ReferenceQueue。這個(gè) ReferenceQueue 的作用在 WeakReference 這個(gè)類頂部的注釋中有描述:

/**
 * Weak reference objects, which do not prevent their referents from being
 * made finalizable, finalized, and then reclaimed.  Weak references are most
 * often used to implement canonicalizing mappings.
 *
 * <p> Suppose that the garbage collector determines at a certain point in time
 * that an object is <a href="package-summary.html#reachability">weakly
 * reachable</a>.  At that time it will atomically clear all weak references to
 * that object and all weak references to any other weakly-reachable objects
 * from which that object is reachable through a chain of strong and soft
 * references.  At the same time it will declare all of the formerly
 * weakly-reachable objects to be finalizable.  At the same time or at some
 * later time it will enqueue those newly-cleared weak references that are
 * registered with reference queues.
 *
 * @author   Mark Reinhold
 * @since    1.2
 */

第二段中提到,在發(fā)生GC的時(shí)候,garbage collector 會(huì)把該對(duì)象的所有 weak reference 清除,而這些被清除的 WeakReference 對(duì)象在GC的同時(shí)或者稍晚一點(diǎn)的時(shí)間點(diǎn)被加入到他們注冊(cè)的ReferenceQueue 中,也就是上面那個(gè)構(gòu)造函數(shù)中傳入的第二個(gè)參數(shù)。我們只需要遍歷 ReferenceQueue 就可以知道哪些WeakReference 已經(jīng)被GC了,然后從 activeEngineResources 中移除這些資源。

那么 ActiveResources 是在什么時(shí)機(jī)去檢查ReferenceQueue 的呢?實(shí)際上在ActiveResources 對(duì)象被創(chuàng)建的時(shí)候,會(huì)起一個(gè)線程專門去檢測(cè)是否有資源被GC:

ActiveResources(
      boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
    this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
    this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;

    monitorClearedResourcesExecutor.execute(
        new Runnable() {
          @Override
          public void run() {
            cleanReferenceQueue();
          }
        });
  }

  @Synthetic 
  void cleanReferenceQueue() {
    while (!isShutdown) {
      try {
        ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
        cleanupActiveReference(ref);

        // This section for testing only.
        DequeuedResourceCallback current = cb;
        if (current != null) {
          current.onResourceDequeued();
        }
        // End for testing only.
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }

  @Synthetic
  void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
    // Fixes a deadlock where we normally acquire the Engine lock and then the ActiveResources lock
    // but reverse that order in this one particular test. This is definitely a bit of a hack...
    synchronized (listener) {
      synchronized (this) {
        activeEngineResources.remove(ref.key);

        if (!ref.isCacheable || ref.resource == null) {
          return;
        }
        EngineResource<?> newResource =
            new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false);
        newResource.setResourceListener(ref.key, listener);
        listener.onResourceReleased(ref.key, newResource);
      }
    }
  }

這個(gè)線程中會(huì)調(diào)用 cleanReferenceQueue 方法,在這個(gè)方法中又調(diào)用了 cleanupActiveReference,就是在這個(gè)方法中,activeEngineResources 刪除已經(jīng)被GC 的資源。

2. Reference 的種類有哪些?各自的使用場景是什么?

LruBitmapPool

/**
 * An {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} implementation that uses an
 * {@link com.bumptech.glide.load.engine.bitmap_recycle.LruPoolStrategy} to bucket {@link Bitmap}s
 * and then uses an LRU eviction policy to evict {@link android.graphics.Bitmap}s from the least
 * recently used bucket in order to keep the pool below a given maximum size limit.
 */

Glide 自己定義了一個(gè) LruBitmapPool 對(duì)象來管理緩存的 Bitmap 對(duì)象。但是具體實(shí)現(xiàn)LRU算法的是 SizeConfigStrategy,它實(shí)現(xiàn)了 LruPoolStrategy 接口。

SizeConfigStrategy

/**
 * Keys {@link android.graphics.Bitmap Bitmaps} using both
 * {@link android.graphics.Bitmap#getAllocationByteCount()} and the
 * {@link android.graphics.Bitmap.Config} returned from
 * {@link android.graphics.Bitmap#getConfig()}.
 *
 * <p> Using both the config and the byte size allows us to safely re-use a greater variety of
 * {@link android.graphics.Bitmap Bitmaps}, which increases the hit rate of the pool and therefore
 * the performance of applications. This class works around #301 by only allowing re-use of
 * {@link android.graphics.Bitmap Bitmaps} with a matching number of bytes per pixel. </p>
 */

SizeConfigStrategy 利用一個(gè) GroupedLinkedMap 來存儲(chǔ) bitmap,這是一個(gè)Glide 自定義的類似 LinkedHashMap 的數(shù)據(jù)結(jié)構(gòu):

/**
 * Similar to {@link java.util.LinkedHashMap} when access ordered except that it is access ordered
 * on groups of bitmaps rather than individual objects. The idea is to be able to find the LRU
 * bitmap size, rather than the LRU bitmap object. We can then remove bitmaps from the least
 * recently used size of bitmap when we need to reduce our cache size.
 *
 * For the purposes of the LRU, we count gets for a particular size of bitmap as an access, even if
 * no bitmaps of that size are present. We do not count addition or removal of bitmaps as an
 * access.
 */

private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();

groupMap 的key的類型是 Key,是通過 Bitmap 的 size 和 config 組合生成的,這也是 SizeConfigStrategy 這個(gè)類名的由來。

GroupedLinkedMap 具體實(shí)現(xiàn)如下圖:


內(nèi)部實(shí)現(xiàn)主要包括3中數(shù)據(jù)結(jié)構(gòu):

  • List,用于存儲(chǔ)每個(gè)LinkedEntry中的數(shù)據(jù)
  • LinkedEntry,本質(zhì)是循環(huán)雙鏈表,用于記錄數(shù)據(jù)的順序
  • HashMap,用于快速找到對(duì)應(yīng)的 Entry

總結(jié)一下,BitmapPool 需要注意的有以下幾點(diǎn):

  • BitmapPool 大小通過 MemorySizeCalculator 設(shè)置;
  • 使用 LRU 算法維護(hù) BitmapPool ;
  • Glide 會(huì)根據(jù) Bitmap 的 size 和 Config 生成一個(gè) Key;
  • Key 也有自己對(duì)應(yīng)的對(duì)象池,使用 Queue 實(shí)現(xiàn);
  • 數(shù)據(jù)最終存儲(chǔ)在 GroupedLinkedMap 中;
  • GroupedLinkedMap 使用哈希表、循環(huán)鏈表、List 來存儲(chǔ)數(shù)據(jù)。

MemoryCache

在最上面的 Glide 緩存層級(jí)中提到過,MemoryCache 中存儲(chǔ)的是最近被加載到內(nèi)存中并且還未被釋放的資源。

MemoryCache 接口的實(shí)現(xiàn)類是 LruResourceCache,和 ActiveResources 的緩存策略一樣,也是采用LRU cache。具體實(shí)現(xiàn)就不贅述了。

磁盤緩存

磁盤緩存同樣采用LRU策略,它的實(shí)現(xiàn)類是 DiskLruCacheWrapper,看名字就知道這是一個(gè)包裝類,包裝的是 DiskLruCache。

DiskLruCache

首先想一個(gè)問題,磁盤緩存同樣采用LRU,那么Glide怎樣在APP啟動(dòng)的時(shí)候知道資源的使用頻率并給資源排序呢?

其實(shí)Glide 的做法是創(chuàng)建一個(gè)日志清單文件來保存這個(gè)順序。DiskLruCache 在 APP 第一次安裝時(shí)會(huì)在緩存文件夾下創(chuàng)建一個(gè) journal 日志文件來記錄圖片的添加、刪除、讀取等等操作,后面每次打開 APP 都會(huì)讀取這個(gè)文件,把其中記錄下來的緩存文件名讀取到 LinkedHashMap 中,后面每次對(duì)圖片的操作不僅是操作這個(gè) LinkedHashMap 還要記錄在 journal 文件中。

DiskLruCache 里有很多IO操作,就是因?yàn)樗斯芾砭彺嬷膺€要把對(duì)資源的操作記錄到日志清單文件中。

磁盤緩存的策略

Glide中定義的磁盤緩存策略有下面五種:

  • DiskCacheStrategy.ALL:原始圖片和轉(zhuǎn)換過的圖片都緩存
  • DiskCacheStrategy.RESOURCE:只緩存原始圖片
  • DiskCacheStrategy.NONE:不緩存
  • DiskCacheStrategy.DATA:只緩存使用過的圖片
  • DiskCacheStrategy.AUTOMATIC: 默認(rèn)策略,嘗試自動(dòng)為本地和遠(yuǎn)程圖片使用最佳的緩存策略。當(dāng)你加載遠(yuǎn)程數(shù)據(jù)(比如,從URL下載)時(shí),AUTOMATIC 策略僅會(huì)存儲(chǔ)未被你的加載過程修改過(比如,變換,裁剪–譯者注)的原始數(shù)據(jù),因?yàn)橄螺d遠(yuǎn)程數(shù)據(jù)相比調(diào)整磁盤上已經(jīng)存在的數(shù)據(jù)要昂貴得多。對(duì)于本地?cái)?shù)據(jù),AUTOMATIC 策略則會(huì)僅存儲(chǔ)變換過的縮略圖,因?yàn)榧词鼓阈枰俅紊闪硪粋€(gè)尺寸或類型的圖片,取回原始數(shù)據(jù)也很容易。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容