從原始碼角度深入理解Glide4(下)

maoqitian發表於2019-02-19

image

上一篇文章從原始碼角度深入理解Glide(上)中,我們已經把Glide載入圖片的基本流程走了一遍,想必你已經對Glide的載入原理有了新的認識並且見識到了Glide原始碼的複雜邏輯,在我們感嘆Glide原始碼複雜的同時我們也忽略了Glide載入圖片過程的其它細節,特別是快取方面,我們在上一篇文章中對於快取的處理都是跳過的,這一篇文章我們就從Glide的快取開始再次對Glide進行深入理解。

Glide快取

  • Glide載入預設情況下可以分為三級快取,哪三級呢?他們分別是記憶體、磁碟和網路。

  • 預設情況下,Glide 會在開始一個新的圖片請求之前檢查以下多級的快取:

    • 1.活動資源 (Active Resources) - 現在是否有另一個 View 正在展示這張圖片
    • 2.記憶體快取 (Memory cache) - 該圖片是否最近被載入過並仍存在於記憶體中
    • 3.資源型別(Resource) - 該圖片是否之前曾被解碼、轉換並寫入過磁碟快取
    • 4.資料來源 (Data) - 構建這個圖片的資源是否之前曾被寫入過檔案快取
  • 網路級別的載入我們已經在上一篇文章瞭解了,上面列出的前兩種情況則是記憶體快取,後兩種情況則是磁碟快取,如果以上四種情況都不存在,Glide則會通過返回到原始資源以取回資料(原始檔案,Uri, Url(網路)等)

快取的key

  • 提起快取,我們首先要明白,Glide中快取的圖片肯定不止一個,當我們載入圖片的同時,如果快取中有我們正在載入的圖片,我們怎麼找到這個圖片的快取呢?所以為了找到對應的快取,則每一個快取都有它對應的標識,這個標識在Glide中用介面Key來描述
/**Key 介面*/
public interface Key {
  String STRING_CHARSET_NAME = "UTF-8";
  Charset CHARSET = Charset.forName(STRING_CHARSET_NAME);
  void updateDiskCacheKey(@NonNull MessageDigest messageDigest);
  @Override
  boolean equals(Object o);
  @Override
  int hashCode();
}
複製程式碼

快取Key的生成

  • 前面提到了快取Key的介面,那這個快取的Key實在哪裡生成的,實現類又是什麼呢?這我們就要看到載入發動機Engine類的load方法
private final EngineKeyFactory keyFactory;
/**Engine類的load方法*/
public <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) {
    Util.assertMainThread();
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
    //省略部分程式碼
    ..........
  }
/**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;
  }
}  
複製程式碼
  • 由以上原始碼我們知道,通過EngineKeyFactory的buildKey方法Glide建立了快取的Key實現類EngineKey物件,由生成EngineKey物件傳入的引數我們可以明白,只要有一個引數不同,所生成的EngineKey物件都會是不同的。記憶體的速度是最快的,理所當然如果記憶體中有快取的對應載入圖片Glide會搜先從記憶體快取中載入。

LRU

LRU演算法思想

  • 從官方文件描述,我們可以知道Glide的快取底層實現原理演算法都是LRU(Least Recently Used),字面意思為最近最少使用。演算法核心思想(個人理解):在一個有限的集合中,存入快取,每一個快取都有唯一標識,當要獲取一個快取,集合中沒有則存入,有則直接從集合獲取,存入快取到集合時如果集合已經滿了則找到集合中最近最少的快取刪除並存入需要存入的快取。 這樣也就有效的避免了記憶體溢位(OOM)的問題。
  • 接下來我們看一張圖能夠更好的理解LRU演算法

LRU圖

  • 橫線上方每個數字代表要存入的資料,橫線下方代表三個記憶體頁(也可以理解為快取結合),快取集合最多可以存入三個快取資料,則從1開始依次按照數字程式碼的快取讀取並存入快取集合,首先開始時三個頁記憶體是空的,前三個快取資料不同,依次存入快取集合,當數字4在記憶體中並進行快取時,根據LRU演算法思想,則2和3相較於1使用時間間隔更少,所以淘汰1,快取資料4替換1的位置,接下去同理。
  • Glide記憶體快取使用的是LruCache,磁碟快取使用的DiskLruCache,他們核心思想都是LRU演算法,而快取集合使用的是LinkedHashMap,熟悉集合框架應該都明白LinkedHashMap整合HashMap,並且LinkedHashMap保證了key的唯一性,更符合LRU演算法的實現。
  • 更深入的瞭解LruCache和DiskLruCache可以檢視郭霖大神的解析Android DiskLruCache完全解析,硬碟快取的最佳方案

記憶體快取

記憶體快取相關API

//跳過記憶體快取
RequestOptions requestOptions =new RequestOptions().skipMemoryCache(true);
Glide.with(this).load(IMAGE_URL).apply(requestOptions).into(imageView);
//Generated API 方式
GlideApp.with(this).load(IMAGE_URL).skipMemoryCache(true).into(imageView); 
//清除記憶體快取,必須在主執行緒中呼叫
Glide.get(context).clearMemory();
複製程式碼

記憶體快取原始碼分析

  • 記憶體快取不需要你進行任何設定,它預設就是開啟的,我們再次回到Engine類的load方法
/**Engine類的load方法*/
public <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) {
    //省略部分程式碼
    ..........
    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;
    }
    //省略部分程式碼
    ..........
  }
複製程式碼
活動資源 (Active Resources)
  • 通過以上Engine類load的原始碼,首先呼叫loadFromActiveResources方法來從記憶體中獲取快取
private final ActiveResources activeResources;
/**Engine類的loadFromActiveResources方法*/
@Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }
    return active;
  }
