源碼解析:Glide 4.9之緩存策略

本文Glide源碼基於4.9,版本下載地址以下:Glide 4.9java

前言

在分析了Glide的圖片加載流程後,更加發覺到Glide的強大,因而這篇文章將繼續深刻分析Glide的緩存策略。不過今天的文章的源碼不少基於上一篇加載流程的基礎之上,所以尚未看上一篇的小夥伴,建議先去閱讀Glide4.9源碼解析-圖片加載流程效果會更佳喲!git

1、設計

1. 二級緩存

  • 內存緩存:基於LruCache和弱引用機制
  • 磁盤緩存:基於DiskLruCache進行封裝

Glide有幾級緩存?對於這個問題,網上的答案不一,有的認爲是五級緩存,也有的認爲是三級緩存,但我我的認爲是二級緩存,由於我的感受網絡加載並不屬於緩存(若是有錯誤,歡迎在評論指出)github

2. 緩存策略

內存緩存-->磁盤緩存-->網絡加載算法

Glide的緩存策略大體是這樣的:假設同時開啓了內存緩存和磁盤緩存,當程序請求獲取圖片時,首先從內存中獲取,若是內存沒有就從磁盤中獲取,若是磁盤中也沒有,那就從網絡上獲取這張圖片。當程序第一次從網絡加載圖片後,就將圖片緩存到內存和磁盤上。緩存

2、流程

1. 生成緩存key

1.1 做用

緩存key是實現內存和磁盤緩存的惟一標識markdown

1.2 原理

重寫equals和hashCode方法,來確保只有key對象的惟一性網絡

1.3 源碼解析

生成緩存key的地方其實就在於咱們上一篇文章提到的過的Engine的load方法中app

Engine#load框架

public synchronized <R> LoadStatus load( GlideContext glideContext, Object model, Key signature, int width, int height, Class<?> resourceClass, Class<R> transcodeClass, Priority priority, DiskCacheStrategy diskCacheStrategy, Map<Class<?>, Transformation<?>> transformations, boolean isTransformationRequired, boolean isScaleOnlyOrNoTransform, Options options, boolean isMemoryCacheable, boolean useUnlimitedSourceExecutorPool, boolean useAnimationPool, boolean onlyRetrieveFromCache, ResourceCallback cb, Executor callbackExecutor) {
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

	//建立EngineKey對象
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
    ......
    return new LoadStatus(cb, engineJob);
  }
複製代碼

這裏會調用keyFactory的buildkey方法來建立EngineKey對象,並將load傳入的數據(String,URL等),圖片的寬高,簽名,設置參數等傳進去。異步

KeyFactory#buildKey

EngineKey buildKey(Object model, Key signature, int width, int height, Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass, Class<?> transcodeClass, Options options) {
    return new EngineKey(model, signature, width, height, transformations, resourceClass,
        transcodeClass, options);
  }
複製代碼

能夠發現KeyFactory的buildKey方法就是簡單的返回了一個EngineKey對象。因此咱們來看看這個EngineKey

EngineKey

class EngineKey implements Key {
  private final Object model;
  private final int width;
  private final int height;
  private final Class<?> resourceClass;
  private final Class<?> transcodeClass;
  private final Key signature;
  private final Map<Class<?>, Transformation<?>> transformations;
  private final Options options;
  private int hashCode;

  EngineKey(
      Object model,
      Key signature,
      int width,
      int height,
      Map<Class<?>, Transformation<?>> transformations,
      Class<?> resourceClass,
      Class<?> transcodeClass,
      Options options) {
    this.model = Preconditions.checkNotNull(model);
    this.signature = Preconditions.checkNotNull(signature, "Signature must not be null");
    this.width = width;
    this.height = height;
    this.transformations = Preconditions.checkNotNull(transformations);
    this.resourceClass =
        Preconditions.checkNotNull(resourceClass, "Resource class must not be null");
    this.transcodeClass =
        Preconditions.checkNotNull(transcodeClass, "Transcode class must not be null");
    this.options = Preconditions.checkNotNull(options);
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof EngineKey) {
      EngineKey other = (EngineKey) o;
      return model.equals(other.model)
          && signature.equals(other.signature)
          && height == other.height
          && width == other.width
          && transformations.equals(other.transformations)
          && resourceClass.equals(other.resourceClass)
          && transcodeClass.equals(other.transcodeClass)
          && options.equals(other.options);
    }
    return false;
  }

  @Override
  public int hashCode() {
    if (hashCode == 0) {
      hashCode = model.hashCode();
      hashCode = 31 * hashCode + signature.hashCode();
      hashCode = 31 * hashCode + width;
      hashCode = 31 * hashCode + height;
      hashCode = 31 * hashCode + transformations.hashCode();
      hashCode = 31 * hashCode + resourceClass.hashCode();
      hashCode = 31 * hashCode + transcodeClass.hashCode();
      hashCode = 31 * hashCode + options.hashCode();
    }
    return hashCode;
  }
  ....
}

