Glide源碼分析之緩存機制

??2017年9月份,記錄了Glide4.0的集成和使用注意事項,后續(xù)一直沒有對Glide的源碼進行深入學習,接下來將對Glide進行逐步深入學習鸭丛,首先從Glide的緩存機制開始。
??我們都知道媒惕,Glide緩存分為內存緩存和磁盤緩存系吩,內存緩存的主要作用是防止應用重復將圖片數據讀取到內存當中,而硬盤緩存的主要作用是防止應用重復從網絡或其他地方重復下載和讀取數據妒蔚。通過這樣的緩存機制,不僅提升了圖片加載速度月弛,還減少了不必要的流量消耗電量消耗等肴盏。那么緩存機制是怎樣的呢?下面就開始對緩存流程進行分析帽衙。
??注:以下分析的前提條件是允許內存緩存和磁盤緩存菜皂。

內存緩存

??這里不對Glide執(zhí)行流程從頭開始分析,直接切入主題厉萝,從Glide緩存管理類Engine的load方法開始恍飘。

1、緩存Key

??Glide緩存是以Key-Value形式保存谴垫。從load方法一開始章母,通過EngineKeyFactory的bulid方法生成EngineKey對象,這個EngineKey就是Glide中的內存緩存Key翩剪,為了保證Key的唯一性乳怎,傳入了8個參數進行構建EngineKey,并且重寫了equals和hashCode方法前弯。最后通過Key從內存緩存中獲取目標緩存資源EngineResource(真正的資源是被封裝到EngineResource中)對象蚪缀。

//EngineKey.java
class EngineKey implements Key {
 /**
    * 生成key
    * model:AppGlide.with(context).load(model).into(imageView) 秫逝,請求加載的 model(File, Url, Url)
    * signature:簽名,可選
    * width, heigh:想要的目標資源大小询枚,override(width, heigh)
    * transformations:資源變換违帆,可選
    * resourceClass:修改過的資源類型Class,eg:Bitmap.class  GifDrawable.class
    * transcodeClass:指定加載的圖片格式金蜀,eg:asBitmap()前方,加載的圖片資源格式為Bitmap.classs
    * options:額外添加的任何選項
    */
  EngineKey(Object model,Key signature,int width,int height,
      Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass,
      Class<?> transcodeClass, Options options) {
    ......
  }
}

//Engine.java load方法
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
2、讀取緩存

??Glide內存緩存策略采用二級緩存廉油,分別是ActiveResources和MemoryCache惠险。
??ActiveResources存儲的是當前正在使用的資源,內部是通過Map<Key, ResourceWeakReference>來保存弱引用資源抒线,資源在保存到HashMap之前會被ResourceWeakReference引用班巩。

//ActiveResources.java
final class ActiveResources {
   ......
  @VisibleForTesting
  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

  /**
  * 存儲當前正在顯示的資源
  */
  void activate(Key key, EngineResource<?> resource) {
   //將資源保存在弱引用中
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key,
            resource,
            getReferenceQueue(),
            isActiveResourceRetentionAllowed);
    //將若弱引用資源保存到HashMap中
    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }
  ......
}

??MemoryCache的實現類是LruResourceCache,主要實現是采用了LRU算法嘶炭,存儲不使用中的圖片資源(即圖片資源EngineResource中的引用數acquired=0)抱慌。

//GlideBuilder.java
public final class GlideBuilder {
  private MemoryCache memoryCache;
  if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
  ......
}
//------------------------------------------------------------------------
//LruResourceCache.java
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
  ......
}
//------------------------------------------------------------------------
//LruCache.java
public class LruCache<T, Y> {
  private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
  ......
}