/**ActiveResources類*/
final class ActiveResources {
     @VisibleForTesting
  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
   //省略部分程式碼
    ........
   @VisibleForTesting
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
    //省略部分程式碼
    ........
  }
}
/**Engine類的onEngineJobComplete方法*/
@SuppressWarnings("unchecked")
  @Override
  public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    Util.assertMainThread();
    // A null resource indicates that the load failed, usually due to an exception.
    if (resource != null) {
      resource.setResourceListener(key, this);

      if (resource.isCacheable()) {
        activeResources.activate(key, resource);
      }
    }

    jobs.removeIfCurrent(key, engineJob);
  }

/**RequestOptions類的skipMemoryCache方法*/
public RequestOptions skipMemoryCache(boolean skip) {
    if (isAutoCloneEnabled) {
      return clone().skipMemoryCache(true);
    }
    this.isCacheable = !skip;
    fields |= IS_CACHEABLE;
    return selfOrThrowIfLocked();
  }
複製程式碼
  • 通過以上原始碼, 這裡需要分幾步來解讀,首先如果是第一次載入,肯定沒有記憶體快取,所以如果第一次載入成功,則在載入成功之後呼叫了Engine物件的onEngineJobComplete方法,並在該方法中將載入成功的resource通過ActiveResources物件的activate方法儲存在其內部維護的弱引用(WeakReference)HashMap中。下次再載入相同的資源,當你設定了skipMemoryCache(true),則表明你不想使用記憶體快取,這時候Glide再次載入相同資源的時候則會跳過記憶體快取的載入,否則可以從ActiveResources物件中獲取,如果記憶體資源沒被回收的話(關於弱引用的一下描述可以看看我以前寫的一篇文章Android 學習筆記之圖片三級快取)。如果該弱引用資源被回收了(GC),則下一步就到記憶體中尋找是否有該資源的快取。
記憶體快取 (Memory cache)
  • 接著回到Engine類的load方法,如果弱引用快取資源已經被回收,則呼叫loadFromCache方法在記憶體快取中查詢快取資源
/**Engine類的load方法*/
public <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) {
    //省略部分程式碼
    ..........
    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;
    }
    //省略部分程式碼
    ..........
  }
/**Engine類的loadFromCache方法*/
private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }
/**Engine類的getEngineResourceFromCache方法*/
private final MemoryCache cache;
private EngineResource<?> getEngineResourceFromCache(Key key) {
    Resource<?> cached = cache.remove(key);

    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      // Save an object allocation if we've cached an EngineResource (the typical case).
      result = (EngineResource<?>) cached;
    } else {
      result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
    }
    return result;
  }
/**GlideBuilder類的build方法*/
private  MemoryCache cache;
@NonNull
  Glide build(@NonNull Context context) {
    //省略部分程式碼
    ..........
    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }

    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }

    if (engine == null) {
      engine =
          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              GlideExecutor.newAnimationExecutor(),
              isActiveResourceRetentionAllowed);
    }
    //省略部分程式碼
    ..........
}    
/**LruResourceCache類的實現繼承關係*/    
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache{......}    
複製程式碼
  • 通過以上原始碼,在loadFromCache同樣也判斷了Glide是否設定了skipMemoryCache(true)方法,沒有設定則呼叫getEngineResourceFromCache方法,在該方法中我們可以看到cache物件就是MemoryCache物件,而該物件實際是一個介面,他的實現類是LruResourceCache,該物件我們前面在GlideBuilder的build方法中進行了新建(在第一步with方法中呼叫了Glide.get方法,在get方法中初始化Glide呼叫了在GlideBuilder的build方法),這裡也就說明Glide的記憶體快取還是使用LruCache來實現,這裡如果獲取到了記憶體快取,則獲取內容快取的同時移除該快取,並在loadFromCache方法中將該資源標記為正在使用同時加入在弱引用中。這樣在ListView或者Recyclerview中載入圖片則下次載入首先從弱引用Map中獲取快取資源,並且標誌當前資源正在使用,可以防止該資源被LRU演算法回收掉。
記憶體快取寫入
  • 前面我們只是分析瞭如何獲取記憶體快取,而記憶體快取又是在哪裡寫入的呢?根據前面分析,首先獲取在弱引用Map中的快取資源,而前面我們在分析活動資源(Active Resources)時候已經說過是在onEngineJobComplete放中往弱引用Map存放快取資源,而 onEngineJobComplete方法是在哪裡呼叫呢,這我們就要回想起上一篇文章中我們再網路載入圖片成功後腰切換在主執行緒回撥來顯示圖片,也就是EngineJob物件的handleResultOnMainThread方法
/**EngineJob類的handleResultOnMainThread方法*/
@Synthetic
  void handleResultOnMainThread() {
    //省略部分程式碼
    ..........
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;
    //省略部分程式碼
    ..........
    engineResource.acquire();
    listener.onEngineJobComplete(this, key, engineResource);
    engineResource.release();
    //省略部分程式碼
    ..........
  }
/**EngineJob類的EngineResourceFactory內部類*/  
@VisibleForTesting
  static class EngineResourceFactory {
    public <R> EngineResource<R> build(Resource<R> resource, boolean isMemoryCacheable) {
      return new EngineResource<>(resource, isMemoryCacheable, /*isRecyclable=*/ true);
    }
  }
/**Engine類的onEngineJobComplete方法*/
@SuppressWarnings("unchecked")
  @Override
  public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    //省略部分程式碼
    ..........
    if (resource != null) {
      resource.setResourceListener(key, this);
      if (resource.isCacheable()) {
        activeResources.activate(key, resource);
      }
    }
    //省略部分程式碼
    ..........
  }  