複製代碼

你會發如今EngineKey中重寫equals和hashcode方法,這樣就能確保只有傳入EngineKey的全部參數都相同的狀況下才認爲是同一個EngineKey對象。

2. 內存緩存

2.1 做用

防止應用重複將圖片數據讀取到內存

2.2 原理

緩存原理:弱引用機制LruCache算法

弱引用機制:當JVM進行垃圾回收時,不管當前的內存是否足夠,都會回收掉弱引用關聯的對象

LruCache算法:內部採用LinkedHashMap以強引用的方式存儲外界的緩存對象,當緩存滿時,LruCache會移除較早使用的緩存對象,而後添加新的緩存對象

緩存實現:正在使用的圖片使用弱引用機制進行緩存,不在使用中的圖片使用LruCache來進行緩存。

2.3 配置

Glide默認狀況下是開啓了內存緩存的,即你不須要作任何處理,只須要經過下面代碼正常調用Glide的三部曲便可。

Glide.with(getContext()).load(url).into(imageView);
複製代碼

那咱們要關閉內存緩存咋整呢?強大的Glide固然思考了這種問題,Glide提供了很是便捷的API,所以咱們只須要經過調用來RequestOptions.skipMemoryCacheOf()並傳入true,表示跳過內存緩存,即禁用內存緩存。

Glide.with(getContext())
        .load(url)
        .apply(RequestOptions.skipMemoryCacheOf(true))
        .into(imageView);
複製代碼

2.4 源碼解析

在前面咱們提到了兩種類型的內存緩存,那麼Glide是如何協調二者的呢?讓咱們繼續回到Engine的load方法

Engine#load

public synchronized <R> LoadStatus load( ...) {
	//建立EngineKey對象
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

    //檢查內存的弱引用緩存是否有目標圖片
	EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }
    //檢查內存的Lrucache緩存是否有目標圖片
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }
    .....  
  }
複製代碼

在上面的方法中咱們能夠發現首先根據寬高,圖片URL地址等生成key,而後根據key首先調用loadFromActiveResources獲取內存的弱引用緩存的圖片,若是獲取不到弱引用緩存的圖片,才調用loadFromCache獲取內存的LruCache緩存。所以咱們先來看看Glide對弱引用緩存的操做。

1. 弱引用緩存

1.1 獲取

從上面Engine的load方法中,咱們知道獲取弱引用緩存會調用Engine的loadFromActiveResources方法

Engine#loadFromActiveResources

private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
	//重點關注get方法,從弱引用中拿數據
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
	  //將EngineResource的引用計數加1
      active.acquire();
    }

    return active;
  }
複製代碼

這裏首先會調用ActiveResources的get方法獲取到圖片資源,而後將EngineResource的引用計數加一,由於此時EngineResource指向了弱引用緩存的圖片資源。

ActiveResources#get

final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>(); 
 synchronized EngineResource<?> get(Key key) {
    //從弱引用HashMap中取出對應的弱引用對象
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }
    EngineResource<?> active = activeRef.get();
	//若是弱引用中關聯的EngineResource對象不存在,即EngineResourse被回收
    if (active == null) {
      //清理弱引用緩存,恢復EngineResource,並保存到LruCache緩存中 
      cleanupActiveReference(activeRef);
    }
    return active;
  }
複製代碼

在ActiveResources中的get中,會經過activeEngineResources的get方法獲得了數據的弱引用對象,而這個activeEngineResources其實就是個HashMap,因此能夠根據key值來找到這個弱引用對象。而咱們要找的圖片資源其實就是這個弱引用對象關聯的對象,讓咱們來看看ResourceWeakReference的實現

static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
    @SuppressWarnings("WeakerAccess") @Synthetic final Key key;
    @SuppressWarnings("WeakerAccess") @Synthetic final boolean isCacheable;

    @Nullable @SuppressWarnings("WeakerAccess") @Synthetic Resource<?> resource;

    @Synthetic
    @SuppressWarnings("WeakerAccess")
    ResourceWeakReference(
        @NonNull Key key,
        @NonNull EngineResource<?> referent,
        @NonNull ReferenceQueue<? super EngineResource<?>> queue,
        boolean isActiveResourceRetentionAllowed) {
      super(referent, queue);
      this.key = Preconditions.checkNotNull(key);
      this.resource =
          referent.isCacheable() && isActiveResourceRetentionAllowed
              ? Preconditions.checkNotNull(referent.getResource()) : null;
      isCacheable = referent.isCacheable();
    }
    ....
  }
}