從源碼中可以了解讀取內存緩存的大致流程:
(1)通過Key從ActiveResources尋找目標資源。
(2)若(1)中未找到目標資源眨猎,則通過Key從MemoryCache中尋找目標資源抑进。
(3)若(1)或者(2)中找到目標資源,則通過ResourceCallback的onResourceReady方法將獲取目標資源和緩存來源回調給SingleRequest類(SingleRequest中實現了ResourceCallback接口)睡陪,在SingleRequest的onResourceReady方法中將目標資源傳遞給我們設置的RequestListener寺渗,若沒有RequestListener實例,則傳遞給封裝了ImageView的Target(若into()方法中傳入的是ImageVIew兰迫,Glide內部通過ImageViewTargetFactory創(chuàng)建BitmapImageViewTarget或DrawableImageViewTarget)信殊,將目標資源設置到ImageView中。

//Engine.java
public class Engine implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {
  ......
  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){

      EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);

      //通過Key從ActiveResources尋找目標資源汁果。
      EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
      if (active != null) {
        //通過ResourceCallback的onResourceReady方法將獲取目標資源和緩存來源回調給SingleRequest類
        cb.onResourceReady(active, DataSource.MEMORY_CACHE);
        if (VERBOSE_IS_LOGGABLE) {
          logWithTimeAndKey("Loaded resource from active resources", startTime, key);
        }
        return null;
      }
      //通過Key從MemoryCache中尋找目標資源
      EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
      if (cached != null) {
        //通過ResourceCallback的onResourceReady方法將獲取目標資源和緩存來源回調給
        cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
        if (VERBOSE_IS_LOGGABLE) {
          logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }
  ......
}

  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
    //目標資源引用數acquired加1
      active.acquire();
    }
    return active;
  }

  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }

    //從MemoryCache查找到目標資源涡拘,刪除MemoryCache集合中的目標資源引用
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      //目標資源引用數acquired加1
      cached.acquire();
    //從緩存中獲取資源顯示的同時,將資源添加到activeResources
      activeResources.activate(key, cached);
    }
    return cached;
  }

  private EngineResource<?> getEngineResourceFromCache(Key key) {
    //刪除MemoryCache集合中的目標資源
    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;
  }

  ......
}

//SingleRequest.java
public final class SingleRequest<R> implements Request,
    SizeReadyCallback,
    ResourceCallback,
    FactoryPools.Poolable {
  ......

  /**
   * A callback method that should never be invoked directly.
   */
  @SuppressWarnings("unchecked")
  @Override
  public void onResourceReady(Resource<?> resource, DataSource dataSource) {
     ......
    onResourceReady((Resource<R>) resource, (R) received, dataSource);
  }

private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) {
    // We must call isFirstReadyResource before setting status.
    boolean isFirstResource = isFirstReadyResource();
    status = Status.COMPLETE;
    this.resource = resource;

    if (glideContext.getLogLevel() <= Log.DEBUG) {
      Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from "
          + dataSource + " for " + model + " with size [" + width + "x" + height + "] in "
          + LogTime.getElapsedMillis(startTime) + " ms");
    }

    isCallingCallbacks = true;
    try {
      boolean anyListenerHandledUpdatingTarget = false;
      //將資源回調給我們實現的RequestListener的onResourceReady方法
      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);

      //若存在RequestListener接口實例据德,則不會將資源回調給Target(默認target:BitmapImageViewTarget和DrawableImageViewTarget鳄乏,由ImageViewTargetFactory創(chuàng)建)
      if (!anyListenerHandledUpdatingTarget) {
        Transition<? super R> animation =
            animationFactory.build(dataSource, isFirstResource);
        target.onResourceReady(result, animation);
      }
    } finally {
      isCallingCallbacks = false;
    }

    notifyLoadSuccess();
  }
  ......
}
3、寫入緩存

??還記得我們在MemoryCache介紹中提到的EngineResource中的圖片資源引用數acquired嗎棘利?當資源被使用時(正在使用的資源保存到ActiveResources中)橱野,會調用acquire,將變量值+1,當資源被釋放時赡译,會調用release()方法仲吏,直到acquired=0,表示資源沒有被使用,這時候通過會調用onResourceReleased方法裹唆,將資源存儲到MemoryCache中誓斥。