複製程式碼
  • 通過以上原始碼,EngineJob類的handleResultOnMainThread方法首先構建了獲取好的包含圖片的資源,標記當前資源正在使用,通過listener.onEngineJobComplete回撥,而listener就是Engine物件,也就到了Engine類的onEngineJobComplete方法,並在該方法中存入了圖片資源到弱引用Map中。
  • 上面我是分析了弱引用資源的快取存入,接著我們看看記憶體快取是在哪裡存入的,在次看回handleResultOnMainThread方法,我們看到onEngineJobComplete回撥前後分別呼叫了EngineResource物件的acquire方法和release方法
/**EngineResource類的acquire方法*/
void acquire() {
    if (isRecycled) {
      throw new IllegalStateException("Cannot acquire a recycled resource");
    }
    if (!Looper.getMainLooper().equals(Looper.myLooper())) {
      throw new IllegalThreadStateException("Must call acquire on the main thread");
    }
    ++acquired;
  }
/**EngineResource類的release方法*/
  void release() {
    if (acquired <= 0) {
      throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
    }
    if (!Looper.getMainLooper().equals(Looper.myLooper())) {
      throw new IllegalThreadStateException("Must call release on the main thread");
    }
    if (--acquired == 0) {
      listener.onResourceReleased(key, this);
    }
  }
/**Engine類的onResourceReleased方法*/
@Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    Util.assertMainThread();
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }  
複製程式碼
  • 通過以上原始碼,其實我們應該能恍然大悟,Glide的記憶體快取存入其實就是通過一個acquired變數來進行控制,如果當前弱引用資源不再使用,也就是acquired等於零的時候,則呼叫回撥listener.onResourceReleased(listener就是Engine物件),在onResourceReleased方法中移除了弱引用資源資源,並且沒有設定skipMemoryCache(true),則通過cache.put存入記憶體快取。
  • 總的來說Glide的記憶體快取主要是結合了弱引用和記憶體來實現的。
Glide記憶體快取機制示意圖

Glide記憶體快取機制示意圖

磁碟快取

  • 說去磁碟快取,上一篇文章我們在簡單使用Glide的例子中就已經使用了Glide的磁碟快取
RequestOptions requestOptions = new RequestOptions()
         .diskCacheStrategy(DiskCacheStrategy.NONE);//不使用快取
     Glide.with(Context).load(IMAGE_URL).apply(requestOptions).into(mImageView);
複製程式碼
  • 既然知道如何使用Glide的磁碟快取,首先我們要了解Glide4中給我提供了哪幾種磁碟快取策略

磁碟快取策略

  • 1.DiskCacheStrategy.NONE: 表示不使用磁碟快取
  • 2.DiskCacheStrategy.DATA: 表示磁碟快取只快取原始載入的圖片
  • DiskCacheStrategy.RESOURCE: 表示磁碟快取只快取經過解碼轉換後的圖片
  • DiskCacheStrategy.ALL: 表示磁碟快取既快取原始圖片,也快取經過解碼轉換後的圖片
  • DiskCacheStrategy.AUTOMATIC: 表示讓Glide根據圖片資源智慧地選擇使用哪一種磁碟快取策略,該選項也是我們在不進行手動設定的時候Glide的預設設定

磁碟快取原始碼分析

  • 不知你是否還記得上一篇文章中在載入圖片的時候我們是在開啟子執行緒任務線上程池中進行的,我們來回顧一下
/**DecodeJob類的start方法*/
public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }
/**DecodeJob類的willDecodeFromCache方法*/ 
 boolean willDecodeFromCache() {
    Stage firstStage = getNextStage(Stage.INITIALIZE);
    return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
  } 
/**DecodeJob類的getNextStage方法*/   
private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
     //省略部分程式碼
    ......
    }
  }
/**DiskCacheStrategy類的ALL物件*/     
public static final DiskCacheStrategy ALL = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return dataSource == DataSource.REMOTE;
    }

    @Override
    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
        EncodeStrategy encodeStrategy) {
      return dataSource != DataSource.RESOURCE_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
    }

    @Override
    public boolean decodeCachedResource() {
      return true;
    }

    @Override
    public boolean decodeCachedData() {
      return true;
    }
  };  
/**GlideBuilder類的build方法*/  
@NonNull
  Glide build(@NonNull Context context) {
    //省略部分程式碼
    ......
    if (diskCacheExecutor == null) {
      diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
    }
    //省略部分程式碼
    ......
}
/**GlideExecutor類的newDiskCacheExecutor方法*/ 
private static final int DEFAULT_DISK_CACHE_EXECUTOR_THREADS = 1;
public static GlideExecutor newDiskCacheExecutor() {
    return newDiskCacheExecutor(
        DEFAULT_DISK_CACHE_EXECUTOR_THREADS,
        DEFAULT_DISK_CACHE_EXECUTOR_NAME,
        UncaughtThrowableStrategy.DEFAULT);
  }
/**GlideExecutor類的newDiskCacheExecutor方法*/ 
 public static GlideExecutor newDiskCacheExecutor(
      int threadCount, String name, UncaughtThrowableStrategy uncaughtThrowableStrategy) {
    return new GlideExecutor(
        new ThreadPoolExecutor(
            threadCount /* corePoolSize */,
            threadCount /* maximumPoolSize */,
            0 /* keepAliveTime */,
            TimeUnit.MILLISECONDS,
            new PriorityBlockingQueue<Runnable>(),
            new DefaultThreadFactory(name, uncaughtThrowableStrategy, true)));
  }  