複製代碼

能夠發現這個類其實繼承了WeakReference,因此當gc發生時,會回收掉ResourceWeakReference對象關聯的EngineResource對象,這個對象封裝了咱們所要獲取的圖片資源。另外這個類還保存了圖片資源和圖片資源的緩存key,這是爲了當關聯的EngineResource對象被回收時,能夠利用保存的圖片資源來恢復EngineResource對象,而後保存到LruCache緩存中並根據key值從HashMap中刪除掉關聯了被回收的EngineResource對象的弱引用對象。能夠看下EngineResourse被回收的狀況

ActiveResources#cleanupActiveReference

void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
    synchronized (listener) {
      synchronized (this) {
        //將該弱引用緩存從HashMap中刪除
        activeEngineResources.remove(ref.key);
        //判斷緩存是否可用
        //isCacheable默認狀況下爲true
        //當配置中設置了RequestOptions.skipMemoryCacheOf()的值的話:
        //1.當skipMemoryCacheOf傳入true時爲false,即關閉內存緩存
        //2.當skipMemoryCacheOf傳入false時爲true,即開啓內存緩存
        if (!ref.isCacheable || ref.resource == null) {
          return;
        }
		//恢復EngineResource,其中這個對象封裝了圖片資源
        EngineResource<?> newResource =
            new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false);
        newResource.setResourceListener(ref.key, listener);
		//回調,該listener爲Engine對象
        listener.onResourceReleased(ref.key, newResource);
      }
    }
  }
複製代碼

Engine#onResourceReleased

public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    //刪除弱引用緩存
    activeResources.deactivate(cacheKey);
	//若是開啓了內存緩存
    if (resource.isCacheable()) {
	  //將弱引用緩存的數據緩存到LruCache緩存中 
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }

複製代碼

這樣當弱引用緩存所關聯的圖片資源被回收時,會將圖片資源保存到LruCache緩存中,從而保證了當獲取不到弱引用緩存的圖片時,能夠獲取的到該圖片的LruCache緩存。

1.2 存儲

弱引用緩存的存儲體如今了兩個地方:

  • 在主線程展現圖片前
  • 獲取LruCache緩存時

下面將對這兩個地方分別進行解剖!

在主線程展現圖片前存儲

在講弱引用緩存的存儲前,咱們首先要明白這個弱引用緩存保存到圖片資源究竟是圖片的原始數據(圖片輸入流)仍是轉換後的圖片資源,搞明白的話,找到弱引用存儲的地方就不是問題了。這裏我就再也不細講如何搞明白這個問題,其中一個思路就是從Engine的load方法中獲取到弱引用緩存的操做入手,即回調入手。

//檢查內存弱引用緩存是否有目標圖片
	EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      //此時回調的是SingleRequest的onResourceReady方法
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }
複製代碼

這個方法其實在上篇文章圖片加載流程也提到過,而後追蹤下去你就會發現其實最後就是展現這個圖片資源,所以咱們能夠肯定這個圖片資源應該就是轉換後的圖片,因此存儲弱引用緩存應該是在轉換圖片後的操做。(這裏我直接講出存儲所在的位置,若是想本身深究能夠看看上篇文章圖片加載流程中的「在主線程中顯示圖片」這個步驟的代碼)最後咱們會發如今EngineJob的notifyCallbacksOfResult方法中找到了弱引用緩存入口

EngineJob#notifyCallbacksOfResult

void notifyCallbacksOfResult() {
    ResourceCallbacksAndExecutors copy;
    Key localKey;
    EngineResource<?> localResource;
    .....
    //內部緩存存儲的入口
    //實際上調用的是Engine的onEngineJobComplete
    listener.onEngineJobComplete(this, localKey, localResource);

    for (final ResourceCallbackAndExecutor entry : copy) {
	  //回到主線程展現照片
      entry.executor.execute(new CallResourceReady(entry.cb));
    }

	//通知上層刪除弱引用緩存數據
    decrementPendingCallbacks();
  }


複製代碼

沒錯其入口就是咱們在Glide圖片加載流程提到過的回到主線程展現照片代碼的前面,即回調了Engine的onEngineJobComplete來存儲弱引用緩存

