Android 圖片加載框架 Glide 4.9.0 (二) 從源碼的角度分析 Glide 緩存策略

前言

因爲以前項目搭建的是 MVP 架構,由RxJava + Glide + OKHttp + Retrofit 等開源框架組合而成,以前也都是停留在使用層面上,沒有深刻的研究,最近打算把它們所有攻下,尚未關注的同窗能夠先關注一波,看完這個系列文章,(不論是面試仍是工做中處理問題)相信你都在知道原理的狀況下,處理問題更加駕輕就熟。java

Android 圖片加載框架 Glide 4.9.0 (一) 從源碼的角度分析 Glide 執行流程面試

Android 圖片加載框架 Glide 4.9.0 (二) 從源碼的角度分析 Glide 緩存策略算法

從源碼的角度分析 Rxjava2 的基本執行流程、線程切換原理緩存

從源碼的角度分析 OKHttp3 (一) 同步、異步執行流程性能優化

從源碼的角度分析 OKHttp3 (二) 攔截器的魅力markdown

從源碼的角度分析 OKHttp3 (三) 緩存策略網絡

從源碼的角度分析 Retrofit 網絡請求,包含 RxJava + Retrofit + OKhttp 網絡請求執行流程架構

介紹

在上一篇中,咱們知道了 Glide 框架的最基本的執行流程,那麼只知道基本執行流程,這顯然是不夠的,咱們要深挖 Glide 框架的細節處理原理,好比緩存機制,圖片處理等,這一篇咱們就一塊兒去探索 Glide 的緩存機制。app

Glide 緩存機制能夠說是設計的很是完美,考慮的很是周全,下面就以一張表格來講明下 Glide 緩存。框架

緩存類型 緩存表明 說明
活動緩存 ActiveResources 若是當前對應的圖片資源是從內存緩存中獲取的,那麼會將這個圖片存儲到活動資源中。
內存緩存 LruResourceCache 圖片解析完成並最近被加載過,則放入內存中
磁盤緩存-資源類型 DiskLruCacheWrapper 被解碼後的圖片寫入磁盤文件中
磁盤緩存-原始數據 DiskLruCacheWrapper 網絡請求成功後將原始數據在磁盤中緩存

若是對 Glide 執行流程不明白的能夠先看 Android 圖片加載框架 Glide 4.9.0 (一) 從源碼的角度分析一次最簡單的執行流程

在介紹緩存原理以前,先來看一張加載緩存執行順序,先有個印象。

uZ8gmR.png

緩存 key 生成

不論是內存緩存仍是磁盤緩存,存儲的時候確定須要一個惟一 key 值,那麼 Glide cache key 是怎麼生成的?經過上一篇源碼加載流程介紹,咱們知道在 Engine 的 load 函數中進行對 key 的生成。下面咱們就經過代碼來看一下。

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
      
      ...
        
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) {
       ....
     //1. 生成緩存惟一 key 值,model 就是圖片地址
     EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);  
      
       ....
        
      }
      ...     
    }

	//生成 key
  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);
  }
複製代碼
class EngineKey implements Key {
 ...
 
   @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;
  }
   
 ...
  
}
複製代碼

根據註釋和代碼能夠看到傳入的參數之多,主要是根據 url ,簽名,寬高等,其內部重寫了 hashCode,equals,來保證對象的惟一性。

內存緩存

經過下面代碼開啓內存緩存,固然 Glide 默認是爲咱們開啓了內存緩存因此不須要咱們調用 skipMemoryCache

//在 BaseRequestOptions 成員變量中默認爲內存緩存開啓。
private boolean isCacheable = true;

//調用層調用
Glide.
      with(MainActivity.this.getApplication()).
      //開啓使用內存緩存
      skipMemoryCache(true).
      into(imageView);
複製代碼

如今緩存 key 有了以後,就能夠根據 key 拿到對應的緩存了,經過文章開始的介紹,咱們知道先加載活動緩存,若是活動緩存沒有在加載內存緩存,先看下代碼;

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
      
      ...
        
public synchronized <R> LoadStatus load( ....//參數 ) {
       ....
     //1. 生成緩存惟一 key 值,model 就是圖片地址
     EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);  
      
     //2. 優先加載內存中的活動緩存 - ActiveResources
    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;
    }
		
   	//3. 若是活動緩存中沒有,就加載 LRU 內存緩存中的資源數據。
    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;
    }
        
      ...     
      
    }