複製程式碼
  • 通過以上原始碼,可以分兩個步驟來進行解讀:
    • 第一步在DecodeJob物件的start方法開啟子執行緒來載入圖片,這裡使用了執行緒池,通過willDecodeFromCache方法和getNextStage放結合,主要通過Stage列舉來判斷當前使用的快取策略,而快取策略的設定則通過DiskCacheStrategy物件的decodeCachedResource和decodeCachedData方法來進行設定,而這兩個方法在DiskCacheStrategy抽象類都是抽象方法,而他們的實現就是我們前面提到的Glide磁碟快取的五種策略,上面程式碼中列出其中一種ALL程式碼,decodeCachedResource和decodeCachedData方法都返回ture,也就說明磁碟快取既快取原始圖片,也快取經過解碼轉換後的圖片;如果decodeCachedResource返回false和decodeCachedData方法返回true,也就代表DATA策略,磁碟快取只快取原始載入的圖片,其他同理
    • 第二步通過前面對設定策略的判斷,如果有快取策略,則拿到的執行緒池就是磁碟快取載入的執行緒池(執行緒池的理解可以看看我以前寫的一篇文章),該執行緒的初始化還是在GlideExecutor物件的build方法中,通過以上原始碼,該執行緒池只有唯一一個核心執行緒,這就保證所有執行的的任務都在這一個執行緒中執行,並且是順序執行,也就不用在考慮執行緒同步的問題了。
  • 根據前面官網的說明,不管是記憶體快取還是磁碟快取,都是使用LRU,接著看看Glide磁碟快取在哪獲取LRU物件,還是得看到GlideBuilder物件的build方法
/**GlideBuilder類的build方法*/  
private DiskCache.Factory diskCacheFactory;
@NonNull
  Glide build(@NonNull Context context) {
    //省略部分程式碼
    ..........
    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }
    //省略部分程式碼
    ..........
}
/**InternalCacheDiskCacheFactory類的繼承關係*/
public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {
   //省略實現程式碼
    ..........  
}
/**DiskLruCacheFactory類的部分程式碼*/
public class DiskLruCacheFactory implements DiskCache.Factory {
   //省略部分程式碼
    ..........
    @Override
  public DiskCache build() {
    File cacheDir = cacheDirectoryGetter.getCacheDirectory();
    //省略部分程式碼
    ..........
    return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);
  }
}
/**DiskLruCacheWrapper類的部分程式碼*/
public class DiskLruCacheWrapper implements DiskCache {
//省略部分程式碼
    ..........
   private synchronized DiskLruCache getDiskCache() throws IOException {
    if (diskLruCache == null) {
      diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
    }
    return diskLruCache;
  } 
  //省略部分程式碼
    ..........
}
/**DiskLruCache類的部分程式碼*/
public final class DiskLruCache implements Closeable {
    //省略部分程式碼
    ..........
    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
      throws IOException {
          DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
          //省略部分程式碼
          ..........
          return cache;
      }
      //省略部分程式碼
      ..........
}

複製程式碼
  • 通過以上原始碼,我們在GlideBuilder物件的build方法中已經新建了InternalCacheDiskCacheFactory物件,也就是DiskLruCacheFactory物件,同樣該物件已經被我們傳入Engine物件的構造方法中,最終包裝成LazyDiskCacheProvider物件(該物件程式碼就不貼出了),所以只要呼叫DiskLruCacheFactory物件的build方法就能夠最終獲取到DiskLruCache物件,該物件是Glide自己實現的,但是其原理和谷歌官方推薦的DiskLruCache也差不了太多,核心還是使用LRU演算法來實現磁碟快取。
資源型別(Resource)
  • 根據前面分分析,假定沒有記憶體快取,而是由磁碟快取,則結合前面分析我們得到了磁碟快取處理的執行緒池,也獲得列舉Stage是RESOURCE_CACHE或DATA_CACHE,則在DecodeJob物件getNextGenerator方法,我們就能得到對應的Generator
/**DecodeJob的getNextGenerator方法*/
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);
    }
  }
複製程式碼
  • 通過getNextGenerator方法的原始碼,如果之前設定磁碟快取策略為DiskCacheStrategy.RESOURCE,則應該對應的就是列舉Stage.RESOURCE_CACHE,也就是說接下來使用的資源Generator是ResourceCacheGenerator,結合上一篇文章,我們分析網路載入流程是這裡獲取的是SourceGenerator,我們接著來看ResourceCacheGenerator的startNext()方法
/** ResourceCacheGenerator類的startNext方法*/
  @SuppressWarnings("PMD.CollapsibleIfStatements")
  @Override
  public boolean startNext() {
     //省略部分程式碼
      ..........
      currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      cacheFile = helper.getDiskCache().get(currentKey);
      if (cacheFile != null) {
        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;
  }
/**DecodeHelper類的getDiskCache方法*/
 DiskCache getDiskCache() {
    return diskCacheProvider.getDiskCache();
  }
/** LazyDiskCacheProvider類的getDiskCache方法 */
@Override
    public DiskCache getDiskCache() {
      if (diskCache == null) {
        synchronized (this) {
          if (diskCache == null) {
            diskCache = factory.build();
          }
          if (diskCache == null) {
            diskCache = new DiskCacheAdapter();
          }
        }
      }
      return diskCache;
    }  
複製程式碼
  • 通過以上原始碼其實已經很清晰,首先還是獲取快取的唯一key,然後helper.getDiskCache().get(currentKey)這一句話就是獲取快取,helper物件就是DecodeHelper,它的getDiskCache方法獲取的物件也就是前面提到的包含DiskLruCacheFactory物件的LazyDiskCacheProvider物件,而LazyDiskCacheProvider物件的getDiskCache方法呼叫了factory.build(),factory物件DiskLruCacheFactory,也就是獲取了我們前面所說的DiskLruCache物件
  • 接著繼續看資料返回走的流程還是通過回撥通cb.onDataFetcherReady將獲取的快取資源傳遞到DecodeJob,由DecodeJob繼續執行剩餘圖片顯示步驟,大致流程和網路載入差不多,這裡就不進行討論了
/** ResourceCacheGenerator類的startNext方法*/
 @Override
  public void onDataReady(Object data) {
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE,
        currentKey);
  }
