[Glide系列第3篇]Glide源碼分析之緩存處理

Android緩存機(jī)制:如果沒有緩存社露,在大量的網(wǎng)絡(luò)請求從遠(yuǎn)程獲取圖片時(shí)會(huì)造成網(wǎng)絡(luò)流量的浪費(fèi)逛钻,加載速度較慢,用戶體驗(yàn)不好

關(guān)于學(xué)習(xí)Glide緩存原理前十分建議你先了解圖片加載的流程,在這基礎(chǔ)上再進(jìn)行學(xué)習(xí)會(huì)更加上手。然后可以看思維導(dǎo)圖年枕,從宏觀角度理解Glide加載。本文的源碼基于V4版本

Glide系列文章

Glide源碼分析流程思維導(dǎo)圖

【兩篇就懂系列】Glide源碼分析之加載圖片流程(1/2)

【兩篇就懂系列】Glide源碼分析之加載圖片流程(2/2)

Glide圖片加載庫從v3遷移到v4的改變和使用


【Glide的緩存】

在緩存這一功能上乎完,Glide將它分成了兩個(gè)模塊熏兄,一個(gè)是內(nèi)存緩存,一個(gè)是硬盤緩存囱怕。同時(shí)內(nèi)存緩存又分為兩級霍弹,一級是LruCache緩存,一級是弱引用緩存娃弓。
  • 內(nèi)存緩存的作用:防止應(yīng)用重復(fù)將圖片數(shù)據(jù)讀取到內(nèi)存當(dāng)中。
    • LruCache緩存:不在使用中的圖片使用LruCache來進(jìn)行緩存岛宦。
    • 弱引用緩存:把正在使用中的圖片使用弱引用來進(jìn)行緩存台丛。
      【這樣的目的保護(hù)正在使用的資源不會(huì)被LruCache算法回收±危】
  • 硬盤緩存的作用:防止應(yīng)用重復(fù)從網(wǎng)絡(luò)或其他地方重復(fù)下載和讀取數(shù)據(jù)挽霉。

默認(rèn)情況下,Glide 會(huì)在開始一個(gè)新的圖片請求之前檢查以下多級的緩存:

  • 內(nèi)存緩存 (Memory cache) - 該圖片是否最近被加載過并仍存在于內(nèi)存中变汪?即LruCache緩存侠坎。
  • 活動(dòng)資源 (Active Resources) - 現(xiàn)在是否有另一個(gè) View 正在展示這張圖片?也就是弱引用緩存裙盾。
  • 資源類型(Resource) - 該圖片是否之前曾被解碼实胸、轉(zhuǎn)換并寫入過磁盤緩存?
  • 數(shù)據(jù)來源 (Data) - 構(gòu)建這個(gè)圖片的資源是否之前曾被寫入過文件緩存番官?
    前兩步檢查圖片是否在內(nèi)存中庐完,如果是則直接返回圖片。后兩步則檢查圖片是否在磁盤上徘熔,以便快速但異步地返回圖片门躯。
    如果四個(gè)步驟都未能找到圖片,則Glide會(huì)返回到原始資源以取回?cái)?shù)據(jù)(原始文件酷师,Uri, Url等)

結(jié)合源碼去分析Glide緩存

首先在[圖片加載源碼分析一]的文章中讶凉,我們在通過單例獲取Glide的實(shí)例時(shí)染乌,調(diào)用過checkAndInitializeGlide(context)這個(gè)方法,在具體的方法里有一段代碼是通過GlideBuilder.build初始化一些對象懂讯,如下

GlideBuilder類
public Glide build(Context context) {
    if (sourceExecutor == null) {
      sourceExecutor = GlideExecutor.newSourceExecutor();
    }

    if (diskCacheExecutor == null) {
      diskCacheExecutor = GlideExecutor.newDiskCacheExecutor();
    }

    if (memorySizeCalculator == null) {
      memorySizeCalculator = new MemorySizeCalculator.Builder(context).build();
    }

    if (connectivityMonitorFactory == null) {
      connectivityMonitorFactory = new DefaultConnectivityMonitorFactory();
    }

    if (bitmapPool == null) {
      int size = memorySizeCalculator.getBitmapPoolSize();
      if (size > 0) {
        bitmapPool = new LruBitmapPool(size);
      } else {
        bitmapPool = new BitmapPoolAdapter();
      }
    }

    if (arrayPool == null) {
      arrayPool = new LruArrayPool(memorySizeCalculator.getArrayPoolSizeInBytes());
    }
  //1
    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
  //2
    if (diskCacheFactory == null) {
      diskCacheFactory = new InternalCacheDiskCacheFactory(context);
    }

    if (engine == null) {
      engine =
          new Engine(
              memoryCache,
              diskCacheFactory,
              diskCacheExecutor,
              sourceExecutor,
              GlideExecutor.newUnlimitedSourceExecutor(),
              GlideExecutor.newAnimationExecutor());
    }

    RequestManagerRetriever requestManagerRetriever = new RequestManagerRetriever(
        requestManagerFactory);

    return new Glide(
        context,
        engine,
        memoryCache,
        bitmapPool,
        arrayPool,
        requestManagerRetriever,
        connectivityMonitorFactory,
        logLevel,
        defaultRequestOptions.lock(),
        defaultTransitionOptions);
  }

