從源碼角度深刻理解Glide4(下)

image

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

Glide緩存

  • Glide加載默認狀況下能夠分爲三級緩存,哪三級呢?他們分別是內存、磁盤和網絡。git

  • 默認狀況下,Glide 會在開始一個新的圖片請求以前檢查如下多級的緩存:github

    • 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源碼中成千上萬行的代碼,才造就了這樣一個強大的框架。最後,也很是感謝您閱讀個人文章,文章中若是有錯誤,請你們給我提出來,你們一塊兒學習進步,若是以爲個人文章給予你幫助,也請給我一個喜歡和關注,同時也歡迎訪問個人我的博客緩存

  • 參考連接bash

相關文章
相關標籤/搜索