複製程式碼
資料來源 (Data)
  • 同理資源型別(Resource),則設定磁碟快取策略為DiskCacheStrategy.DATA,則應該對應的就是列舉Stage.DATA_CACHE,使用的資源Generator是DataCacheGenerator,所以直接看看DataCacheGenerator的startNext()方法,該方法原始碼如下,同樣是根據key通過DiskLruCache物件來獲取磁碟快取(DATA),資料返回走的流程還是通過回撥通cb.onDataFetcherReady將獲取的快取資源傳遞到DecodeJob,由DecodeJob繼續執行剩餘圖片顯示
/** DataCacheGenerator類的startNext方法*/
 @Override
  public boolean startNext() {
      //省略部分程式碼
      ..........
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      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;
  }
/** DataCacheGenerator類的onDataReady方法*/  
@Override
  public void onDataReady(Object data) {
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
  }  
複製程式碼
磁碟快取資料存入
  • 前面我們只是瞭解了磁碟快取的獲取,磁碟快取又是在哪裡存入的,接著往下看。
  • 根據上一篇文章的分析,載入圖片會走到DecodeJob物件的decodeFromRetrievedData方法
/** DecodeJob類的decodeFromRetrievedData方法*/  
private void decodeFromRetrievedData() {
    //省略部分程式碼
      ..........
    notifyEncodeAndRelease(resource, currentDataSource);
    //省略部分程式碼
      ..........
} 
/** DecodeJob類的notifyEncodeAndRelease方法*/  
private final DeferredEncodeManager<?> deferredEncodeManager = new DeferredEncodeManager<>();
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
//省略部分程式碼
      ..........
    stage = Stage.ENCODE;
    try {
      if (deferredEncodeManager.hasResourceToEncode()) {
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } 
    //省略部分程式碼
    ..........
  }
/** 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();
      }
    } 
複製程式碼
  • 通過以上原始碼可以看到DecodeJob物件的decodeFromRetrievedData方法通過呼叫notifyEncodeAndRelease方法,在該方法中呼叫了內部類DeferredEncodeManager的encode方法存入了磁碟快取,這裡存入的是轉換後的磁碟快取(Resource)。
  • 原始資料也就是SourceGenerator第一次網路下載成功之後獲取的圖片資料,之後再做磁碟快取,所以再次回到看到SourceGenerator的onDataReady方法
/**SourceGenerator類的onDataReady方法**/ 
private Object dataToCache;
@Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }  
/**SourceGenerator類的startNext方法**/
@Override
  public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
   //省略部分程式碼
  ..........
  }
/**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());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      helper.getDiskCache().put(originalKey, writer);
    } 
    //省略部分程式碼
    ..........
    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }
/**DecodeJob類的reschedule方法**/ 
@Override
  public void reschedule() {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }
/**Engine類的reschedule方法**/   
 @Override
  public void reschedule(DecodeJob<?> job) {
    getActiveSourceExecutor().execute(job);
  }  
複製程式碼
  • 通過以上原始碼,其實邏輯已經很清晰,會讓你有“柳暗花明又一村”的感覺,onDataReady網路請求成功並且設定了快取策略,則將圖片資源賦值給Object型別的dataToCache,執行回撥cb.reschedule,cb就是DecodeJob物件,所以接著執行了DecodeJob物件的reschedule方法,該方法再次執行回撥也就是執行了Engine物件的reschedule方法,該方法再次執行DecodeJob,也就會再次觸發SourceGenerator類的startNext方法,該方法首先判斷了Object型別的dataToCache是否有值,前面分析該物件已經賦值,所以就進入到SourceGenerator物件的cacheData方法存入了我們的原始下載圖片的快取。

僅從快取載入圖片

  • 前面我基本把Glide的快取模組梳理了一遍,但是還差個東西,那就是如果我只想Glide載入快取呢?這種需求還是有的,比如說我們在有些應用看到的省流量模式,不就是正好對應這個需求,沒關係Gldie也已經為我們考慮到了,那就是onlyRetrieveFromCache(true),只要設定了這個,圖片在記憶體快取或在磁碟快取中就會被載入出來,而沒有快取,則這一次載入失敗。
  • 我們看看如何使用
RequestOptions requestOptions = new RequestOptions().onlyRetrieveFromCache(true);
Glide.with(this).load(IMAGE_URL).apply(requestOptions).into(mImageView);
//Generated API 方式        
GlideApp.with(this)
  .load(url)
  .diskCacheStrategy(DiskCacheStrategy.ALL)
  .into(mImageView);        
複製程式碼
  • 使用起來還是很方便的,只要設定onlyRetrieveFromCache(true)方法就行,而它的原理也其實也很簡單,我們再次回到DecodeJob物件的getNextStage方法,如果前面獲取了快取,則相應得到對應的Generator載入圖片,如果獲取不到快取,則列舉Stage.FINISHED,DecodeJob物件的getNextGenerator方法則會返回null。(如下程式碼所示)
/**DecodeJob類的getNextStage方法**/ 
private Stage getNextStage(Stage current) {
    switch (current) {
    //省略部分程式碼
    ..........
      case DATA_CACHE:
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
    }
  }
/**DecodeJob類的getNextGenerator方法**/   
private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
    //省略部分程式碼
    ..........
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }  
複製程式碼
Glide磁碟快取機制示意圖

Glide磁碟快取機制示意圖

Glide快取小結

  • 通過前面對Glide快取的分析,讓我再次認識到Glide的強大,使用時只是簡單的幾個方法設定或者不設定,Glide都能夠在背後依靠其複雜的邏輯為我們快速的載入出圖片並顯示,快取還有一些細節比如可以自定義key等,這裡就不進行展開了,有興趣的可以自行研究。

Glide 回撥與監聽