//EngineResource.java
class EngineResource<Z> implements Resource<Z> {
  private int acquired;
  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;
  }

  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) {
      //ResourceListener接口由Engine實現
      listener.onResourceReleased(key, this);
    }
  }
  ......
}
//------------------------------------------------------------------------
//Engine.java
public class Engine implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {

    @Override
    public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
      Util.assertMainThread();
      activeResources.deactivate(cacheKey);
      if (resource.isCacheable()) {
        //將不使用的圖片資源緩存到MemoryCache中
        cache.put(cacheKey, resource);
      } else {
        resourceRecycler.recycle(resource);
      }
   }
  ......
}

磁盤緩存

1、緩存Key

(1)未修改過的本地緩存資源Key#ResourceCacheKey

//ResourceCacheKey.java
final class ResourceCacheKey implements Key{
  ResourceCacheKey(ArrayPool arrayPool,Key sourceKey,Key signature,int width,int height,
      Transformation<?> appliedTransformation,Class<?> decodedResourceClass,Options options) {
    ......
  }
}

//ResourceCacheGenerator.java  startNext()
currentKey = new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());

(2)修改過的本地資源緩存Key#DataCacheKey

//DataCacheKey.java
final class DataCacheKey implements Key{
  DataCacheKey(Key sourceKey, Key signature) {
    this.sourceKey = sourceKey;
    this.signature = signature;
  }
}
//DataCacheGenerator.java  startNext()
 Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
2许帐、讀取緩存

??當在內存緩存中獲取不到目標圖片資源時劳坑,在Engine的load方法中,會繼續(xù)創(chuàng)建任務EngineJob和DecodeJob來從磁盤緩存中查找目標資源成畦,若磁盤中也找不到距芬,則會從網絡下載資源,下面我們進行具體分析循帐。
??在Engine的load方法中通過調用EngineJob的start方法啟動任務框仔,進入start方法中,可以看到真正去執(zhí)行任務的是DecodeJob(DecodeJob實現了Runnable接口)拄养,通過我們選擇的緩存策略离斩,來選擇我們將要提交任務的線程池。

//Engine.java load方法
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
    if (current != null) {
      current.addCallback(cb);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }
    //創(chuàng)建任務
    EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

   //創(chuàng)建任務
    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

    jobs.put(key, engineJob);

    //ResourceCallback接口由SingleRequest實現
    engineJob.addCallback(cb);
    //開啟任務
    engineJob.start(decodeJob);

??通過磁盤緩存策略瘪匿,選擇線程池跛梗,若緩存策略是DiskCacheStrategy.ALL / DiskCacheStrategy.RESOURCE / DiskCacheStrategy.AUTOMATIC / DiskCacheStrategy.DATA,則允許從修改或者未修改的磁盤緩存資源中獲取棋弥,選擇diskCacheExecutor線程池來執(zhí)行任務核偿。

//EngineJob.java
 /**
   * Returns true if this job will attempt to decode a resource from the disk cache, and false if it
   * will always decode from source.
   */
 public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    //是否從磁盤緩存中解碼資源
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }
//------------------------------------------------------------------------
//DecodeJob.java
/**
   * Returns true if this job will attempt to decode a resource from the disk cache, and false if it
   * will always decode from source.
   */
  boolean willDecodeFromCache() {
    Stage firstStage = getNextStage(Stage.INITIALIZE);
    //當firstStage是修改或者未修改的磁盤緩存資源,返回true
    return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
  }