複製代碼

Glide 爲何會設計 2 個內存緩存

不知道你們經過上面代碼跟註釋(2,3 ) , 有沒有發現爲何 Glide 會弄 2 個內存緩存(一個 Map + 弱引用,一個 LRU 內存緩存),你們有沒有想過爲何?在看 郭霖大神對 Glide 緩存機制分析 中說道, ActiveResources 就是一個弱引用的 HashMap ,用來緩存正在使用中的圖片,使用 ActiveResources 來緩存正在使用中的圖片,能夠保護這些圖片不會被 LruCache 算法回收掉 ,起初當我看見這句話的時候,我是真的很不理解,由於 Lru 是最近最少時候纔會回收尾端數據,那麼這裏的 ActiveResources 來緩存正在使用中的圖片,能夠保護這些圖片不會被 LruCache 算法回收掉 ,我是越想越以爲矛盾,後來中午吃飯的時候,靈光一閃忽然想到了一種狀況,我也不知道是否是這樣,先看下面一張圖。

uZdRJI.png

詳細舉例說明: 好比咱們 Lru 內存緩存 size 設置裝 99 張圖片,在滑動 RecycleView 的時候,若是剛剛滑動到 100 張,那麼就會回收掉咱們已經加載出來的第一張,這個時候若是返回滑動到第一張,會從新判斷是否有內存緩存,若是沒有就會從新開一個 Request 請求,很明顯這裏若是清理掉了第一張圖片並非咱們要的效果。因此在從內存緩存中拿到資源數據的時候就主動添加到活動資源中,而且清理掉內存緩存中的資源。這麼作很顯然好處是 保護不想被回收掉的圖片不被 LruCache 算法回收掉,充分利用了資源。 我也不知道這樣理解是否正確,但願若是有其它的含義,麻煩告知一下,謝謝 !

上面咱們說到了 Glide 爲何會設計 2 個內存緩存,下面咱們就對這 2 個緩存的 存/取/刪 來具體說明下。

ActiveResources 活動資源

獲取活動資源

經過以前的介紹咱們知道,活動緩存是在Engine load中獲取

public synchronized <R> LoadStatus load( ....//參數 ) {
      
     //1. 優先加載內存中的活動緩存 - ActiveResources
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      // 若是找到獲取資源,就返回上層
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      return null;
    }       
      ...     
      
    }


  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
 		// 經過 活動資源的 get 函數拿到活動資源的緩存
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      //正在使用,引用計數 +1
      active.acquire();
    }

    return active;
  }

複製代碼

繼續看 get 的具體實現

final class ActiveResources {
 
  //
  @VisibleForTesting
  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
  
  ...

  //外部調用 get 函數拿到活動資源緩存
  @Nullable
  synchronized EngineResource<?> get(Key key) {
    //經過 HashMap + WeakReference 的存儲結構
    //經過 HashMap 的 get 函數拿到活動緩存
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

    EngineResource<?> active = activeRef.get();
    if (active == null) {
      cleanupActiveReference(activeRef);
    }
    return active;
  }

  //繼承 WeakReference 弱引用,避免內存泄漏。
    @VisibleForTesting
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
  ....
}

複製代碼

經過上面代碼咱們知道活動緩存是 Map + WeakReference 來進行維護的,這樣作的好處是避免圖片資源內存泄漏。

存儲活動資源

經過文章開頭表格中提到,活動資源是加載內存資源以後存儲的,那麼咱們就來看下什麼時候加載內存資源,看下面代碼。

仍是在Engine load函數中

//1. 若是活動緩存中沒有,就加載 LRU 內存緩存中的資源數據。
    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;
    }

	// 加載內存中圖片資源
  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
		//2. 拿到內存緩存,內部將當前的 key 緩存 remove 了
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      // 3. 若是內存緩存不爲空,則引用計數器 +1
      cached.acquire();
      //4. 添加進活動緩存中
      activeResources.activate(key, cached);
    }
    return cached;
  }
複製代碼

經過註釋 4 ,咱們知道在拿到內存緩存的時候,先將內存緩存的當前 key 刪除了,而後添加到活動緩存中。