在我代碼中標(biāo)記1和2處慕匠,1處new出了一個(gè)LruResourceCache,并把它賦值到了memoryCache這個(gè)對象上面域醇。你沒有猜錯(cuò)台谊,這個(gè)就是Glide實(shí)現(xiàn)內(nèi)存緩存所使用的LruCache對象了。2處InternalCacheDiskCacheFactory是磁盤緩存(內(nèi)部存儲)所使用的工廠對象譬挚。同時(shí)在其中初始化了磁盤緩存的大小和文件的路徑锅铅。

創(chuàng)建好了這些對象說明我們已經(jīng)把準(zhǔn)備工作做好了。

【內(nèi)存緩存】

接口為MemoryCache减宣,Glide使用LruResourceCache作為默認(rèn)的內(nèi)存緩存盐须,該類是接口MemoryCache的一個(gè)缺省實(shí)現(xiàn)(接口的另一個(gè)實(shí)現(xiàn)類為MemoryCacheAdapter)。使用固定大小的內(nèi)存和 LRU 算法漆腌。LruResourceCache的大小由 Glide 的MemorySizeCalculator類來決定贼邓,這個(gè)類主要關(guān)注設(shè)備的內(nèi)存類型,設(shè)備 RAM 大小闷尿,以及屏幕分辨率塑径。

【內(nèi)存緩存的讀取】

我們先分析Glide從哪里讀取內(nèi)存緩存,以及內(nèi)存緩存的原理填具。