private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE:
        //所選的緩存策略是否允許從修改過的磁盤緩存資源中解碼資源顽染,若是漾岳,則返回RESOURCE_CACHE,反之家乘,繼續(xù)調用getNextStage方法遞歸
        return diskCacheStrategy.decodeCachedResource()
            ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE);
      case RESOURCE_CACHE:
        //所選的緩存策略是否允許從未修改的原始磁盤緩存資源中解碼資源蝗羊,若是,則返回Stage.DATA_CACHE仁锯,反之,繼續(xù)調用getNextStage方法遞歸
        return diskCacheStrategy.decodeCachedData()
            ? Stage.DATA_CACHE : getNextStage(Stage.DATA_CACHE);
      case DATA_CACHE:
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        //若只從磁盤緩存中獲取資源翔悠,若此時緩存中沒有目標資源业崖,也不會從網絡進行加載資源
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }
//------------------------------------------------------------------------
//DiskCacheStrategy.java
DiskCacheStrategy類種各緩存策略中對decodeCachedResource和decodeCachedData的實現這里就不貼出來,可以自行查看蓄愁。
boolean decodeCachedResource() 返回true的策略有ALL双炕、RESOURCE、AUTOMATIC
boolean decodeCachedData() 返回true的策略有AUTOMATIC撮抓、ALL妇斤、DATA

??DecodeJob提交到diskCacheExecutor線程池中后,線程池為DecodeJob分配線程執(zhí)行任務,DecodeJob實現了Runnable接口站超,所以最終調用的是DecodeJob的run方法荸恕。接著調用runWrapped方法,方法中的runReason變量在DecodeJob的構造中賦了初始值INITIALIZE死相。然后融求,調用getNextStage方法,來獲取磁盤的加載策略Stage算撮。最后生宛,在getNextGenerator方法中通過磁盤加載策略來創(chuàng)建對應加載資源的生成器對象,調用runGenerators方法加載資源肮柜。

//DecodeJob.java
private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        //加載資源
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        //加載資源
        runGenerators();
        break;
      case DECODE_DATA:
        //處理已經load到的數據
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }

??磁盤的加載策略Stage陷舅,由我們在RequestOptions中設置的緩存策略決定。runReason 初始默認值是INITIALIZE审洞,所以這里current最開始傳入的是Stage.INITIALIZE莱睁,diskCacheStrategy這個值是由RequestOptions傳入,默認緩存策略是DiskCacheStrategy.AUTOMATIC预明。

??若此時的緩存策略是DiskCacheStrategy.ALL或 DiskCacheStrategy.RESOURCE或DiskCacheStrategy.AUTOMATIC缩赛,diskCacheStrategy.decodeCachedResource()將返回true,成員變量stage賦值為Stage.RESOURCE_CACHE撰糠。

??若diskCacheStrategy.decodeCachedResource()返回false酥馍,繼續(xù)調用getNextStage方法,current傳入Stage.RESOURCE_CACHE阅酪。若緩存策略是DiskCacheStrategy.AUTOMATIC或DiskCacheStrategy.ALL或DiskCacheStrategy.DATA辽装,diskCacheStrategy.decodeCachedData()將返回true,成員變量stage賦值為Stage. DATA_CACHE碗旅。

??若diskCacheStrategy.decodeCachedData()返回false拆撼,繼續(xù)調用getNextStage方法,current傳入Stage. DATA_CACHE辉词,若此時設置了只從磁盤緩存中獲取資源必孤,成員變量stage賦值為Stage.FINISHED,反之成員變量stage賦值為Stage. SOURCE瑞躺。

??磁盤加載數據的策略有三種RESOURCE_CACHE敷搪,DATA_CACHE,SOURCE幢哨。
??RESOURCE_CACHE:從修改過的本地資源緩存中獲取數據赡勘。
??DATA_CACHE:從未修改過的本地緩存中獲取數據。
??SOURCE:從原始的資源中獲取捞镰,可能是服務器闸与,也可能是本地的一些原始資源毙替。

//DecodeJob.java
  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:
        // Skip loading from source if the user opted to only retrieve the resource from cache.
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }

??獲取了磁盤加載數據的策略后,根據磁盤加載數據策略創(chuàng)建對應的加載資源的生成器践樱。

//DecodeJob.java
  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);
    }
  }