圖片載入成功回撥原理

  • 由上一篇文章分析,我們來回顧一下圖片載入成功之後的邏輯。資料載入成功之後切換主執行緒最終呼叫SingleRequest類的onResourceReady方法,在該方法中載入成功的資料通過target.onResourceReady方法將資料載入出來,target就是DrawableImageViewTarget物件,他繼續了實現了Target介面的基類ImageViewTarget,所以呼叫它實現的onResourceReady方法或者父類實現的onResourceReady方法就實現了載入成功資料的回撥,並由DrawableImageViewTarget物件顯示載入成功的圖片,這就是資料載入成功回撥原理。
/**SingleRequest類的onResourceReady方法**/    
@Nullable 
private List<RequestListener<R>> requestListeners;
private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
    //省略部分程式碼
    ..........
    isCallingCallbacks = true;
    try {
      boolean anyListenerHandledUpdatingTarget = false;
      //省略部分程式碼
     ..........

      if (!anyListenerHandledUpdatingTarget) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        target.onResourceReady(result, animation);
      }
    } 
    //省略部分程式碼
    ..........
  }
/**Target 介面**/      
public interface Target<R> extends LifecycleListener {}
/**ImageViewTarget類的onResourceReady方法**/ 
@Override
  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    if (transition == null || !transition.transition(resource, this)) {
      setResourceInternal(resource);
    } else {
      maybeUpdateAnimatable(resource);
    }
  }
/**ImageViewTarget類的setResourceInternal方法**/ 
private void setResourceInternal(@Nullable Z resource) {
    setResource(resource);
    maybeUpdateAnimatable(resource);
  }
DrawableImageViewTarget
/**DrawableImageViewTarget類的setResource方法**/ 
 @Override
  protected void setResource(@Nullable Drawable resource) {
    view.setImageDrawable(resource);
  }  
複製程式碼

Glide監聽(listener)

  • 再次看看Glide監聽(listener)的例子
Glide.with(this).load(IMAGE_URL).listener(new RequestListener<Drawable>() {
           @Override
           public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
               return false;
           }

           @Override
           public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
               return false;
           }
       }).into(mImageView);
複製程式碼
  • Glide監聽的實現同樣還是基於我們上面分析的SingleRequest物件的onResourceReady方法,使用的時候呼叫RequestBuilder物件的listener方法,傳入的RequestListener物件加入到requestListeners,這樣在SingleRequest物件的onResourceReady方法中遍歷requestListeners,來回撥listener.onResourceReady方法,布林型別的anyListenerHandledUpdatingTarget則接收回撥listener.onResourceReady方法的返回值,如果返回true,則不會執會往下執行,則接著的into方法就不會被觸發,說明我們自己在監聽中處理,返回false則不攔截。
/**RequestBuilder類的listener方法**/ 
@Nullable 
private List<RequestListener<TranscodeType>> requestListeners;
  public RequestBuilder<TranscodeType> listener(
      @Nullable RequestListener<TranscodeType> requestListener) {
    this.requestListeners = null;
    return addListener(requestListener);
  }
/**RequestBuilder類的addListener方法**/  
  public RequestBuilder<TranscodeType> addListener(
      @Nullable RequestListener<TranscodeType> requestListener) {
    if (requestListener != null) {
      if (this.requestListeners == null) {
        this.requestListeners = new ArrayList<>();
      }
      this.requestListeners.add(requestListener);
    }
    return this;
  }

/**SingleRequest類的onResourceReady方法**/    
@Nullable 
private List<RequestListener<R>> requestListeners;
private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
    //省略部分程式碼
    ..........
    isCallingCallbacks = true;
    try {
      boolean anyListenerHandledUpdatingTarget = false;
      if (requestListeners != null) {
        for (RequestListener<R> listener : requestListeners) {
          anyListenerHandledUpdatingTarget |=
              listener.onResourceReady(result, model, target, dataSource, isFirstResource);
        }
      }
      anyListenerHandledUpdatingTarget |=
          targetListener != null
              && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource);

      if (!anyListenerHandledUpdatingTarget) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        target.onResourceReady(result, animation);
      }
    } 
    //省略部分程式碼
    ..........
  }
複製程式碼

Target(目標)

  • Target在Glide中相當於中間人的作用,在圖片的展示起到承上啟下的功效,首先看看Target介面的繼承關係圖

Target繼承關係圖

  • 通過該圖,我們可以把Target分為三類,一種是簡單的Target,一種是載入到特定View的Target(ViewTarget),還有一種是FutureTarget,可以知道非同步執行的結果,得到快取檔案
  • 上一篇文章分析into方法時我們是分析into(ImageView)這個方法開始的,它內部還是會得到特定的Target物件,也就是我們一直說的DrawableImageViewTarget,而他是屬於ViewTarget的子類

簡單的Target(SimpleTarget)

  • SimpleTarget其實是在給我們更靈活的載入到各種各樣物件準備的,只要指定我們載入獲取的是什麼物件asBitmap(),就能使用SimpleTarge或者整合它的我們自定義的物件,在其中通過獲取的Bitmap顯示在對應的控制元件上,比如上一篇文章例子提到的NotifivationTarget,就是載入到指定的Notifivation中,靈活載入。
//注意需要指定Glide的載入型別asBitmap,不指定Target不知道本身是是型別的Target
Glide.with(this).asBitmap().load(IMAGE_URL).into(new SimpleTarget<Bitmap>() {
            @Override
            public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
            //載入完成已經在主執行緒
                mImageView.setImageBitmap(resource);
            }
        });
複製程式碼

特定View的Target(ViewTarget)

  • 由DrawableImageViewTarget和BitmapImageViewTarget我們就可以知道這是為了不同型別的圖片資源準備的Target,但是還有一種需求,就是如果我們傳入是要載入圖片資源的View,但是該View不被Glide支援,目前into方法支援傳入ImageView,沒關係,ViewTarget可以幫上忙,比如我們需要載入到RelativeLayout