1. Lurcache算法
對于大多內(nèi)存緩存的實(shí)現(xiàn)统舀,我們通常會(huì)知道這樣一個(gè)算法,LruCache算法(Least Recently Used)劳景,也叫近期最少使用算法誉简。它的主要算法原理就是把最近使用的對象用強(qiáng)引用存儲在LinkedHashMap(雙向循環(huán)列表)中,并且把最近最少使用的對象在緩存值達(dá)到預(yù)設(shè)定值之前從內(nèi)存中移除盟广。淘汰最長時(shí)間未使用的對象
上面我們提到的LruResourceCache就是Glide實(shí)現(xiàn)內(nèi)存緩存所使用的LruCache對象了闷串,而這個(gè)類繼承LruCache。也是最近最少使用算法的具體實(shí)現(xiàn)筋量。
public class LruCache<T, Y> {
  private final LinkedHashMap<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);
  private final int initialMaxSize;
  private int maxSize;
  private int currentSize = 0;

  /**
   * Constructor for LruCache.
   *
   * @param size The maximum size of the cache, the units must match the units used in {@link
   *             #getSize(Object)}.
   */
  public LruCache(int size) {
    this.initialMaxSize = size;
    this.maxSize = size;
  }

  /**
   * Sets a size multiplier that will be applied to the size provided in the constructor to put the
   * new size of the cache. If the new size is less than the current size, entries will be evicted
   * until the current size is less than or equal to the new size.
   *
   * @param multiplier The multiplier to apply.
   */
  public synchronized void setSizeMultiplier(float multiplier) {
    if (multiplier < 0) {
      throw new IllegalArgumentException("Multiplier must be >= 0");
    }
    maxSize = Math.round(initialMaxSize * multiplier);
    evict();
  }

  /**
   * Returns the size of a given item, defaulting to one. The units must match those used in the
   * size passed in to the constructor. Subclasses can override this method to return sizes in
   * various units, usually bytes.
   *
   * @param item The item to get the size of.
   */
  protected int getSize(Y item) {
    return 1;
  }

  /**
   * Returns the number of entries stored in cache.
   */
  protected synchronized int getCount() {
    return cache.size();
  }

  /**
   * A callback called whenever an item is evicted from the cache. Subclasses can override.
   *
   * @param key  The key of the evicted item.
   * @param item The evicted item.
   */
  protected void onItemEvicted(T key, Y item) {
    // optional override
  }

  /**
   * Returns the current maximum size of the cache in bytes.
   */
  public synchronized int getMaxSize() {
    return maxSize;
  }

  /**
   * Returns the sum of the sizes of all items in the cache.
   */
  public synchronized int getCurrentSize() {
    return currentSize;
  }

  /**
   * Returns true if there is a value for the given key in the cache.
   *
   * @param key The key to check.
   */

  public synchronized boolean contains(T key) {
    return cache.containsKey(key);
  }

  /**
   * Returns the item in the cache for the given key or null if no such item exists.
   *
   * @param key The key to check.
   */
  @Nullable
  public synchronized Y get(T key) {
    return cache.get(key);
  }

  /**
   * Adds the given item to the cache with the given key and returns any previous entry for the
   * given key that may have already been in the cache.
   *
   * <p> If the size of the item is larger than the total cache size, the item will not be added to
   * the cache and instead {@link #onItemEvicted(Object, Object)} will be called synchronously with
   * the given key and item. </p>
   *
   * @param key  The key to add the item at.
   * @param item The item to add.
   */
  public synchronized Y put(T key, Y item) {
    final int itemSize = getSize(item);
    if (itemSize >= maxSize) {
      onItemEvicted(key, item);
      return null;
    }

    final Y result = cache.put(key, item);
    if (item != null) {
      currentSize += getSize(item);
    }
    if (result != null) {
      // TODO: should we call onItemEvicted here?
      currentSize -= getSize(result);
    }
    evict();

    return result;
  }

  /**
   * Removes the item at the given key and returns the removed item if present, and null otherwise.
   *
   * @param key The key to remove the item at.
   */
  @Nullable
  public synchronized Y remove(T key) {
    final Y value = cache.remove(key);
    if (value != null) {
      currentSize -= getSize(value);
    }
    return value;
  }

  /**
   * Clears all items in the cache.
   */
  public void clearMemory() {
    trimToSize(0);
  }

  /**
   * Removes the least recently used items from the cache until the current size is less than the
   * given size.
   *
   * @param size The size the cache should be less than.
   */
  protected synchronized void trimToSize(int size) {
    Map.Entry<T, Y> last;
    while (currentSize > size) {
      last = cache.entrySet().iterator().next();
      final Y toRemove = last.getValue();
      currentSize -= getSize(toRemove);
      final T key = last.getKey();
      cache.remove(key);
      onItemEvicted(key, toRemove);
    }
  }

  private void evict() {
    trimToSize(maxSize);
  }
}
2. Glide內(nèi)存緩存的實(shí)現(xiàn)自然也是使用的LruCache算法烹吵。不過除了LruCache算法之外,Glide還結(jié)合了一種弱引用的機(jī)制毛甲,共同完成了內(nèi)存緩存功能年叮。這樣做的目的是把正在使用中的圖片使用弱引用來進(jìn)行緩存,不在使用中的圖片使用LruCache來進(jìn)行緩存的功能玻募。分工合作只损,保護(hù)正在使用的資源不會(huì)被LruCache算法回收掉。(劃重點(diǎn))
3. Glide默認(rèn)情況下,Glide自動(dòng)就是開啟內(nèi)存緩存的跃惫。也就是說叮叹,當(dāng)我們使用Glide加載了一張圖片之后,這張圖片就會(huì)被緩存到內(nèi)存當(dāng)中爆存,只要在它還沒從內(nèi)存中被清除之前蛉顽,下次使用Glide再加載這張圖片都會(huì)直接從內(nèi)存當(dāng)中讀取,而不用重新從網(wǎng)絡(luò)或硬盤上讀取了先较,這樣無疑就可以大幅度提升圖片的加載效率携冤。比方我們在使用RecyclerView、listview闲勺、viewpager這種控件時(shí)曾棕,反復(fù)上下滑動(dòng),當(dāng)移出屏幕的項(xiàng)被回收再次移入屏幕展示時(shí)菜循,那么只要是Glide加載過的圖片都可以直接從內(nèi)存當(dāng)中迅速讀取并展示出來翘地。
4. 看過了之前圖片加載的兩篇文章,我們在第三步into時(shí)癌幕,onSizeReady準(zhǔn)備圖片加載時(shí)衙耕,會(huì)調(diào)用Engine.load這個(gè)比較重要的方法,在上一篇文章分析時(shí)勺远,我們忽略了對緩存的處理橙喘,而是直接分析沒有緩存的加載過程。而這篇文章我們返回看緩存處理谚中。
重新放出Engine.load方法渴杆。
Engine類
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 = LogTime.getLogTime();
  //a
    EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
        resourceClass, transcodeClass, options);
  //b
    EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
    if (cached != null) {
      cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return null;
    }
  //c
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }
  //d
    EngineJob<?> current = jobs.get(key);
    if (current != null) {
      current.addCallback(cb);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Added to existing load", startTime, key);
      }
      return new LoadStatus(cb, current);
    }
  //e
    EngineJob<R> engineJob = engineJobFactory.build(key, isMemoryCacheable,
        useUnlimitedSourceExecutorPool, useAnimationPool);
    DecodeJob<R> decodeJob = decodeJobFactory.build(
        glideContext,
        model,
        key,
        signature,
        width,
        height,
        resourceClass,
        transcodeClass,
        priority,
        diskCacheStrategy,
        transformations,
        isTransformationRequired,
        isScaleOnlyOrNoTransform,
        onlyRetrieveFromCache,
        options,
        engineJob);
    jobs.put(key, engineJob);
    engineJob.addCallback(cb);
    engineJob.start(decodeJob);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Started new load", startTime, key);
    }
    return new LoadStatus(cb, engineJob);
  }
  • //a處:首先通過KeyFactory的buildKey方法創(chuàng)建了一個(gè)EngineKey對象(緩存鍵),這個(gè)對象就是我們說的緩存key宪塔,加載資源的唯一標(biāo)識∧野荩可以看到?jīng)Q定緩存Key的條件非常多某筐,即使你用override()方法改變了一下圖片的width或者h(yuǎn)eight,也會(huì)生成一個(gè)完全不同的緩存Key冠跷。
  • //b處:通過loadFromCache方法南誊,通過key查找緩存資源,此時(shí)的緩存為內(nèi)存緩存蜜托,如果獲取的到就直接調(diào)用cb.onResourceReady()方法進(jìn)行回調(diào)抄囚。
  • //c處:如果內(nèi)存緩存沒有找到對應(yīng)key的資源,則調(diào)用loadFromActiveResources方法橄务,還是通過key獲取緩存資源幔托,而此時(shí)的緩存也是內(nèi)存緩存。獲取到的話也直接進(jìn)行回調(diào)。