Engine#onEngineJobComplete

public synchronized void onEngineJobComplete( EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    if (resource != null) {
      resource.setResourceListener(key, this);
      //若是開啓內存緩存的話,將解析後的圖片添加到弱引用緩存
      if (resource.isCacheable()) {
        activeResources.activate(key, resource);
      }
    }

    jobs.removeIfCurrent(key, engineJob);
  }
複製代碼

ActiveResources#activate

synchronized void activate(Key key, EngineResource<?> resource) {
    //構建弱引用對象
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
    //將獲取到的緩存圖片存儲到弱引用對象的HashMap中
    //key值不重複返回null,key值重複返回舊對象
    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      //若是key值重複,就將以前的弱引用對象的圖片資源置爲null 
      removed.reset();
    }
  }
複製代碼

從這裏也能夠獲得一個結論:正在使用的圖片會存儲到弱引用緩存中而不是LruCache緩存

獲取LruCache緩存時存儲

因爲這個操做同時也涉及了LruCache的獲取,故能夠直接看下面對LruCache獲取的解析

1.3 刪除

弱引用緩存的刪除其實體如今兩處:

  • JVM進行GC時
  • 弱引用緩存對象引用計數爲0時

JVM進行GC時

當JVM進行GC時,因爲弱引用對象的特性,致使了弱引用緩存所關聯的對象也會被回收,而後就會刪除掉這個弱引用緩存對象,這部分咱們在弱引用緩存獲取的時候也分析過,這裏再也不進行解析(忘記的能夠回頭看看前面弱引用獲取的分析)。

弱引用緩存對象引用計數爲0時

細心的你不知有沒有發現,其實在上面對緩存入口的分析時其實已經貼出了弱引用緩存刪除的代碼語句。不過爲了方便閱讀,在這裏我仍是再次直接貼出來。

EngineJob#notifyCallbacksOfResult

//內部緩存存儲的入口
    //實際上調用的是Engine的onEngineJobComplete
    listener.onEngineJobComplete(this, localKey, localResource);

    for (final ResourceCallbackAndExecutor entry : copy) {
	  //回到主線程展現照片
      entry.executor.execute(new CallResourceReady(entry.cb));
    }

	//通知上層刪除弱引用緩存數據
    decrementPendingCallbacks();
複製代碼

EngineJob#decrementPendingCallbacks

synchronized void decrementPendingCallbacks() {
    .....
    if (decremented == 0) {
      if (engineResource != null) {
	  	//重點關注
        engineResource.release();
      }
      release();
    }
  }
複製代碼

這裏調用了EngineResource的release方法,讓咱們來看看

EngineResource#release

void release() {
    synchronized (listener) {
      synchronized (this) {
        if (acquired <= 0) {
          throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
        }
		//每次調用release內部引用計數法減一,當爲0時,表明沒引用,通知上層回收
        if (--acquired == 0) {
		  //回調,listener爲Engine類型 
          listener.onResourceReleased(key, this);
        }
      }
    }
  }
複製代碼

在這裏使用了著名的判斷對象是否存活的算法-引用計數法,每次調用EngineResource對象的release方法,都會令該引用減一,當引用計數爲0時,表示已經再也不使用該對象,即圖片再也不使用時,就會回調Engine的onResourceReleased方法

Engine#onResourceReleased

public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    //刪除弱引用緩存
    activeResources.deactivate(cacheKey);
	//若是開啓了內存緩存
    if (resource.isCacheable()) {
	  //將弱引用緩存的數據緩存到LruCache緩存中 
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }
複製代碼

跟上面存儲弱引用緩存時提到的發生GC的狀況同樣,最終會刪除弱引用緩存,而後將該圖片資源添加到LruCache緩存中。從這裏也能夠驗證了咱們上文提到的內存緩存的原理中的緩存實現:正在使用的圖片使用弱引用機制進行緩存,不在使用中的圖片使用LruCache來進行緩存。

2. LruCache緩存

2.1 獲取

上文咱們提到,獲取內存緩存時,若是獲取不到弱引用緩存時纔會調用loadFromCache獲取LruCache緩存。讓咱們看看Engine的loadFromCache方法

Engine#loadFromCache