清理活動資源

經過上一篇 Android 圖片加載框架 Glide 4.9.0 (一) 從源碼的角度分析一次最簡單的執行流程 中得知,咱們是在EngineJob中發出的通知回調,告知Engine onResourceReleased來刪除 活動資源。下面咱們在回顧一下執行流程,那麼咱們就從 EngineJob 發出通知開始看吧:

class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
      
      ....
      
@Override
  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    synchronized (this) {
      this.resource = resource;
      this.dataSource = dataSource;
    }
    notifyCallbacksOfResult();
  }


  @Synthetic
  void notifyCallbacksOfResult() {
 		 .....

    //回調上層 Engine 任務完成了
    listener.onEngineJobComplete(this, localKey, localResource);

    //遍歷資源回調給 ImageViewTarget ,並顯示
    for (final ResourceCallbackAndExecutor entry : copy) {
      entry.executor.execute(new CallResourceReady(entry.cb));
    }
    //這裏是通知發出上層刪除活動資源數據
    decrementPendingCallbacks();
  }
      
      
 ....
   
    }

	//重要的就是這裏了
  @Synthetic
  synchronized void decrementPendingCallbacks() {
    if (decremented == 0) {
      if (engineResource != null) {
        		//若是不爲空,那麼就調用內部 release 函數
        		engineResource.release();
      }
    }
  }

複製代碼

看一下 EngineResource 的 release 函數

class EngineResource<Z> implements Resource<Z> {
  ...
    
    
    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) {
          //回調出去,Engine 來接收
          listener.onResourceReleased(key, this);
        }
      }
    }
  }
  
  ...
  
}
複製代碼

根據註釋,咱們知道這裏用了引用計數法,有點像 GC 回收的 引用計數法的影子。也就是說,當徹底沒有使用這樣圖片的時候,就會把活動資源清理掉,接着往下看,會調用 Engine 的 onResourceReleased 函數。

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
      
   。。。
   
  //接收來自 EngineResource 的調用回調
  @Override
  public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
   //1. 收到當前圖片沒有引用,清理圖片資源
    activeResources.deactivate(cacheKey);
   //2. 若是開啓了內存緩存
    if (resource.isCacheable()) {
      //3. 將緩存存儲到內存緩存中。
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }
  。。。
    }
    
複製代碼

經過上面註釋 1 能夠知道,這裏首先會將緩存圖片從 activeResources 中移除,而後再將它 put 到 LruResourceCache 內存緩存當中。這樣也就實現了正在使用中的圖片使用弱引用來進行緩存,不在使用中的圖片使用 LruCache 來進行緩存的功能,設計的真的很巧妙。

LruResourceCache 內存資源

獲取內存資源

不知道有沒有小夥伴注意到,其實在講活動資源存儲的時候已經涉及到了內存緩存的存儲,下面咱們在來看一下,具體代碼以下:

public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener {
      
    ...//忽略一些成員變量跟構造函數
     public synchronized <R> LoadStatus load( ...//忽略參數 ) {   
    
    ....
		//1. 加載內存緩存
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      //若是內存緩存中有,就通知上層,最後在 SingleRequest 接收
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }
      
    }
      
    ...
 }

  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
		//2. 經過 getEngineResourceFromCache 獲取內存資源
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {//若是內存資源存在
      //則引用計數 +1
      cached.acquire();
      //3. 將內存資源存入活動資源
      activeResources.activate(key, cached);
    }
    return cached;
  }


  private EngineResource<?> getEngineResourceFromCache(Key key) {
    //經過 Engine load 中傳參可知,cache 就是 Lru 內存資源緩存
    //2.1 這裏是經過 remove 刪除來拿到緩存資源
    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;
  }
複製代碼

經過註釋1得知,這裏是開始加載內存緩存中的資源;

經過註釋2.1 可知,這裏經過 Lru 內存緩存的 remove 來拿到內存緩存

最後將內存存儲到活動緩存中,並緩存當前找到的內存緩存。

這裏的活動緩存跟內存緩存緊密聯繫在一塊兒,環環相扣,就像以前咱們的疑問,爲何 Glide 會設計 2 個內存緩存的緣由了。

存儲內存資源

經過 EngineResource 的引用計數法機制 release 函數,只要沒有引用那麼就回調,請看下面代碼:

class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
      
      ....
      
@Override
  public void onResourceReady(Resource<R> resource, DataSource dataSource) {
    synchronized (this) {
      this.resource = resource;
      this.dataSource = dataSource;
    }
    notifyCallbacksOfResult();
  }


  @Synthetic
  void notifyCallbacksOfResult() {
 		 .....

    //這裏是通知發出上層刪除活動資源數據
    decrementPendingCallbacks();
  }  
      
 ....
   
    }

	//重要的就是這裏了
  @Synthetic
  synchronized void decrementPendingCallbacks() {
    if (decremented == 0) {
      if (engineResource != null) {
        		//若是不爲空,那麼就調用內部 release 函數
        		engineResource.release();
      }
    }
  }

  void release() {
    synchronized (listener) {
      synchronized (this) {
        //引用計數爲 0 的時候,回調。
        if (--acquired == 0) {
          listener.onResourceReleased(key, this);
        }
      }
    }
  }

複製代碼

最後會回調到 Engine 的 onResourceReleased 函數:

@Override
  public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    //1. 清理活動緩存
    activeResources.deactivate(cacheKey);
    //若是開啓了內存緩存
    if (resource.isCacheable()) {
      //2. 清理出來的活動資源,添加進內存緩存
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }
複製代碼

到了這裏咱們知道,在清理活動緩存的時候添加進了內存緩存。

清理內存緩存

這裏的清理是在獲取內存緩存的時候經過 remove 清理掉的,詳細能夠看內存緩存獲取的方式。

內存緩存小結

經過上面分析可知,內存緩存有活動緩存和內存資源緩存,下面看一個圖來總結下它們相互之間是怎麼配合交換數據的。

內存緩存加載機制

總結下步驟:

  1. 第一次加載沒有獲取到活動緩存。
  2. 接着加載內存資源緩存,先清理掉內存緩存,在添加進行活動緩存。
  3. 第二次加載活動緩存已經存在。
  4. 當前圖片引用爲 0 的時候,清理活動資源,而且添加進內存資源。
  5. 又回到了第一步,而後就這樣環環相扣。

磁盤緩存

在介紹磁盤緩存以前先看一張表格

緩存表示 說明
DiskCacheStrategy.NONE 表示不開啓磁盤緩存
DiskCacheStrategy.RESOURCE 表示只緩存轉換以後的圖片。
DiskCacheStrategy.ALL 表示既緩存原始圖片,也緩存轉換事後的圖片。
DiskCacheStrategy.DATA 表示只緩存原始圖片
DiskCacheStrategy.AUTOMATIC 根據數據源自動選擇磁盤緩存策略(默認選擇)

上面這 4 中參數其實很好理解,這裏有一個概念須要記住,就是當咱們使用 Glide 去加載一張圖片的時候,Glide 默認並不會將原始圖片展現出來,而是會對圖片進行壓縮和轉換,總之就是通過種種一系列操做以後獲得的圖片,就叫轉換事後的圖片。而 Glide 默認狀況下在硬盤緩存的就是 DiskCacheStrategy.AUTOMATIC

如下面的代碼來開啓磁盤緩存:

Glide.
      with(MainActivity.this.getApplication()).
      //使用磁盤資源緩存功能
      diskCacheStrategy(DiskCacheStrategy.RESOURCE).
      into(imageView);
複製代碼

知道了怎麼開啓,下面咱們就來看一下磁盤緩存的的加載與存儲。

這 2 個加載流程幾乎如出一轍,只是加載的數據源不一樣,下面咱們具體來看一下

DiskCacheStrategy.RESOURCE 資源類型

獲取資源數據

經過上一篇Glide 加載流程 咱們知道,若是在活動緩存、內存緩存中沒有找數據,那麼就從新開啓一個 GlideExecutor 線程池在 DecodeJob run 執行新的請求,下面咱們就直接來看 DecodeJob run 函數,跟着它去找 資源數據的加載:

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback, Runnable, Comparable<DecodeJob<?>>, Poolable {
      
   ...
    
   @Override
  public void run() {
     ...
    try {
      //若是取消就通知加載失敗
      if (isCancelled) {
        notifyFailed();
        return;
      }
      //1. 執行runWrapped
      runWrapped();
    } catch (CallbackException e) {
    ...
    }
  }
   ... 
 }

  private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
        //2. 找到執行的狀態
        stage = getNextStage(Stage.INITIALIZE);
        //3. 找到具體執行器
        currentGenerator = getNextGenerator();
        //4. 開始執行
        runGenerators();
        break;
     ...
    }
  }
  private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE: //3.1解碼後的資源執行器
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE://原始數據執行器
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE://新的請求,http 執行器
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }
複製代碼