??獲得生成器對象后厂画,調用runGenerators方法執(zhí)行生成器,主要是調用生成器的startNext方法映胁,當startNext啟動了DataFetcher加載數據的方法loadData時木羹,返回true。從while的判斷條件中可知解孙,若任務被取消或者數據加載成功啟動(isCancelled和isStarted其中之一為true)坑填,整個while循環(huán)就會結束,再接下來的判斷中弛姜,stage狀態(tài)為完成或者任務被取消并且數據加載沒有啟動脐瑰,則直接notifyFailed,此次加載失敗廷臼。若while條件判斷中任務未被取消(isCancelled為true)且加載數據未成功啟動苍在,則執(zhí)行while循環(huán)體中的邏輯。

??執(zhí)行循環(huán)體荠商,若當前的生成器加載數據未成功啟動寂恬,則通過getNextStage和getNextGenerator獲取下一個磁盤加載策略和生成器,直到啟動成功或者任務取消莱没,循環(huán)結束初肉。簡單來說,會依次從ResourceCacheGenerator->DataCacheGenerator->SourceGenerator這樣一個鏈執(zhí)行饰躲,只要其中一個的startNext方法返回為true牙咏,則不再尋找下一個Generator。

??例如嘹裂,第一次循環(huán)currentGenerator為ResourceCacheGenerator妄壶,假設未啟動成功,再次進入getNextStage寄狼,當前stage為Stage.RESOURCE_CACHE丁寄,所以獲取的下一個stage是Stage.DATA_CACHE,通過stage獲取下一個生成器DataCacheGenerator泊愧。若第二次循環(huán)還未啟動成功狡逢,并且onlyRetrieveFromCache是false,返回下一個stage為Stage.SOURCE拼卵,下一個生成器是SourceGenerator,當stage是Stage.SOURCE蛮艰,內部循環(huán)被return腋腮,請求reschedule方法重新調度雀彼,調度的最終也還是調用runGenerators()。

//DecodeJob.java
  private void runGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {
      stage = getNextStage(stage);
      currentGenerator = getNextGenerator();

      if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }
    }
    // We've run out of stages and generators, give up.
    if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
      notifyFailed();
    }

    // Otherwise a generator started a new load and we expect to be called back in
    // onDataFetcherReady.
  }

??獲取緩存數據即寡,調用對應DataFetcherGenerator的startNext方法徊哑。
??(1)ResourceCacheGenerator#startNext
??首先,通過緩存key獲取對應緩存文件cacheFile和對應的加載器集合modelLoaders(cacheFile是File類型聪富,在Glide.java中注冊的加載File類型的加載器有FileLoader和ByteBufferFileLoader)莺丑。接著,遍歷加載器墩蔓,通過加載器去構建LoadData對象梢莽,LoadData內部會創(chuàng)建一個DataFetcher對象,以FileLoader加載器為例奸披,LoadData內部會創(chuàng)建一個FileFetcher對象昏名。然后,通過調用FileFetcher的loadData方法阵面,從緩存中加載數據轻局。最后,將獲取的數據通過層層往上回調样刷,顯示在我們的目標ImageView上仑扑。

??將獲取的緩存數據層層回調順序:FileLoader.java#loadData()—>ResourceCacheGenerator.java#onDataReady()—>DecodeJob.java#onDataFetcherReady() —> EngineJob.java#onResourceReady() ,通過handler切到主線程進行處理—>SingleRequest.java#onResourceReady()—>Target.java#onResourceReady()—>ImageView

1)FileLoader.java#loadData()
??獲取的緩存數據通過DataFetcher.DataCallback的onDataReady()方法向ResourceCacheGenerator傳遞(ResourceCacheGenerator實現了DataFetcher.DataCallback接口)置鼻。

2)ResourceCacheGenerator.java#onDataReady()
??緩存數據通過DataFetcherGenerator.FetcherReadyCallback的onDataFetcherReady()方法向DecodeJob傳遞(DecodeJob實現了DataFetcherGenerator.FetcherReadyCallback接口)