也就是說重挑,Glide的圖片加載過程中會(huì)調(diào)用兩個(gè)方法來獲取內(nèi)存緩存嗓化,loadFromCache()和loadFromActiveResources()。這兩個(gè)方法中前者使用的就是LruCache算法谬哀,后者使用的就是弱引用刺覆。
  • //d處:如果以上都沒有找到,那是否可能該緩存任務(wù)正在處理史煎,還沒有完成緩存谦屑,所以根據(jù)key判斷緩存的job中是否有current,如果有篇梭,就不用新創(chuàng)建任務(wù)了对室,而是給其添加回調(diào),等待完成后獲取如孝。
  • //e處:如果以上條件都不滿足岁诉,我們就需要?jiǎng)?chuàng)建新的加載任務(wù)。并把當(dāng)前任務(wù)存放在jobs這個(gè)map中喉磁。同時(shí)要開啟線程來加載新的圖片了谓苟。
5.看一下loadFromCache()和loadFromActiveResources()這兩個(gè)方法的源碼:
//Engine類
  private final MemoryCache cache;
  private final Map<Key, WeakReference<EngineResource<?>>> activeResources;
...省略
//內(nèi)存緩存 (Memory cache) - 該圖片是否最近被加載過并仍存在于內(nèi)存中?
 private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
  //a
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
  //c
      cached.acquire();
  //d
      activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue()));
    }
    return cached;
  }

  @SuppressWarnings("unchecked")
  private EngineResource<?> getEngineResourceFromCache(Key key) {
  //b
    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*/);
    }
    return result;
  }
//活動(dòng)資源 (Active Resources) - 現(xiàn)在是否有另一個(gè) View 正在展示這張圖片协怒?
 //e
  private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }

    EngineResource<?> active = null;
    WeakReference<EngineResource<?>> activeRef = activeResources.get(key);
    if (activeRef != null) {
      active = activeRef.get();//得到引用的資源
  //再次判斷是為了防止引用被清空涝焙,或gc回收
      if (active != null) {
        active.acquire();
      } else {
        activeResources.remove(key);
      }
    }

    return active;
  }
  • //a處:首先關(guān)于傳入?yún)?shù)isMemoryCacheable,代表內(nèi)存緩存是否被開啟孕暇,Glide默認(rèn)為開啟仑撞,true。但如果想要禁用的話呢妖滔?通過向skipMemoryCache()傳入true隧哮,此時(shí)isMemoryCacheable將為false,返回值也為null座舍。Glide-v3到v4寫法的變化
GlideApp.with(fragment)
  .load(url)
  .skipMemoryCache(true)
  .into(view);
  • //b處:接著調(diào)用了getEngineResourceFromCache(key)方法來獲取緩存沮翔。在這個(gè)方法中,會(huì)使用緩存Key來從cache當(dāng)中取值曲秉,而這里的cache對象就是在構(gòu)建Glide對象時(shí)創(chuàng)建的LruResourceCache采蚀,那么說明這里其實(shí)使用的就是LruCache算法了。當(dāng)我們從LruResourceCache中獲取到緩存圖片之后會(huì)將它從緩存中移除承二。cache.remove(key)榆鼠。這個(gè)語句既返回了對應(yīng)key的value值,也將對應(yīng)的key從cache中移除亥鸠。
  • //c處:如果cached不為null妆够,首先調(diào)用cached.acquire();EngineResource用一個(gè)acquired變量來記錄圖片被引用的次數(shù),調(diào)用acquire()方法會(huì)讓變量acquire加1,調(diào)用release()方法會(huì)讓變量減1责静。(當(dāng)acquired變量大于0的時(shí)候袁滥,說明圖片正在使用中,也就應(yīng)該放到activeResources弱引用緩存當(dāng)中,如果acquired變量等于0了灾螃,說明圖片已經(jīng)不在使用中了题翻。釋放資源,從activeResources弱引用緩存中移除腰鬼,并put到LruResourceCache當(dāng)中)
  • //d處:然后將這個(gè)緩存圖片存儲到activeResources當(dāng)中嵌赠。activeResources就是一個(gè)弱引用的HashMap,用來緩存正在使用中的圖片熄赡,我們可以看到姜挺,loadFromActiveResources()方法就是從activeResources這個(gè)HashMap當(dāng)中取值的。使用activeResources來緩存正在使用中的圖片彼硫,可以保護(hù)這些圖片不會(huì)被LruCache算法回收掉炊豪。
  • //e處:如果從內(nèi)存中沒有找到資源,那有一種可能為該資源已被LruCache算法移除拧篮,但是它正在被另一個(gè)view展示词渤,所以此時(shí)還是有此資源的緩存。所以查找串绩,存在缺虐,引用值加1,不存在礁凡,則把key從activeResources弱引用緩存中移除高氮。

【內(nèi)存緩存的寫入】

1.當(dāng)沒有讀取到緩存時(shí),我們肯定要正常開啟線程去下載資源了顷牌,具體流程可以看之前的文章剪芍,那么從服務(wù)端得到資源后是何時(shí)以及如何寫入到緩存中呢?下面來具體分析:

上一篇文章講解過窟蓝,當(dāng)從服務(wù)端得到stream然后做處理得到的最終圖片資源通過層層回調(diào)返回等最終交給EngineJob的onResourceReady的方法處理紊浩。而在這個(gè)方法中通過Handler發(fā)送一條消息將執(zhí)行邏輯切回到主線程當(dāng)中,從而執(zhí)行handleResultOnMainThread()方法疗锐。