經過上面分析的代碼跟註釋,咱們知道這裏是在找具體的執行器,找完了以後註釋 4 開始執行,如今咱們直接看註釋 4。先提一下 註釋 3.1 由於外部咱們配置的是 RESOURCE 磁盤資源緩存策略,因此直接找到的是 ResourceCacheGenerator 執行器。

private void runGenerators() {
    //若是當前任務沒有取消,執行器不爲空,那麼就執行 currentGenerator.startNext() 函數
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();
      if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }
    }
	..
  }
複製代碼

經過上面代碼可知,主要是執行 currentGenerator.startNext() 就句代碼,currentGerator 是一個接口,經過註釋 3.1 咱們知道這裏它的實現類是 ResourceCacheGenerator ,那麼咱們具體看下 ResourceCacheGenerator 的 startNext 函數;

class ResourceCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> {
      
    ...
      
      @Override
  public boolean startNext() {

    ...
      
    while (modelLoaders == null || !hasNextModelLoader()) {
      resourceClassIndex++;
  
      ...
      //1. 拿到資源緩存 key
      currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      //2. 經過 key 獲取到資源緩存
      cacheFile = helper.getDiskCache().get(currentKey);
      if (cacheFile != null) {
        sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

		loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      //3. 獲取一個數據加載器
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      //3.1 爲資源緩存文件,構建一個加載器,這是構建出來的是 ByteBufferFileLoader 的內部類 ByteBufferFetcher
      loadData = modelLoader.buildLoadData(cacheFile,
          helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        //3.2 利用 ByteBufferFetcher 加載,最後把結果會經過回調給 DecodeJob 的 onDataFetcherReady 函數
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
      
    ...
      
      
    }
複製代碼

經過上面註釋能夠獲得幾點信息

  1. 首先根據 資源 ID 等一些信息拿到資源緩存 Key
  2. 經過 key 拿到緩存文件
  3. 構建一個 ByteBufferFetcher 加載緩存文件
  4. 加載完成以後回調到 DecodeJob 中。

存儲資源數據

先來看下面一段代碼:

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback, Runnable, Comparable<DecodeJob<?>>, Poolable {
    
    ...
      
      
     private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
   
      ....

    stage = Stage.ENCODE;
    try {
      //1. 是否能夠將轉換後的圖片緩存
      if (deferredEncodeManager.hasResourceToEncode()) {
        //1.1 緩存入口
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
		...
    }
    onEncodeComplete();
  }     
    }
		
    void encode(DiskCacheProvider diskCacheProvider, Options options) {
      GlideTrace.beginSection("DecodeJob.encode");
      try {
        //1.2 將 Bitmap 緩存到資源磁盤
        diskCacheProvider.getDiskCache().put(key,
            new DataCacheWriter<>(encoder, toEncode, options));
      } finally {
        toEncode.unlock();
        GlideTrace.endSection();
      }
    }
複製代碼

經過上面咱們知道 http 請求到圖片輸入流以後通過一系列處理,轉換獲得目標 Bitmap 資源,最後經過回調到 DecodeJob 進行緩存起來。

清理資源緩存

  1. 用戶主動經過系統來清理
  2. 卸載軟件
  3. 調用 DisCache.clear();

DiskCacheStrategy.DATA 原始數據類型

獲取原始數據

參考上小節DiskCacheStrategy.RESOURCE 獲取資源,不一樣的是把 ResourceCacheGenerator 換成 DataCacheGenerator 加載了。

存儲原始數據

這裏既然存的是原始數據那麼咱們直接從 http 請求以後的響應數據開始查看,經過上一篇咱們知道是在 HttpUrlFetcher 中請求網絡,直接定位到目的地:

public class HttpUrlFetcher implements DataFetcher<InputStream> {
  
    @Override
  public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      //1. 經過 loadDataWithRedirects 來進行http 請求,返回 InputStream
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      //2. 將請求以後的數據返回出去
      callback.onDataReady(result);
    } catch (IOException e) {
		  ...
    } finally {
			...
    }
  }
}
複製代碼

根據註釋能夠得知,這裏主要用於網絡請求,請求響應數據回調給 MultiModelLoader 中。咱們看下 它具體實現:

class MultiModelLoader<Model, Data> implements ModelLoader<Model, Data> {  
  ...
@Override
    public void onDataReady(@Nullable Data data) {
    //若是數據不爲空,那麼就回調給 SourceGenerator
      if (data != null) {
        callback.onDataReady(data);
      } else {
        startNextOrFail();
      }
    }
 .... 
}
複製代碼

這裏的 callback 指的是 SourceGenerator ,繼續跟

class SourceGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object>, DataFetcherGenerator.FetcherReadyCallback {
    ....
      
    
      
      @Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      //1. 收到網絡下載好的圖片原始數據,賦值給成員變量 dataToCache
      dataToCache = data;
      //2. 交給 EngineJob 
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }      
   ....       
    }
複製代碼

經過上面註釋能夠知道 cb.reschedule(); 最後回調到 EngineJob 類,會執行 reschedule(DecodeJob<?> job) 函數的 getActiveSourceExecutor().execute(job); 用線程池執行任務,最後又回到了 DecodeJob 的 run 函數 拿到執行器DataCacheGenerator ,最終會在 SourceGenerator 的 startNext() 函數,以前流程代碼我就不貼了,上面講了不少次了,相信你們應該記得了,咱們直接看 startNext() 函數吧:

class SourceGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object>, DataFetcherGenerator.FetcherReadyCallback {