3)DecodeJob.java#onDataFetcherReady()
??首先镇饮,調用decodeFromRetrievedData()對獲取的數據進行處理(解碼/變換等操作)。然后沃疮,將處理好的數據傳遞到notifyComplete()方法盒让。最后,在notifyComplete()方法中調用DecodeJob.Callback的onResourceReady()方法司蔬,將數據傳遞到EngineJob(EngineJob實現了DecodeJob.Callback接口)邑茄。

4)EngineJob.java#onResourceReady()
??首先,通過Handler將獲取的緩存數據傳遞到主線程中進行處理俊啼。然后肺缕,調用handleResultOnMainThread()方法,將從緩存獲取的數據封裝到EngineResource中授帕。最后同木,將封裝好的EngineResource通過ResourceCallback的onResourceReady()方法傳遞到SingleRequest(SingleRequest實現了ResourceCallback接口)。

5)SingleRequest.java#onResourceReady()
GlideApp.with(context).load(url).diskCacheStrategy(DiskCacheStrategy.ALL).listener(new RequestListener<Drawable>() {...}).into(imageView/Target);
??還記得上面我們加載圖片時設置的listener和into嗎跛十?在onResourceReady中是這樣處理的彤路,若存在RequestListener接口實例,則不會將獲取到的資源回調給Target芥映,反之 會傳遞給Target洲尊。into中若我們設置ImageView远豺,源碼中會根據我們選擇加載資源的類型(例如:asDrawable()、asBitmap())坞嘀,為我們創(chuàng)建默認的Target(BitmapImageViewTarget或DrawableImageViewTarget躯护,由ImageViewTargetFactory創(chuàng)建),無論是哪種Target丽涩,最終都會將獲取到的資源傳遞給Target的onResourceReady()方法棺滞。

6)Target.java#onResourceReady()
??以DrawableImageViewTarget為例,首先矢渊,獲取的資源傳遞到了它的父類ImageViewTarget中onResourceReady()方法中继准,然后,調用setResourceInternal()方法昆淡,在方法中通過調用setResource方法設置資源锰瘸,這個方法在父類中是一個抽象方法,那么昂灵,具體實現在DrawableImageViewTarget類中避凝,最后,在具體實現的setResource方法中將獲取的圖片資源設置到ImageVew中眨补。

//ResourceCacheGenerator.java

@Override
  public boolean startNext() {
    List<Key> sourceIds = helper.getCacheKeys();
    if (sourceIds.isEmpty()) {
      return false;
    }
    List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
    if (resourceClasses.isEmpty()) {
      if (File.class.equals(helper.getTranscodeClass())) {
        return false;
      }
      throw new IllegalStateException(
         "Failed to find any load path from " + helper.getModelClass() + " to "
             + helper.getTranscodeClass());
    }
    while (modelLoaders == null || !hasNextModelLoader()) {
      resourceClassIndex++;
      if (resourceClassIndex >= resourceClasses.size()) {
        sourceIdIndex++;
        if (sourceIdIndex >= sourceIds.size()) {
          return false;
        }
        resourceClassIndex = 0;
      }

      Key sourceId = sourceIds.get(sourceIdIndex);
      Class<?> resourceClass = resourceClasses.get(resourceClassIndex);
      Transformation<?> transformation = helper.getTransformation(resourceClass);
      // PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway,
      // we only run until the first one succeeds, the loop runs for only a limited
      // number of iterations on the order of 10-20 in the worst case.
      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;
  }
//FileLoader.java

  @Override
  public LoadData<Data> buildLoadData(@NonNull File model, int width, int height,
      @NonNull Options options) {
    return new LoadData<>(new ObjectKey(model), new FileFetcher<>(model, fileOpener));
  }

  @Override
  public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) {
     try {
       data = opener.open(file);
     } catch (FileNotFoundException e) {
       if (Log.isLoggable(TAG, Log.DEBUG)) {
         Log.d(TAG, "Failed to open file", e);
       }
       callback.onLoadFailed(e);
       return;
     }
     callback.onDataReady(data);
  }