EngineJob類
...
  private final EngineJobListener listener;
... 
 void handleResultOnMainThread() {
    stateVerifier.throwIfRecycled();
    if (isCancelled) {
      resource.recycle();
      release(false /*isRemovedFromQueue*/);
      return;
    } else if (cbs.isEmpty()) {
      throw new IllegalStateException("Received a resource without any callbacks to notify");
    } else if (hasResource) {
      throw new IllegalStateException("Already have resource");
    }
    engineResource = engineResourceFactory.build(resource, isCacheable);
    hasResource = true;

    // Hold on to resource for duration of request so we don't recycle it in the middle of
    // notifying if it synchronously released by one of the callbacks.
    engineResource.acquire();
    listener.onEngineJobComplete(key, engineResource);

    for (ResourceCallback cb : cbs) {
      if (!isInIgnoredCallbacks(cb)) {
        engineResource.acquire();
        cb.onResourceReady(engineResource, dataSource);
      }
    }
    // Our request is complete, so we can release the resource.
    engineResource.release();

    release(false /*isRemovedFromQueue*/);
  }

在這個(gè)方法里,通過EngineResourceFactory構(gòu)建出了一個(gè)包含圖片資源的EngineResource對象费彼,然后將這個(gè)對象回調(diào)到Engine的onEngineJobComplete()方法當(dāng)中.

Engine類
public class Engine implements EngineJobListener,
    MemoryCache.ResourceRemovedListener,
    EngineResource.ResourceListener {
...
  @SuppressWarnings("unchecked")
  @Override
  public void onEngineJobComplete(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.put(key, new ResourceWeakReference(key, resource, getReferenceQueue()));
      }
    }
    // TODO: should this check that the engine job is still current?
    jobs.remove(key);
  }
}
重點(diǎn):先將resource添加監(jiān)聽滑臊,然后回調(diào)過來的EngineResource被put到了activeResources當(dāng)中,在這里寫入到了內(nèi)存緩存的弱引用緩存箍铲。寫入到弱引用緩存的原因是這個(gè)資源是屬于正在被加載展示的資源雇卷,也就是正在被使用的資源。
那什么時(shí)候要寫入到內(nèi)存緩存的LruCache中呢?我們說過要將不在使用中的圖片使用LruCache來進(jìn)行緩存关划,那怎么判斷是否在使用中?那就是前面講到的EngineResource中的一個(gè)引用機(jī)制小染,通過acquired的值來判斷。(當(dāng)acquired變量大于0的時(shí)候贮折,說明圖片正在使用中裤翩,也就應(yīng)該放到activeResources弱引用緩存當(dāng)中,如果acquired變量等于0了,說明圖片已經(jīng)不在使用中了调榄,此時(shí)放到LruCache來進(jìn)行緩存踊赠。)acquired的增加和減少通過EngineResource的acquire()和release()方法。
EngineResource類
class EngineResource<Z> implements Resource<Z> {
  private int acquired;
  private ResourceListener listener;
  ...
  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) {
      listener.onResourceReleased(key, this);
    }
  }
}
在release方法可以看到每庆,當(dāng)acquired=0時(shí)筐带,調(diào)用engine的onResourceReleased();
Engine類
  @Override
  public void onResourceReleased(Key cacheKey, EngineResource resource) {
    Util.assertMainThread();
    activeResources.remove(cacheKey);
    if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }
當(dāng)不在使用中時(shí),此時(shí)就可以從 activeResources移除缤灵,同時(shí)就可以添加到Lrucache中了伦籍。此處為寫入內(nèi)存緩存的LruCache地方。
截止到此 內(nèi)存緩存分析完畢腮出。

【磁盤緩存】

接口為DiskCache帖鸦,Glide 使用DiskLruCacheWrapper作為默認(rèn)的磁盤緩存,該類是接口MemoryCache的實(shí)現(xiàn)類(該接口的另一個(gè)實(shí)現(xiàn)類為DiskCacheAdapter)利诺。 DiskLruCacheWrapper是一個(gè)使用 LRU 算法的固定大小的磁盤緩存富蓄。默認(rèn)磁盤大小為250MB,位置是在應(yīng)用的緩存文件夾 下中的一個(gè) 特定目錄 慢逾。
Google也曾提供了一個(gè)現(xiàn)成的工具類立倍,DiskLruCache。郭霖大神這篇文章對這個(gè)DiskLruCache工具進(jìn)行了比較全面的分析侣滩,感興趣的朋友可以參考一下 Android DiskLruCache完全解析口注,硬盤緩存的最佳方案
默認(rèn)情況下我們進(jìn)行初始化glide時(shí)是磁盤內(nèi)部存儲new InternalCacheDiskCacheFactory(context),假如應(yīng)用程序展示的媒體內(nèi)容是公開的(從無授權(quán)機(jī)制的網(wǎng)站上加載君珠,或搜索引擎等)寝志,那么應(yīng)用可以將這個(gè)緩存位置改到外部存儲:在自定義moudle的配置GlideBuilder.setDiskCache(new ExternalDiskCacheFactory(context));
無論使用內(nèi)部或外部磁盤緩存,應(yīng)用程序都可以改變磁盤緩存的大小和改變緩存文件夾在外存或內(nèi)存上的名字:
官網(wǎng)示例:
@GlideModule
public class YourAppGlideModule extends AppGlideModule {
  @Override
  public void applyOptions(Context context, GlideBuilder builder) {
    int diskCacheSizeBytes = 1024  1024  100;  100 MB
    builder.setDiskCache(
        new InternalDiskCacheFactory(context, cacheFolderName, diskCacheSizeBytes));
  }
}
上面我們提到過內(nèi)存緩存可以禁止策添,同樣磁盤緩存也可以材部。
GlideApp.with(fragment)
  .load(url)
  .diskCacheStrategy(DiskCacheStrategy.NONE)
  .into(view);