  /**這個臨時的變量就是 http 請求回來的圖片原始數據 */
  private Object dataToCache;


@Override
  public boolean startNext() {
   ....
     
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      //放入緩存
      cacheData(data);
    }
    
    ...
    }
    return started;
  }


  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());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      //存儲原始數據
      //經過 StreamEncoder encode 寫入文件
      helper.getDiskCache().put(originalKey, writer);
    
    } finally {
      loadData.fetcher.cleanup();
    }

    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }
複製代碼

經過上面代碼得知,這裏將原始數據寫入文件中了。

清理資源緩存

  1. 用戶主動經過系統來清理
  2. 卸載軟件
  3. 調用 DisCache.clear();

磁盤緩存小節

存儲

  1. 資源緩存是在把圖片轉換完以後才緩存;
  2. 原始數據是網絡請求成功以後就寫入緩存;

獲取

  1. 資源緩存跟原始數據都是在 GlideExecutor 線程池中,Decodejob 中檢查獲取數據。

這裏咱們看一張流程圖吧

複用池

Glide 中複用池也起了一個很大的做用,這裏我就不貼代碼了,由於這個很好理解,你們能夠去 Glide 中的 Downsample 詳細瞭解。在這裏我就簡單說一下 Glide 中複用池的處理。

在 Glide 中,在每次解析一張圖片爲 Bitmap 的時候不論是內存緩存仍是磁盤緩存,都會從其BitmapPool 中查找一個可被複用的 Bitmap ,以後在將此塊的內存緩存起來。

注意:在使用複用池的時候,若是存在能被複用的圖片會重複使用該圖片的內存。 因此複用並不能減小程序正在使用的內存大小。Bitmap 複用,解決的是減小頻繁申請內存帶來的性能(抖動、碎片)問題。

總結

最後在來一個思惟圖來總結一下:

能夠看到 Glide 在性能優化方面可謂是達到了極致,不光設計了多級複雜的緩存策略,就連開銷較大的 Bitmap 內存也利用了複用池進行了管理,因此就算用戶在沒有開啓全部緩存的狀況下, Bitmap 也保證了內存的合理使用,避免 OOM。儘量的減小了對象的建立開銷,保證了 Glide 加載的流暢性。

到這裏 Glide 4.9.0 版本的緩存機制也已經講完了,相信在看完以後,你將對 Glide 緩存機制有了必定了解。

感謝你的閱讀,文章中若有誤,還請指出,謝謝!

參考

相關文章
相關標籤/搜索