private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
     //isMemoryCacheable默認狀況下爲true
     //當配置中設置了RequestOptions.skipMemoryCacheOf()的值的話:
     //1.當skipMemoryCacheOf傳入true時爲false,即關閉內存緩存
     //2.當skipMemoryCacheOf傳入false時爲true,即開啓內存緩存
    if (!isMemoryCacheable) {
      return null;
    }
    //獲取圖片緩存,並將該緩存從LruCache緩存中刪除
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
	  //將EngineResource的引用計數加1 
      cached.acquire();
	  //將內存緩存存入弱引用緩存中
	  //好處:保護這些圖片不會被LruCache算法回收掉
      activeResources.activate(key, cached);
    }
    return cached;
  }
複製代碼

獲取LruCache緩存跟弱引用緩存的獲取操做很類似,首先調用了getEngineResourceFromCache來獲取圖片資源,而後將EngineResource的引用計數加1,而且還會將獲取到的圖片資源存儲到弱引用緩存中。這裏咱們只分析getEngineResourceFromCache方法,由於調用ActiveResource的activate存儲到弱引用緩存咱們已經在上面弱引用緩存的存儲中分析過了。

EngineResource#getEngineResourceFromCache

/** * 做用:獲取圖片緩存 * 過程:根據緩存key從cache中取值 * 注:此cache對象爲Glide構建時建立的LruResourceCache對象,說明使用的是LruCache算法 */
  private EngineResource<?> getEngineResourceFromCache(Key key) {
    //當獲取到緩存圖片時,從緩存中移除
    Resource<?> cached = cache.remove(key);

    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      result = (EngineResource<?>) cached;
    } else {
      result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
    }
    return result;
  }
複製代碼

在上面需注意的是該cache就是LruCache緩存的cache,另外你會發現獲取圖片緩存居然不是調用cache的get方法,而是cache的remove方法,這就是Glide緩存策略的奇妙之處了。當獲取到LruCache緩存的同時會刪除掉該LruCache緩存,而後將該緩存存儲到弱引用緩存中,這是爲了保護這些圖片不會被LruCache算法回收掉。

2.2 存儲

當弱引用緩存刪除時,會將緩存存儲到LruCache緩存中。(分析能夠看弱引用緩存刪除操做)

2.3 刪除

當獲取LruCache緩存的同時對該LruCache緩存進行刪除操做。(分析能夠看LruCache緩存的獲取操做)

2.5 小結

分析完內存緩存,你會發現弱引用緩存和LruCache緩存真的是環環相扣,密不可分,不少操做都是有關聯性的。其流程圖以下:

流程圖的前提:開啓內存緩存,關閉磁盤緩存

3. 磁盤緩存

3.1 做用

防止應用重複的從網絡或從其它地方下載和讀取數據

3.2 原理

使用Glide自定義的DiskLruCache算法

DiskLruCache算法是基於LruCache算法,該算法的應用場景是存儲設備的緩存,即磁盤緩存。

3.3 配置

磁盤緩存也是默認開啓的,默認狀況下磁盤緩存的類型爲DiskCacheStrategy.AUTOMATIC,固然能夠經過代碼關閉或者選擇其它類型的磁盤緩存

Glide.with(getContext())
        .load(url)
	    .apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.RESOURCE))
        .into(imageView);
複製代碼

diskCacheStrategyOf的相關參數說明以下:

參數 說明
DiskCacheStrategy.AUTOMATIC
這是默認的最優緩存策略:
本地:僅存儲轉換後的圖片(RESOURCE)
網絡:僅存儲原始圖片(DATA)。由於網絡獲取數據比解析磁盤上的數據要昂貴的多
DiskCacheStrategy.NONE 不開啓磁盤緩存
DiskCacheStrategy.RESOURCE 緩存轉換事後的圖片
DiskCacheStrategy.DATA 緩存原始圖片,即原始輸入流。它須要通過壓縮轉換,解析等操做才能最終展現出來
DiskCacheStrategy.ALL 既緩存原始圖片,又緩存轉換後的圖片

注:Glide加載圖片默認是不會將一張原始圖片展現出來的,而是將原始圖片通過壓縮轉換,解析等操做。而後將轉換後的圖片展現出來。

3.4 源碼解析

下列源碼解析的前提:開啓了磁盤緩存

雖說上面的參數有五種,但其實咱們只須要分析其中兩種就能理解其它參數了。沒錯,接下來咱們將分析RESOURCE類型和DATA類型。在分析前咱們先回顧下Engine的load方法

Engine#load