【磁盤緩存策略】

看到diskCacheStrategy()方法,我們就必須要提一下磁盤緩存策略:DiskCacheStrategy可被diskCacheStrategy方法應(yīng)用到每一個(gè)單獨(dú)的請求唯竹。 目前支持的策略如下
  • DiskCacheStrategy.ALL : 表示既緩存原始圖片乐导,也緩存轉(zhuǎn)換過后的圖片。對于遠(yuǎn)程圖片浸颓,緩存DATA和RESOURCE物臂。對于本地圖片旺拉,只緩存RESOURCE。
  • DiskCacheStrategy.AUTOMATIC :它會(huì)嘗試對本地和遠(yuǎn)程圖片使用最佳的策略棵磷。當(dāng)你加載遠(yuǎn)程數(shù)據(jù)(比如蛾狗,從URL下載)時(shí),AUTOMATIC 策略僅會(huì)存儲未被你的加載過程修改過(比如仪媒,變換沉桌,裁剪–譯者注)的原始數(shù)據(jù)(DATA),因?yàn)橄螺d遠(yuǎn)程數(shù)據(jù)相比調(diào)整磁盤上已經(jīng)存在的數(shù)據(jù)要昂貴得多规丽。對于本地?cái)?shù)據(jù)蒲牧,AUTOMATIC 策略則會(huì)僅存儲變換過的縮略圖(RESOURCE),因?yàn)榧词鼓阈枰俅紊闪硪粋€(gè)尺寸或類型的圖片赌莺,取回原始數(shù)據(jù)也很容易冰抢。
  • DiskCacheStrategy.DATA:表示只緩存未被處理的文件。我的理解就是我們獲得的stream艘狭。它是不會(huì)被展示出來的挎扰,需要經(jīng)過裝載decode,對圖片進(jìn)行壓縮和轉(zhuǎn)換巢音,等等操作遵倦,得到最終的圖片才能被展示。
  • DiskCacheStrategy.NONE: 表示不緩存任何內(nèi)容官撼。
  • DiskCacheStrategy.RESOURCE:表示只緩存轉(zhuǎn)換過后的圖片梧躺。(也就是經(jīng)過decode,轉(zhuǎn)化裁剪的圖片)
默認(rèn)的策略為DiskCacheStrategy.AUTOMATIC傲绣,改變策略也很簡單掠哥, xxx.diskCacheStrategy(DiskCacheStrategy.ALL);

【磁盤緩存的讀取】