/**
 * @author maoqitian
 * @Description: 自定義RelativeLayout
 * @date 2019/2/18 0018 19:51
 */

public class MyView extends RelativeLayout {
    private ViewTarget<MyView, Drawable> viewTarget;
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        viewTarget =new ViewTarget<MyView, Drawable>(this) {
            @Override
            public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
                setBackground(resource);
            }
        };
    }

    public ViewTarget<MyView, Drawable> getViewTarget() {
        return viewTarget;
    }
}

//使用Glide載入
MyView rl_view = findViewById(R.id.rl_view);
Glide.with(this).load(IMAGE_URL).into(rl_view.getViewTarget());
複製程式碼

FutureTarget

  • FutureTarget的一大用處就是可以得到快取檔案
new Thread(new Runnable() {
            @Override
            public void run() {
                FutureTarget<File> target = null;
                RequestManager requestManager = Glide.with(MainActivity.this);
                try {
                    target = requestManager
                            .downloadOnly()
                            .load(IMAGE_URL)
                            .submit();
                    final File downloadedFile = target.get();
                    Log.i(TAG,"快取檔案路徑"+downloadedFile.getAbsolutePath());
                } catch (ExecutionException | InterruptedException e) {

                } finally {
                    if (target != null) {
                        target.cancel(true); // mayInterruptIfRunning
                    }
                }
            }
        }).start();
複製程式碼

preload(預載入)

  • 如何使用
Glide.with(this).load(IMAGE_URL).preload();
複製程式碼
  • 預載入其實也是屬於Target的範圍,只是他載入的物件為空而已,也就是沒有載入目標
/**RequestBuilder類的preload方法**/
 @NonNull
  public Target<TranscodeType> preload() {
    return preload(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
  }
/**RequestBuilder類的preload方法**/ 
@NonNull
  public Target<TranscodeType> preload(int width, int height) {
    final PreloadTarget<TranscodeType> target = PreloadTarget.obtain(requestManager, width, height);
    return into(target);
  }
/**RequestBuilder類的onResourceReady方法**/   
public final class PreloadTarget<Z> extends SimpleTarget<Z> {

private static final Handler HANDLER = new Handler(Looper.getMainLooper(), new Callback() {
    @Override
    public boolean handleMessage(Message message) {
      if (message.what == MESSAGE_CLEAR) {
        ((PreloadTarget<?>) message.obj).clear();
        return true;
      }
      return false;
    }
  });

//省略部分程式碼
    ..........
public static <Z> PreloadTarget<Z> obtain(RequestManager requestManager, int width, int height) {
    return new PreloadTarget<>(requestManager, width, height);
  } 

@Override
  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    HANDLER.obtainMessage(MESSAGE_CLEAR, this).sendToTarget();
  }
  //省略部分程式碼
    ..........
}  
複製程式碼
  • 通過以上原始碼,邏輯已經非常清晰,Glide的preload方法裡使用的繼承SimpleTarget的PreloadTarget物件來作為Target,在它的onResourceReady方法中並沒有任何的載入操作,只是呼叫了Handler來釋放資源,到這裡也許你會有疑惑,不是說預載入麼,怎麼不載入。哈哈,其實到onResourceReady方法被呼叫經過前面的分析Glide已經走完快取的所有邏輯,那就很容易理解了,預載入只是把圖片載入到快取當中,沒有進行其他操作,自然是預載入,並且載入完成之後釋放了資源。

Generated API

  • Generated API說白了就是Glide使用註解處理器生成一個API(GlideApp),該API可以代替Glide幫助我們完成圖片載入。
  • Generated API 目前僅可以在 Application 模組內使用,使用Generated API一方面在Application 模組中可將常用的選項組打包成一個選項在 Generated API 中使用,另一方面可以為Generated API 擴充套件自定義選項(擴充套件我們自定義的功能方法)。
  • 在上一篇文章中例子中我們可以看到使用Generated API之後使用Glide的方式基本上和Glide3的用法一樣流式API使用,先來回顧一下如何使用Generated API
//在app下的gradle新增Glide註解處理器的依賴
dependencies {
  annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
}
//新建一個類整合AppGlideModule並新增上@GlideModule註解,重新rebuild專案就可以使用GlideApp了
@GlideModule
public final class MyAppGlideModule extends AppGlideModule {}

複製程式碼
  • 經過上的程式碼的操作,通過Glide註解處理器已經給我們生成了GlideApp類
/**GlideApp類部分程式碼**/ 
public final class GlideApp {
    //省略部分程式碼
    ..........
    @NonNull
    public static GlideRequests with(@NonNull Context context) {
    return (GlideRequests) Glide.with(context);
  }
  //省略部分程式碼
    ..........
}
/**GlideApp類部分程式碼**/ 
public class GlideRequest<TranscodeType> extends RequestBuilder<TranscodeType> implements Cloneable {
//省略部分程式碼
    ..........
  @NonNull
  @CheckResult
  public GlideRequest<TranscodeType> placeholder(@Nullable Drawable drawable) {
    if (getMutableOptions() instanceof GlideOptions) {
      this.requestOptions = ((GlideOptions) getMutableOptions()).placeholder(drawable);
    } else {
      this.requestOptions = new GlideOptions().apply(this.requestOptions).placeholder(drawable);
    }
    return this;
  }
  //省略部分程式碼
    ..........
}
/**RequestBuilder類的getMutableOptions方法**/ 
protected RequestOptions getMutableOptions() {
    return defaultRequestOptions == this.requestOptions
        ? this.requestOptions.clone() : this.requestOptions;
  }