public synchronized <R> LoadStatus load( .....) {
      
      
     ......
	//建立EngineKey對象
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

    //檢查內存弱引用緩存是否有目標圖片
	EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }
    //檢查內存中Lrucache緩存是否有目標圖片
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }

    //走到這表示內存中沒有圖片,則開啓新線程加載圖片
	
	........
    //建立EngineJob對象,用來開啓線程(異步加載圖片)
    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);
    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);
    jobs.put(key, engineJob);
    engineJob.addCallback(cb, callbackExecutor);
    engineJob.start(decodeJob);
    return new LoadStatus(cb, engineJob);
  }

複製代碼

從上面能夠發現若是獲取不到內存緩存時,會開啓線程來加載圖片。從上篇文章Glide 4.9源碼解析-圖片加載流程咱們能夠知道,接下來會執行DecodeJob的run方法。

DecodeJob

public void run() {
      .....
	  //重點關注,調用runWrapped
      runWrapped();
  }

  private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
	  	//獲取任務場景
        stage = getNextStage(Stage.INITIALIZE);
	    //獲取這個場景的執行者
        currentGenerator = getNextGenerator();
	    //執行者執行任務
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }

  private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
	  	//獲取轉換後圖片的執行者
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
	  	//獲取原始圖片的執行者
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
	  	// 無緩存, 網絡獲取數據的執行者
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }
複製代碼

能夠發如今上述的方法中首先要找到對於場景的執行者而後執行任務。而執行者有三個,在上篇文章咱們分析的是無緩存的狀況,即網絡獲取數據的執行者。接下來咱們就得分析獲取轉換後圖片的執行者和獲取原始突破的執行者。

1. RESOURCE緩存(轉換圖片)

1.1 獲取

因爲咱們如今配置的緩存策略爲RESOURCE,故對於執行者將是獲取轉換圖片的執行者ResourceCacheGenerator,接下來會執行者會執行任務,讓咱們看看runGenerators方法

Engine#runGenerators

private void runGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
	// 調用 currentGenerator.startNext() 執行了請求操做
	//咱們這裏主要分析的是無緩存狀況,因此這裏的currentGenerator應該是ResourceCacheGenerator
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();

      if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }
    }
複製代碼

由於咱們如今的執行者爲ResourceCacheGenerator,因此會調用ResourceCacheGenerator的startNext來進行獲取圖片。

ResourceCacheGenerator#startNext

public boolean startNext() {
    ....
    while (modelLoaders == null || !hasNextModelLoader()) {
      .....

      Key sourceId = sourceIds.get(sourceIdIndex);
      Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
      Transformation<?> transformation = helper.getTransformation(resourceClass);
      //構建獲取緩存數據的key,這個key中傳入了圖片大小,變換等參數
      //即根據各類變換的條件獲取緩存數據,故這個執行者就是用來獲取變換以後的緩存數據
      currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
	  //從緩存中獲取緩存信息
	  //首先經過getDiskCache獲取DiskCache對象
	  //而後經過key獲取到轉換事後的資源
      cacheFile = helper.getDiskCache().get(currentKey);
      if (cacheFile != null) {
        sourceKey = sourceId;
		//該modeLoaders的類型爲File類型的
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
	  //獲取數據加載器
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
	  //構建一個加載器,構建出來的是ByteBufferFileLoader
      loadData = modelLoader.buildLoadData(cacheFile,
          helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
		//調用了ByteBufferFileLoader的內部類ByteBufferFetcher的loadData
		//最後會把結果回調給DecodeJob的onDataFetcherReady
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }

    return started;
  }
複製代碼

能夠發如今ResourceCacheGenerator的startNext方法中,首先根據圖片的參數,寬高等信息拿到緩存key,而後經過緩存key獲取到磁盤上的文件,即磁盤緩存。最後經過加載器的loadData來處理獲取到的磁盤緩存,因爲磁盤緩存是File類型,根據Glide的註冊表registry中能夠找到該加載器實際上是ByteBufferFileLoader(具體查找可看上篇博客的分析),故最後會調用ByteBufferFileLoader內部類ByteBufferFetcher的loadData方法來處理磁盤緩存。

ByteBufferFileLoader.ByteBufferFetcher#loadData

public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) {
      ByteBuffer result;
      try {
	  	//獲取對應磁盤上的轉換事後的數據
        result = ByteBufferUtil.fromFile(file);
      } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to obtain ByteBuffer for file", e);
        }
        callback.onLoadFailed(e);
        return;
      }
	  //此callback爲ResourceCacheGenerator 
      callback.onDataReady(result);
    }
複製代碼

在loadData中會經過ByteBufferUtil工具類來獲取對應磁盤文件的數據,而後經過回調ResourceCacheGenerator的onDataReady方法將數據回調出去。