??(2)DataCacheGenerator#startNext
??與 ResourceCacheGenerator的流程差不多管削,區(qū)別在與它們的緩存key不一樣,前者是ResourceCacheKey撑螺,后者是DataCacheKey含思,它們構造key所需的參數不同,具體請看源碼甘晤,這里就不介紹了含潘。

//DataCacheGenerator.java
@Override
  public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      sourceIdIndex++;
      if (sourceIdIndex >= cacheKeys.size()) {
        return false;
      }

      Key sourceId = cacheKeys.get(sourceIdIndex);
      // PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times
      // and the actions it performs are much more expensive than a single allocation.
      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
      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;
  }

??(3)SourceGenerator#startNext
?? dataToCache初始是null,所以线婚,我們先看While里面的流程遏弱。磁盤中不存在緩存,所以這是時候的model是GlideUrl塞弊,通過GlideUrl獲取到加載器類型是HttpGlideUrlLoader漱逸,LoadData中對應的DataFetcher是HttpUrlFetcher。調用HttpUrlFetcher的loadData方法游沿,從網絡上或者其他地方下載資源饰抒,然后將結果通過DataFetcher.DataCallback的onDataReady()方法向SourceGenerator傳遞(SourceGenerator實現了DataFetcher.DataCallback接口)。在SourceGenerator的onDataReady()中判斷數據來源诀黍,若數據不是來自遠程袋坑,則和之前所說的將數據層層往上回調傳遞流程是一樣的;若來自遠程眯勾,則將數據賦值給dataToCache并且重新調度咒彤,最終會再次調用SourceGenerator#startNext疆柔,這時dataToCache不等于null了。
?? dataToCache不等于null時镶柱,調用cacheData()方法,將數據編碼作為未修改的資源存儲到磁盤中模叙,然后歇拆,創(chuàng)建DataCacheGenerator生成器,最后范咨,按照DataCacheGenerator#startNext流程執(zhí)行故觅。

//SourceGenerator.java
  @Override
  public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }

    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;

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

  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);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished encoding source to cache"
            + ", key: " + originalKey
            + ", data: " + dataToCache
            + ", encoder: " + encoder
            + ", duration: " + LogTime.getElapsedMillis(startTime));
      }
    } finally {
      loadData.fetcher.cleanup();
    }

    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }
3、寫入緩存

??上面我們有提到了無磁盤緩存數據時渠啊,通過SourceGenerator獲取網絡或者其他地方的數據输吏,在SourceGenerator的dataToCache不為null時,會調用cacheData方法將資源作為未修改過的資源緩存到磁盤中替蛉。
??還有一處寫入磁盤緩存的地方贯溅,那就我們在層層往上回調數據時,傳到DecodeJob躲查,對獲取的資源數據進行處理后它浅,在notifyEncodeAndRelease()方法中會將經過解碼變換好的資源編碼存儲到磁盤緩存中。