複製程式碼
  • 通過以上原始碼,可以發現,GlideApp物件的with方法返回的是GlideRequests物件,GlideRequests物件繼承的是RequestBuilder,這時應該又是豁然開朗的感覺,GlideApp能夠適應流式API,其實就是對RequestBuilder包裝了一層,GlideRequests物件通過其父類RequestBuilder物件的getMutableOptions方法獲取到requestOptions,然後在相應的方法中操作requestOptions以達到可以使用流式API的功能。

GlideExtension

  • GlideExtension字面意思就是Glide擴充套件,它是一個作用於類上的註解,任何擴充套件 Glide API 的類都必須使用這個註解來標記,否則其中被註解的方法就會被忽略。 被 @GlideExtension 註解的類應以工具類的思維編寫。這種類應該有一個私有的、空的構造方法,應為 final 型別,並且僅包含靜態方法。

@GlideOption

  • GlideOption註解是用來擴充套件RequestOptions,擴充套件功能方法第一個引數必須是RequestOptions。下面我們通過設定一個擴充套件預設設定佔位符和錯誤符方法的例子來說明GlideOption註解。
/**
 * @author maoqitian
 * @Description: GlideApp 功能擴充套件類
 * @date 2019/2/19 0019 12:51
 */
@GlideExtension
public class MyGlideExtension {

    private MyGlideExtension() {
    }

    //可以為方法任意新增引數,但要保證第一個引數為 RequestOptions
    /**
     * 設定通用的載入佔點陣圖和錯誤載入圖
     * @param options
     */
    @GlideOption
    public static void normalPlaceholder(RequestOptions options) {
        options.placeholder(R.drawable.ic_cloud_download_black_24dp).error(R.drawable.ic_error_black_24dp);
    }
}
/**GlideOptions類中生成對應的方法**/
/**
   * @see MyGlideExtension#normalPlaceholder(RequestOptions)
   */
  @CheckResult
  @NonNull
  public GlideOptions normalPlaceholder() {
    if (isAutoCloneEnabled()) {
      return clone().normalPlaceholder();
    }
    MyGlideExtension.normalPlaceholder(this);
    return this;
  }
/**GlideRequest類中生成對應的方法**/  
/**
   * @see GlideOptions#normalPlaceholder()
   */
  @CheckResult
  @NonNull
  public GlideRequest<TranscodeType> normalPlaceholder() {
    if (getMutableOptions() instanceof GlideOptions) {
      this.requestOptions = ((GlideOptions) getMutableOptions()).normalPlaceholder();
    } else {
      this.requestOptions = new GlideOptions().apply(this.requestOptions).normalPlaceholder();
    }
    return this;
  }
複製程式碼
  • 如上程式碼所示,我們可以通過@GlideExtension註解設定自己功能擴充套件類,使用@GlideOption註解標註對贏擴充套件功能靜態方法,重構專案後Glide註解處理器則會自動在GlideOptions物件和GlideRequest物件中生成相應的方法能夠被我們呼叫
//呼叫我們剛剛設定的擴充套件功能方法
GlideApp.with(this).load(IMAGE_URL)
                .normalPlaceholder()
                .into(mImageView);
複製程式碼

GlideType

  • GlideType註解是用於擴充套件RequestManager的,同理擴充套件的方法第一個引數必須是RequestManager,並設定型別為載入資源型別,該註解主要作用就是擴充套件Glide支援載入資源的型別,以下舉出官方文件支援gif的一個例子,還是在我們剛剛擴充套件功能類中。
@GlideExtension
public class MyGlideExtension {

    private static final RequestOptions DECODE_TYPE_GIF = decodeTypeOf(GifDrawable.class).lock();

    @GlideType(GifDrawable.class)
    public static void asMyGif(RequestBuilder<GifDrawable> requestBuilder) {
        requestBuilder
                .transition(new DrawableTransitionOptions())
                .apply(DECODE_TYPE_GIF);
    }
}

/**GlideRequests類中生成的asMyGif方法**/

/**
   * @see MyGlideExtension#asMyGif(RequestBuilder)
   */
  @NonNull
  @CheckResult
  public GlideRequest<GifDrawable> asMyGif() {
    GlideRequest<GifDrawable> requestBuilder = this.as(GifDrawable.class);
    MyGlideExtension.asMyGif(requestBuilder);
    return requestBuilder;
  }
複製程式碼
  • 同理在我們載入Gif資源的時候可以直接使用
 GlideApp.with(this).asMyGif().load(IMAGE_URL)
                .into(mImageView);
複製程式碼

原始碼閱讀方法思路

  • 看了這麼多原始碼,其實我想說說框架原始碼閱讀的方法思路:
    • 1.首先自己能把框架大體的流程走一遍,然後根據自己剛剛的思路把文章寫出來,在寫文章的同時也能發現自己剛剛的思路是否有問題,慢慢糾正
    • 2.文章寫完,把整體流程圖畫出來,畫圖的過程一個是複習思路,還可以讓自己對原始碼邏輯更加清晰
    • 3.閱讀框架原始碼時看到英文註釋可以先理解其含義,在你看原始碼沒頭緒的時候往往思路就在註釋中,如果對原始碼中一個類很迷惑,可以直接看該類的頭部註釋往往註明了該類的作用。

最後說點

  • 到此,真的很想大叫一聲宣洩一下,Glide原始碼就像一座山,一座高峰,你必須沉住氣,慢慢的解讀,要不然稍不留神就會掉入程式碼的海洋,迷失方向。回頭看看,你不得不感嘆正式由於Glide原始碼中成千上萬行的程式碼,才造就了這樣一個強大的框架。最後,也非常感謝您閱讀我的文章,文章中如果有錯誤,請大家給我提出來,大家一起學習進步,如果覺得我的文章給予你幫助,也請給我一個喜歡和關注,同時也歡迎訪問我的個人部落格

  • 參考連結

相關文章