ResourceCacheGenerator#onDataReady

public void onDataReady(Object data) {

    //此時的cb爲DecodeJob,即調用了DecodeJob的onDataFetcherReady
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE,
        currentKey);
  }
複製代碼

進行回調DecodeJob的onDataFetcherReady

DecodeJob#onDataFetcherReady

public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {
    //將各類值賦值給成員變量
    this.currentSourceKey = sourceKey;
    this.currentData = data;
    this.currentFetcher = fetcher;
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      GlideTrace.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
	  	//解析獲取的數據
        decodeFromRetrievedData();
      } finally {
        GlideTrace.endSection();
      }
    }
  }
複製代碼

接下來就會對數據進行壓縮轉換等操做,而後進行展現(在上篇文章已經分析,這裏再也不進行後續的分析)。

1.2 存儲

因爲咱們分析的是轉換後的圖片的存儲,故其存儲位置應該是在對原始圖片壓縮轉換解析等一系列操做完後進行的,根據上一篇文章的分析,咱們直接看DecodeJob的decodeFromRetrievedData方法

DecodeJob

private void decodeFromRetrievedData() {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Retrieved data", startFetchTime,
          "data: " + currentData
              + ", cache key: " + currentSourceKey
              + ", fetcher: " + currentFetcher);
    }
    Resource<R> resource = null;
    try {
	  //轉換後的圖片資源
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if (resource != null) {
	  //通知外界資源獲取成功 
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      runGenerators();
    }
  }

  private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
    .....
    //圖片加載流程時重點關注的地方
    notifyComplete(result, dataSource);

    stage = Stage.ENCODE;
    try {
	  //是否能夠將轉換的圖片緩存
      if (deferredEncodeManager.hasResourceToEncode()) {
	  	 //磁盤緩存入口 
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
      if (lockedResource != null) {
        lockedResource.unlock();
      }
    }
    onEncodeComplete();
  }
複製代碼

咱們在通知外界資源獲取成功即notifyEncodeAndRelease方法中發現了RESOURCE類型的磁盤緩存的入口。

DecodeJob.DeferredEncodeManager#encode

void encode(DiskCacheProvider diskCacheProvider, Options options) {
      GlideTrace.beginSection("DecodeJob.encode");
      try {
	  	//將圖片資源緩存到資源磁盤
        diskCacheProvider.getDiskCache().put(key,
            new DataCacheWriter<>(encoder, toEncode, options));
      } finally {
        toEncode.unlock();
        GlideTrace.endSection();
      }
    }
複製代碼

在encode方法中經過DiskCacheProvider獲取到DiskCache,而後調用put方法將圖片資源緩存到磁盤上。

1.3 刪除

因爲緩存在了磁盤上,故刪除不只僅由代碼控制。常見的刪除方式以下:

  • 用戶主動刪除手機上的對應文件
  • 卸載軟件
  • 手動調用Glide.get(applicationContext).clearDiskCache()

2. DATA緩存(原始圖片)

2.1 獲取

經過上面的分析咱們知道原始圖片對應的執行者爲DataCacheGenerator,故仍是會調用DataCacheGenerator的startNext方法來獲取磁盤緩存

DataCacheGenerator#startNext

public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      .....
      Key sourceId = cacheKeys.get(sourceIdIndex);
      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
      //構建緩存key 
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      //獲取緩存key的磁盤資源
      cacheFile = helper.getDiskCache().get(originalKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      loadData =
          modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
              helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }
複製代碼

你會發現其實這個startNext方法與剛剛分析的ResourceCacheGenerator的startNext幾乎是同樣,不一樣的是緩存key的構建參數是不同的,由於原始圖片的緩存key是不須要圖片的寬高,配置,變換等參數。而後接下來的分析與轉換圖片獲取的分析是一致的,這裏再也不進行分析。

2.2 存儲

咱們知道原始圖片說白了就是網絡獲取後獲得的原始的輸入流,經過上一篇加載流程的分析,咱們知道獲取到原始輸入流是在HttpUrlFetcher的loadData方法中

HttpUrlFetcher#loadData

public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
	  //獲取網絡圖片的輸入流 
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
	  //將inputStream回調出去,回調了SourceGenerator的onDataReady
      callback.onDataReady(result);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }
複製代碼

在獲取到原始輸入流後,會調用SourceGenerator的onDataReady將輸入流回調出去

SourceGenerator#onDataReady