//DecodeJob.java
private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
    if (resource instanceof Initializable) {
      ((Initializable) resource).initialize();
    }

    Resource<R> result = resource;
    LockedResource<R> lockedResource = null;
    if (deferredEncodeManager.hasResourceToEncode()) {
      lockedResource = LockedResource.obtain(resource);
      result = lockedResource;
    }

    notifyComplete(result, dataSource);

    stage = Stage.ENCODE;
    try {
      //是否存在編碼器
      //Glide.java中默認注冊的編碼器有ByteBufferEncoder镣煮、StreamEncoder姐霍、BitmapEncoder、BitmapDrawableEncoder典唇、GifDrawableEncoder
      if (deferredEncodeManager.hasResourceToEncode()) {
        //將處理好的資源編碼存儲到磁盤緩存中
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
      if (lockedResource != null) {
        lockedResource.unlock();
      }
    }
    // Call onEncodeComplete outside the finally block so that it's not called if the encode process
    // throws.
    onEncodeComplete();
  }

  /**
  * 資源解碼變換
  */
  @Synthetic
  @NonNull
  <Z> Resource<Z> onResourceDecoded(DataSource dataSource,
      @NonNull Resource<Z> decoded) {
    @SuppressWarnings("unchecked")
    Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass();
    Transformation<Z> appliedTransformation = null;
    Resource<Z> transformed = decoded;
    if (dataSource != DataSource.RESOURCE_DISK_CACHE) {
      //圖片資源變換
      appliedTransformation = decodeHelper.getTransformation(resourceSubClass);
      transformed = appliedTransformation.transform(glideContext, decoded, width, height);
    }
    // TODO: Make this the responsibility of the Transformation.
    if (!decoded.equals(transformed)) {
      decoded.recycle();
    }

    final EncodeStrategy encodeStrategy;
    final ResourceEncoder<Z> encoder;
    //Glide.java中默認注冊的編碼器:Encoder:ByteBufferEncoder镊折、StreamEncoder、BitmapEncoder介衔、BitmapDrawableEncoder恨胚、GifDrawableEncoder
    if (decodeHelper.isResourceEncoderAvailable(transformed)) {
      encoder = decodeHelper.getResultEncoder(transformed);
      encodeStrategy = encoder.getEncodeStrategy(options);
    } else {
      encoder = null;
      encodeStrategy = EncodeStrategy.NONE;
    }

    Resource<Z> result = transformed;
    boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey);
    if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource,
        encodeStrategy)) {
      //需要緩存變換后的資源
      if (encoder == null) {
        throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass());
      }
      final Key key;
      switch (encodeStrategy) {
        case SOURCE:
          key = new DataCacheKey(currentSourceKey, signature);
          break;
        case TRANSFORMED:
          key =
              new ResourceCacheKey(
                  decodeHelper.getArrayPool(),
                  currentSourceKey,
                  signature,
                  width,
                  height,
                  appliedTransformation,
                  resourceSubClass,
                  options);
          break;
        default:
          throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy);
      }

      LockedResource<Z> lockedResult = LockedResource.obtain(transformed);
      //這里的encoder將賦值給toEncode,toEncode!=null時hasResourceToEncode()返回true
      deferredEncodeManager.init(key, encoder, lockedResult);
      result = lockedResult;
    }
    return result;
  }

參考資料:
http://www.reibang.com/p/133adedd8860
http://www.reibang.com/p/e01f68802604

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末夜牡,一起剝皮案震驚了整個濱河市与纽,隨后出現的幾起案子,更是在濱河造成了極大的恐慌塘装,老刑警劉巖急迂,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異蹦肴,居然都是意外死亡僚碎,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門阴幌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來勺阐,“玉大人卷中,你說我怎么就攤上這事≡ǔ椋” “怎么了蟆豫?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長懒闷。 經常有香客問我十减,道長,這世上最難降的妖魔是什么愤估? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任帮辟,我火速辦了婚禮,結果婚禮上玩焰,老公的妹妹穿的比我還像新娘由驹。我一直安慰自己,他們只是感情好昔园,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布蔓榄。 她就那樣靜靜地躺著,像睡著了一般蒿赢。 火紅的嫁衣襯著肌膚如雪润樱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天羡棵,我揣著相機與錄音壹若,去河邊找鬼。 笑死皂冰,一個胖子當著我的面吹牛店展,可吹牛的內容都是我干的。 我是一名探鬼主播秃流,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赂蕴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舶胀?” 一聲冷哼從身側響起概说,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嚣伐,沒想到半個月后糖赔,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡轩端,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年放典,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡奋构,死狀恐怖壳影,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情弥臼,我是刑警寧澤宴咧,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站醋火,受9級特大地震影響悠汽,放射性物質發(fā)生泄漏。R本人自食惡果不足惜芥驳,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茬高。 院中可真熱鬧兆旬,春花似錦、人聲如沸怎栽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熏瞄。三九已至脚祟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間强饮,已是汗流浹背由桌。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留邮丰,地道東北人行您。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像剪廉,于是被迫代替她去往敵國和親娃循。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容