上面講過內(nèi)存緩存的讀取秃诵,那磁盤緩存是在哪里讀取的呢续搀?和內(nèi)存緩存一樣,我們觸發(fā)圖片的加載是在Engine的load方法中菠净,當(dāng)我們從內(nèi)存緩存以及當(dāng)前任務(wù)中都沒有找到資源時(shí)禁舷,我們要開啟線程去下載,engineJob.start(decodeJob);上一篇文章因?yàn)楹雎跃彺嬉阃紤]加載牵咙,所以當(dāng)時(shí)忽略緩存操作,而這次我們帶著緩存一起看攀唯。
EngineJob類
public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }
DecodeJob類
  /**
   * 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);
    return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
  }

  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);
    }
  }

  • willDecodeFromCache()方法通過調(diào)用getNextStage霜大,傳入初始化標(biāo)識INITIALIZE,得到當(dāng)前階段標(biāo)識革答,diskCacheStrategy.decodeCachedResource()返回一個(gè)boolean標(biāo)識战坤,如果我們指定磁盤緩存策略為DiskCacheStrategy.ALL或DiskCacheStrategy.RESOURCE或由DiskCacheStrategy.AUTOMATIC對遠(yuǎn)程圖片使用磁盤緩存時(shí),此時(shí)返回true残拐,標(biāo)識途茫。返回Stage.RESOURCE_CACHE。如果為false溪食,遞歸調(diào)用囊卜,判斷是否為diskCacheStrategy.decodeCachedData(),也就是指定磁盤緩存策略為DiskCacheStrategy.ALL或DiskCacheStrategy.DATA或由DiskCacheStrategy.AUTOMATIC對本地圖片使用磁盤緩存時(shí)错沃,此時(shí)返回true栅组。否則返回false,遞歸調(diào)用枢析,判斷onlyRetrieveFromCache的boolean值玉掸,這個(gè)值是初始化DecodeJob中傳進(jìn)來的,它代表是否僅從緩存加載圖片醒叁,通過onlyRetrieveFromCache(true)制定司浪,默認(rèn)為false,如果為true把沼,它意味著要從內(nèi)存或磁盤讀取啊易,如果內(nèi)存或磁盤不存在該資源,則加載直接失敗饮睬。一般情況下我們不會(huì)制定租谈,為false,也就是會(huì)返回 Stage.SOURCE捆愁。代表不使用磁盤緩存割去,也就是之前文章分析的,直接從服務(wù)器下載牙瓢。關(guān)于onlyRetrieveFromCache劫拗,再多說兩句:
某些情形下,你可能希望只要圖片不在緩存中則加載直接失敺恕(比如省流量模式页慷?–譯者注)。如果要完成這個(gè)目標(biāo)胁附,你可以在單個(gè)請求的基礎(chǔ)上使用 
GlideApp.with(fragment)
  .load(url)
  .onlyRetrieveFromCache(true)
  .into(imageView);
  • 所以酒繁,如果getNextStage方法返回的標(biāo)識為Stage.RESOURCE_CACHE或Stage.DATA_CACHE就代表我們沒有禁止磁盤緩存,那么willDecodeFromCache()將返回true控妻。此時(shí)executor=diskCacheExecutor州袒,返回false,executor=getActiveSourceExecutor();而這些executor在glide初始化的GlideBuilder.build方法里已經(jīng)被實(shí)例了弓候。
  • 然后執(zhí)行executor.execute(decodeJob);接著會(huì)command.run();開啟線程任務(wù)郎哭,也就是執(zhí)行DecodeJob的run方法,run方法里調(diào)用runWrapped方法,runReason默認(rèn)為INITIALIZE
DecodeJob類
private void runWrapped() {
     switch (runReason) {
      case INITIALIZE:
        // 初始化 獲取下一個(gè)階段狀態(tài)
        stage = getNextStage(Stage.INITIALIZE);
        currentGenerator = getNextGenerator();
        // 運(yùn)行
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }
  
private Stage getNextStage(Stage current) {
    switch (current) {
      case INITIALIZE: 
      // 根據(jù)定義的緩存策略來回去下一個(gè)狀態(tài)
        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 Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }

// 根據(jù)Stage找到數(shù)據(jù)抓取生成器他匪。
private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE:
       // 產(chǎn)生含有降低采樣/轉(zhuǎn)換資源數(shù)據(jù)緩存文件的DataFetcher。
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE:
       // 產(chǎn)生包含原始未修改的源數(shù)據(jù)緩存文件的DataFetcher夸研。
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE:
      // 生成使用注冊的ModelLoader和加載時(shí)提供的Model獲取源數(shù)據(jù)規(guī)定的DataFetcher邦蜜。
      // 根據(jù)不同的磁盤緩存策略,源數(shù)據(jù)可首先被寫入到磁盤亥至,然后從緩存文件中加載悼沈,而不是直接返回。
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }
 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.
  }
這里我們選擇ResourceCacheGenerator或DataCacheGenerator都好姐扮,我們就以ResourceGenerator為示例絮供,在runGenerators()方法里,還是看currentGenerator.startNext()茶敏。
ResourceCacheGenerator類
@Override
  public boolean startNext() {
    List<Key> sourceIds = helper.getCacheKeys();
    if (sourceIds.isEmpty()) {
      return false;
    }
    List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
    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);

      currentKey = new ResourceCacheKey(sourceId, helper.getSignature(), helper.getWidth(),
          helper.getHeight(), transformation, resourceClass, helper.getOptions());
      cacheFile = helper.getDiskCache().get(currentKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
// 查找ModelLoader 
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      loadData =
          modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(),
              helper.getOptions());
  // 通過FileLoader繼續(xù)加載數(shù)據(jù)
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }

    return started;
  }
FileLoad類
public void loadData(Priority priority, DataCallback<? super Data> callback) {
    // 讀取文件數(shù)據(jù)
     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);
   }
在這里我們就可以看到根據(jù)key讀取緩存文件cacheFile壤靶,傳入File,得到對應(yīng)的modelloader.fetcher去獲取數(shù)據(jù)睡榆,加載完畢后通過萍肆,callback.onDataReady(result);把數(shù)據(jù)回調(diào)返回 此callback就是對應(yīng)的Generator,我們這里是指ResourceCacheGenerator
ResourceCacheGenerator類
 @Override
  public void onDataReady(Object data) {
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE,
        currentKey);
  }
繼續(xù)回調(diào)胀屿,cb為SourceGenerator
SourceGenerator類
  // Called from source cache generator.
  @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    // This data fetcher will be loading from a File and provide the wrong data source, so override
    // with the data source of the original fetcher
    cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey);
  }

繼續(xù)回調(diào)cb為DecodeJob

DecodeJob類
  @Override
  public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
      DataSource dataSource, Key attemptedKey) {
    this.currentSourceKey = sourceKey;
    this.currentData = data;
    this.currentFetcher = fetcher;
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      TraceCompat.beginSection("DecodeJob.decodeFromRetrievedData");
      try {
        decodeFromRetrievedData();
      } finally {
        TraceCompat.endSection();
      }
    }
  }
//然后判斷線程塘揣,這里原因上篇文章具體講解過,最后還是執(zhí)行decodeFromRetrievedData();
  private void decodeFromRetrievedData() {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Retrieved data", startFetchTime,
          "data: " + currentData
          + ", cache key: " + currentSourceKey
          + ", fetcher: " + currentFetcher);
    }
    Resource<R> resource = null;
    try {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      runGenerators();
    }
  }

在onDataReady方法回調(diào)給decodeJob的DataSource是DataSource.RESOURCE_DISK_CACHE
通過decodeFromData方法將數(shù)據(jù)解碼成Resource對象后返回即可宿崭。然后通過notifyEncodeAndRelease回調(diào)UI線程顯示出來亲铡。
至此,磁盤緩存的讀取邏輯完畢

【磁盤緩存的寫入】

我們在SourceGenerator這個(gè)類的startNext方法觸發(fā)數(shù)據(jù)的加載時(shí)葡兑, loadData.fetcher.loadData(helper.getPriority(), this);加載完畢會(huì)返調(diào)用SourceGenerator.onDataReady(result);將結(jié)果返回
SourceGenerator類
@Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      dataToCache = data;
      // We might be being called back on someone else's thread. Before doing anything, we should
      // reschedule to get back onto Glide's thread.
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }
此時(shí)如果我們的磁盤緩存策略沒有禁止奖蔓,那么 dataToCache = data;同時(shí)執(zhí)行 cb.reschedule();也就是DecodeJob.reschedule():
DecodeJob
public void reschedule() {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
// We might be being called back on someone else's thread. Before doing anything, we should
    // reschedule to get back onto Glide's thread.

    callback.reschedule(this);
  }
callback.reschedule(this);也就是Engine.reschedule();再說一遍原因是我們數(shù)據(jù)加載完被回調(diào)至此,我們可能在其他線程里讹堤,但是我們需要切換到Glide自定義的線程吆鹤。
  @Override
  public void reschedule(DecodeJob<?> job) {
    // Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
    // up.
    getActiveSourceExecutor().execute(job);
  }
也就是GlideEexcutor的execute,在這里調(diào)用DecodeJob的run方法洲守,--runWrapped疑务,因?yàn)?runReason = RunReason.SWITCH_TO_SOURCE_SERVICE,所以直接調(diào)用 runGenerators();方法梗醇。--> 繼續(xù)知允,currentGenerator.startNext()這里的代碼已經(jīng)重復(fù)很多很多次了,就不過多贅述了叙谨。
 @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;
  }
此時(shí)調(diào)用currentGenerator.startNext()方法dataToCache已經(jīng)不為null了温鸽。也就是cacheData(data);就是這里了手负,我們在這里寫入數(shù)據(jù)涤垫。
private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
// 根據(jù)不同的數(shù)據(jù)獲取注冊的不同Encoder
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
//得到DiskCache得實(shí)現(xiàn)姑尺,并存入磁盤。
      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);
  }

至此,磁盤緩存的寫入也講解完畢雹姊。

寫源碼分析真的十分頭疼 ? ? 給個(gè)小心心鼓勵(lì)一下吧~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末股缸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吱雏,更是在濱河造成了極大的恐慌,老刑警劉巖瘾境,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歧杏,死亡現(xiàn)場離奇詭異,居然都是意外死亡迷守,警方通過查閱死者的電腦和手機(jī)犬绒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兑凿,“玉大人凯力,你說我怎么就攤上這事±窕” “怎么了咐鹤?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長圣絮。 經(jīng)常有香客問我祈惶,道長,這世上最難降的妖魔是什么扮匠? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任捧请,我火速辦了婚禮,結(jié)果婚禮上棒搜,老公的妹妹穿的比我還像新娘疹蛉。我一直安慰自己,他們只是感情好力麸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布可款。 她就那樣靜靜地躺著,像睡著了一般末盔。 火紅的嫁衣襯著肌膚如雪筑舅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天陨舱,我揣著相機(jī)與錄音翠拣,去河邊找鬼。 笑死游盲,一個(gè)胖子當(dāng)著我的面吹牛误墓,可吹牛的內(nèi)容都是我干的蛮粮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谜慌,長吁一口氣:“原來是場噩夢啊……” “哼然想!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起欣范,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤变泄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后恼琼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妨蛹,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年晴竞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛙卤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡噩死,死狀恐怖颤难,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情已维,我是刑警寧澤行嗤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站衣摩,受9級特大地震影響昂验,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜艾扮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一既琴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泡嘴,春花似錦甫恩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至抛虫,卻和暖如春松靡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背建椰。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工雕欺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓屠列,卻偏偏與公主長得像啦逆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子笛洛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 引言:2017年8月13日夏志,因感印軍越邊近兩個(gè)月,心中激憤苛让,夜不能寐沟蔑,故作此詞明志! 夜來枕上心煩亂狱杰,坐起望空嘆溉贿。...
    李鴻鈞閱讀 554評論 1 9
  • 大雨滂沱之后, 我的心將更為澄凈浦旱。 若你肯等待, 所有漂浮不定的云彩九杂, 都將化為幸福的海......颁湖。
    月白風(fēng)清L閱讀 145評論 0 2
  • 1 你不在身旁 風(fēng)都入了我的詩行 于是搗碎了整個(gè)寒塘 2 遇見你之后 我這漂泊踉蹌的孤舟 得意與失意并立船頭 3 ...
    不見得閱讀 316評論 0 1
  • 我聲稱你擁有了我的夢大家都笑了起來笑吧 笑吧我憤怒地哭著你怎么還不來 明明我是唯一的清醒智者怎么他們一笑倒成了跳梁...
    長舌婦閱讀 272評論 2 1