public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
	//若是開啓了磁盤緩存
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
	  //將網絡獲取到的原始數據,賦值給dataToCache 
      dataToCache = data;
      //調用DecodeJob的reschedule,用線程池執行任務,實際上就是再次調用SourceGenerator的startNext
      cb.reschedule();
    } else {
	  //沒有開啓磁盤緩存
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }
複製代碼

因爲咱們開啓了磁盤緩存,故會將原始數據賦值給dataToCache,而後回調了DecodeJob的reschedule。

DecodeJob#reschedule

public void reschedule() {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
	//此時的callback爲EngineJob
    callback.reschedule(this);
  }
複製代碼

這裏繼續回調了EngineJob的reschedule方法

EngineJob#reschedule

public void reschedule(DecodeJob<?> job) {
    //再次切換到網絡線程,執行DecodeJob的run方法
    getActiveSourceExecutor().execute(job);
  }
複製代碼

這裏的job爲DecodeJob,而getActiveSourceExecutor()會拿到線程池,因此reschedule方法其實會繼續執行DecodeJob的run方法,而後拿到網絡獲取數據的執行者SourceGenerator,再次執行SourceGenerator的startNext方法(考慮到篇幅,再也不貼其中的流程代碼了,詳細能夠看上篇文章Glide 4.9源碼解析-圖片加載流程

不過估計在這裏可能有人會疑問,咱們明明開啓了磁盤緩存,爲何會獲取到無緩存,網絡獲取數據的執行者呢?這是由於咱們在存儲原始圖片的前提下,確定是磁盤沒有緩存,所以會從網絡加載圖片獲得原始圖片的輸入流,而後回調,回調後固然仍是拿到網絡獲取數據的執行者SourceGenerator。讓咱們再來看看這個startNext方法。

SourceGenerator#startNext

public boolean startNext() {
     //第二次進入
    //如今dataToCache不等於null,爲原始圖片
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
	  //放入緩存 
      cacheData(data);
    }
    //當原始圖片放入磁盤緩存後,sourceCacheGenerator爲DataCacheGenerator
    //而後繼續執行DataCacheGenerator的startNext方法
    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
      
    //走到這,說明是沒有開啓磁盤緩存或獲取不到磁盤緩存的狀況下 
    sourceCacheGenerator = null;

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }
複製代碼

經過上面的分析咱們知道此時的dataToCache是不爲null的,而是原始圖片,因此會調用cacheData方法將原始圖片放入到磁盤緩存中。(若是你閱讀了Glide圖片加載流程的話,就會發現咱們在圖片加載流程的時候分析的實際上是下面的代碼,即沒有開啓緩存或獲取不到磁盤緩存的狀況)這裏咱們繼續看cacheData方法

SourceGenerator#cacheData

private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      //構建緩存key 
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());

	  //存儲原始圖片
      helper.getDiskCache().put(originalKey, writer);
      ......
    } finally {
      loadData.fetcher.cleanup();
    }
    //構造一個DataCacheGenerator對象,用來加載剛保存的磁盤緩存
    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }
複製代碼

在cacheData方法中會經過DiskCache的put方法將緩存key,原始圖片等存儲到磁盤中。而後會構造DataCacheGenerator對象,這時候咱們看回SourceGenerator的startNext方法,因爲此時的sourceCacheGenerator已是DataCacheGenerator對象了,因此會調用DataCacheGenerator的startNext方法來獲取磁盤緩存中的原始圖片。

2.3 刪除

因爲原始圖片的緩存也屬於磁盤緩存,故跟RESOURCE緩存同樣刪除不只僅由代碼控制,常見刪除方式以下:

  • 用戶主動刪除手機上的對應文件
  • 卸載軟件
  • 手動調用Glide.get(applicationContext).clearDiskCache()

3.5 小結

從上面的分析能夠發現Glide中首先會讀取轉換後的圖片的緩存,而後再讀取原始圖片的緩存。可是存儲的時候偏偏相反,首先存儲的是原始圖片的緩存,再存儲轉換後的圖片,不過獲取和存儲都受到Glide使用API的設置的影響。其流程圖以下:

流程圖的前提:關閉內存緩存或獲取不到內存緩存,開啓磁盤緩存

總結

經過對Glide緩存策略的分析,發現了Glide的緩存機制是多麼的複雜,但又是多麼的出色啊,因此用起來纔會這麼流暢和舒服。分析到這,Glide的緩存策略也就講完了,而對Glide這個強大的圖片開源庫的源碼分析也告一段落了,經過對Glide的圖片加載流程和緩存策略的源碼解析,讓我更加佩服Glide這個強大的開源庫。

參考文章:

相關文章
相關標籤/搜索