Glide - 3 源碼分析 Glide 緩存機(jī)制

Start

前言

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

  • 活動(dòng)資源 (Active Resources) - 現(xiàn)在是否有另一個(gè) View 正在展示這張圖片?
  • 內(nèi)存緩存 (Memory cache) - 該圖片是否最近被加載過并仍存在于內(nèi)存中毅戈?
  • 資源類型(Resource) - 該圖片是否之前曾被解碼、轉(zhuǎn)換并寫入過磁盤緩存沐鼠?
  • 數(shù)據(jù)來源 (Data) - 構(gòu)建這個(gè)圖片的資源是否之前曾被寫入過文件緩存肝箱?

前兩步檢查圖片是否在內(nèi)存中,如果是則直接返回圖片绵载。后兩步則檢查圖片是否在磁盤上埂陆,以便快速但異步地返回圖片苛白。

如果四個(gè)步驟都未能找到圖片,則Glide會返回到原始資源以取回?cái)?shù)據(jù)(原始文件焚虱,Uri, Url等)购裙。

加載緩存執(zhí)行順序

緩存 Key

既然是緩存功能,就必然會有用于進(jìn)行緩存的 Key鹃栽。那么 Glide 的緩存 Key 是怎么生成的呢躏率?
Glide 的緩存 Key 生成規(guī)則非常繁瑣,決定緩存Key的參數(shù)竟然有 8 個(gè)民鼓。不過繁瑣歸繁瑣禾锤,至少邏輯還是比較簡單的,我們先來看一下 Glide 緩存 Key 的生成邏輯摹察。
生成緩存 Key 的代碼在 Engine 類的 load() 方法當(dāng)中恩掷,來看一下:

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,
      Executor callbackExecutor) {
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height,
            transformations,
            resourceClass,
            transcodeClass,
            options);
    EngineResource<?> memoryResource;
    synchronized (this) {
      // ?? 1:下面要用到
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
    ...
  }
...
}

這里 model 就是我們要加載的圖片的唯一標(biāo)識,比如說如果是一張網(wǎng)絡(luò)上的圖片的話供嚎,那么這個(gè)id就是這張圖片的url地址黄娘。

接下來這行,將這個(gè) model 連同著signature克滴、width逼争、height等等 8 個(gè)參數(shù)一起傳入到 EngineKeyFactory 的 buildKey() 方法當(dāng)中,從而構(gòu)建出了一個(gè) EngineKey 對象劝赔,這個(gè) EngineKey 也就是 Glide 中的緩存 Key 了誓焦。

可見,決定緩存 Key 的條件非常多着帽,即使你用 override() 方法改變了一下圖片的 width 或者 height杂伟,也會生成一個(gè)完全不同的緩存 Key。

EngineKey 類的源碼大家有興趣可以自己去看一下仍翰,其實(shí)主要就是重寫了 equals()hashCode() 方法赫粥,保證只有傳入 EngineKey 的所有參數(shù)都相同的情況下才認(rèn)為是同一個(gè) EngineKey 對象。

EngineKey

內(nèi)存緩存

有了緩存 Key予借,接下來就可以開始進(jìn)行緩存了越平,那么我們先從內(nèi)存緩存看起。

首先你要知道灵迫,默認(rèn)情況下秦叛,Glide 自動(dòng)就是開啟內(nèi)存緩存的。

也就是說瀑粥,當(dāng)我們使用 Glide 加載了一張圖片之后挣跋,這張圖片就會被緩存到內(nèi)存當(dāng)中,只要在它還沒從內(nèi)存中被清除之前利凑,下次使用 Glide 再加載這張圖片都會直接從內(nèi)存當(dāng)中讀取浆劲,而不用重新從網(wǎng)絡(luò)或硬盤上讀取了嫌术。

如果你有什么特殊的原因需要禁用內(nèi)存緩存功能,Glide 對此提供了接口:

Glide.with(this)
     .load(url)
     .skipMemoryCache(true)
     .into(imageView);

可以看到牌借,只需要調(diào)用 skipMemoryCache() 方法并傳入 true度气,就表示禁用掉Glide的內(nèi)存緩存功能。

沒錯(cuò)膨报,關(guān)于Glide內(nèi)存緩存的用法就只有這么多磷籍,可以說是相當(dāng)簡單。接下來就讓我們就通過閱讀源碼來分析一下 Glide 的內(nèi)存緩存功能是如何實(shí)現(xiàn)的现柠。

其實(shí)說到內(nèi)存緩存的實(shí)現(xiàn)院领,非常容易就讓人想到 LruCache 算法(Least Recently Used),也叫近期最少使用算法够吩。

首先回憶一下比然,在上一篇文章的第三步 into() 方法中,我們當(dāng)時(shí)分析到了在 開始用引擎load: 方法的時(shí)候周循,說到過强法,先去緩存去找,如果沒有就去走 * waitForExistingOrStartNewJob* 方法湾笛。當(dāng)時(shí)我們沒有展開說緩存那塊饮怯,那么我們現(xiàn)在來看下它的源碼,也就是上面 ??1 的那 loadFromMemory 方法嚎研,我們看一下代碼:

@Nullable
  private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    if (!isMemoryCacheable) {
      return null;
    }
    // 優(yōu)先加載內(nèi)存中的活動(dòng)緩存 - ActiveResources
    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return active;
    }

    // 如果活動(dòng)緩存中沒有蓖墅,就加載 LRU 內(nèi)存緩存中的資源數(shù)據(jù)。
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return cached;
    }

    return null;
  }

可以看到临扮,首先就判斷了 isMemoryCacheable 是不是 false论矾,如果是 false 的話就直接返回 null。這是什么意思呢公条?

其實(shí)很簡單拇囊,我們剛剛不是學(xué)了一個(gè) skipMemoryCache() 方法嗎?如果在這個(gè)方法中傳入 true靶橱,那么這里的 isMemoryCacheable 就會是 false,表示內(nèi)存緩存已被禁用 Glide 的圖片加載過程中會調(diào)用兩個(gè)方法來獲取內(nèi)存緩存路捧,loadFromActiveResources()loadFromCache() 关霸。

我們來看一下 loadFromActiveResources() 的源碼:

  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key) {
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }

    return active;
  }

這里調(diào)用到了 ActiveResources 類里面的 * get* 方法,代碼如下:

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

  @Nullable
  synchronized EngineResource<?> get(Key key) {
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) {
      return null;
    }

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

  ···

 @VisibleForTesting
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {

  ···
}

可以看到 activeResources 就是一個(gè)弱引用的 HashMap杰扫,用來緩存正在使用中的圖片队寇。我們可以看到,loadFromActiveResources() 方法就是從 activeResources 這個(gè) HashMap 當(dāng)中取值的章姓。使用 activeResources 來緩存正在使用中的圖片佳遣,可以保護(hù)這些圖片不會被 LruCache 算法回收掉识埋。

接下來再看一下 loadFromCache(key) 方法,在這個(gè)方法中零渐,會使用緩存 Key 來從 cache 當(dāng)中取值窒舟,而這里的 cache 對象就是在構(gòu)建 Glide 對象時(shí)創(chuàng)建的 LruResourceCache,那么說明這里其實(shí)使用的就是 LruCache 算法了诵盼。代碼如下:

public class Engine
    implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {

  ···
  
  private final MemoryCache cache;

  ···

  private EngineResource<?> loadFromCache(Key key) {
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }

  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, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
    }
    return result;
  }

先看 cache惠豺,這個(gè)是 MemoryCache ,MemoryCache 代碼如下:

/** An interface for adding and removing resources from an in memory cache. */
public interface MemoryCache {

···

可以看到是一個(gè)接口风宁,在 GlideBuilder 類中實(shí)現(xiàn)如下:

    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }

而 LruResourceCache 從名字上看是實(shí)現(xiàn)LRU算法的洁墙,進(jìn)去看一下:

/** An LRU in memory cache for {@link com.bumptech.glide.load.engine.Resource}s. */
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
···

果然是,那內(nèi)部就是 LinkedHashMap 實(shí)現(xiàn)的戒财,我們接著看 getEngineResourceFromCache 方法里面的 remove 方法热监,LinkedHashMap 的 remove 方法是,存在這個(gè)key對應(yīng)的值饮寞,那就刪除并且返回狼纬,沒有的話直接返回空。

為什么 Glide 會弄 2 個(gè)內(nèi)存緩存(一個(gè) Map + 弱引用骂际,一個(gè) LRU 內(nèi)存緩存?

郭神的回道是這樣的:ActiveResources 就是一個(gè)弱引用的 HashMap 疗琉,用來緩存正在使用中的圖片,使用 ActiveResources 來緩存正在使用中的圖片,可以保護(hù)這些圖片不會被 LruCache 算法回收掉歉铝。

DevYK的理解是這樣的:比如我們 Lru 內(nèi)存緩存 size 設(shè)置裝 99 張圖片盈简,在滑動(dòng) RecycleView 的時(shí)候,如果剛剛滑動(dòng)到 100 張太示,那么就會回收掉我們已經(jīng)加載出來的第一張柠贤,這個(gè)時(shí)候如果返回滑動(dòng)到第一張,會重新判斷是否有內(nèi)存緩存类缤,如果沒有就會重新開一個(gè) Request 請求臼勉,很明顯這里如果清理掉了第一張圖片并不是我們要的效果。所以在從內(nèi)存緩存中拿到資源數(shù)據(jù)的時(shí)候就主動(dòng)添加到活動(dòng)資源中餐弱,并且清理掉內(nèi)存緩存中的資源宴霸。這么做很顯然好處是 保護(hù)不想被回收掉的圖片不被 LruCache 算法回收掉,充分利用了資源。

好的膏蚓,從內(nèi)存緩存中讀取數(shù)據(jù)的邏輯大概就是這些了瓢谢。概括一下來說,就是如果能從內(nèi)存緩存當(dāng)中讀取到要加載的圖片驮瞧,那么就直接進(jìn)行回調(diào)氓扛,如果讀取不到的話,才會開啟線程執(zhí)行后面的圖片加載邏輯论笔。

硬盤緩存

調(diào)用如下代碼開啟磁盤緩存:

Glide.with(this)
     .load(url)
     .diskCacheStrategy(DiskCacheStrategy. RESOURCE)//使用磁盤資源緩存功能
     .into(imageView);

這個(gè) diskCacheStrategy() 方法它可以接收五種參數(shù):

  • DiskCacheStrategy.NONE: 表示不緩存任何內(nèi)容采郎。
  • DiskCacheStrategy.RESOURCE: 表示只緩存轉(zhuǎn)換過后的圖片千所。
  • DiskCacheStrategy.DATA: 表示只緩存原始圖片。
  • DiskCacheStrategy.ALL : 表示既緩存原始圖片蒜埋,也緩存轉(zhuǎn)換過后的圖片淫痰。
  • DiskCacheStrategy.AUTOMATIC:表示根據(jù)數(shù)據(jù)獲取器和編碼策略自動(dòng)選擇策略(默認(rèn))

默認(rèn)的策略叫做 AUTOMATIC,它會嘗試對本地和遠(yuǎn)程圖片使用最佳的策略理茎。當(dāng)你加載遠(yuǎn)程數(shù)據(jù)(比如黑界,從URL下載)時(shí),AUTOMATIC 策略僅會存儲未被你的加載過程修改過(比如皂林,變換朗鸠,裁剪–譯者注)的原始數(shù)據(jù),因?yàn)橄螺d遠(yuǎn)程數(shù)據(jù)相比調(diào)整磁盤上已經(jīng)存在的數(shù)據(jù)要昂貴得多础倍。對于本地?cái)?shù)據(jù)烛占,AUTOMATIC 策略則會僅存儲變換過的縮略圖,因?yàn)榧词鼓阈枰俅紊闪硪粋€(gè)尺寸或類型的圖片沟启,取回原始數(shù)據(jù)也很容易忆家。

關(guān)于 Glide 硬盤緩存的用法也就只有這么多,那么接下來我們通過閱讀源碼來分析一下德迹,Glide的硬盤緩存功能是如何實(shí)現(xiàn)的芽卿。

DiskCacheStrategy.RESOURCE 資源類型
獲取資源數(shù)據(jù)

在上一篇文章中,創(chuàng)建 DecodeJob 并且調(diào)用 start 方法開始工作:胳搞,當(dāng)時(shí)沒有說了不考慮緩存卸例,現(xiàn)在重新看一下緩存這塊,代碼如下:

class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
···
  public synchronized void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor =
        decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }
···
}

可以看到肌毅,這里通過 willDecodeFromCache() 方法來判斷是否從磁盤緩存中讀取數(shù)據(jù)筷转,

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

從代碼可以看到最終返回 true 還是 false 是根據(jù)我們選擇的 DiskCacheStrategy 模式來決定,上面說到了一共有五種模式悬而,這里看一下具體源碼呜舒,我們在選擇模式的時(shí)候,各個(gè)值對應(yīng)的結(jié)果是什么笨奠,這樣袭蝗,在 getNextStage 方法中邏輯結(jié)果了。

public abstract class DiskCacheStrategy {
  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;
        }
      };

  public static final DiskCacheStrategy NONE =
      new DiskCacheStrategy() {
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return false;
        }
        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return false;
        }
        @Override
        public boolean decodeCachedResource() {
          return false;
        }
        @Override
        public boolean decodeCachedData() {
          return false;
        }
      };

  public static final DiskCacheStrategy DATA =
      new DiskCacheStrategy() {
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return dataSource != DataSource.DATA_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
        }
        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return false;
        }
        @Override
        public boolean decodeCachedResource() {
          return false;
        }
        @Override
        public boolean decodeCachedData() {
          return true;
        }
      };

  public static final DiskCacheStrategy RESOURCE =
      new DiskCacheStrategy() {
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return false;
        }
        @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 false;
        }
      };

  public static final DiskCacheStrategy AUTOMATIC =
      new DiskCacheStrategy() {
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return dataSource == DataSource.REMOTE;
        }
        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
                  || dataSource == DataSource.LOCAL)
              && encodeStrategy == EncodeStrategy.TRANSFORMED;
        }
        @Override
        public boolean decodeCachedResource() {
          return true;
        }
        @Override
        public boolean decodeCachedData() {
          return true;
        }
      };

  public abstract boolean isDataCacheable(DataSource dataSource);

  public abstract boolean isResourceCacheable(
      boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy);

  public abstract boolean decodeCachedResource();

  public abstract boolean decodeCachedData();
}

看到這個(gè)類艰躺,應(yīng)該就很清楚了呻袭。那么我們默認(rèn)是 decodeJob.willDecodeFromCache() 默認(rèn)是 true 來分析磁盤緩存里面的邏輯,看過上一期就可以知道 decodeJob 其實(shí)就是 runnable腺兴,執(zhí)行 execute 方法其實(shí)是走到 DecodeJob 的 run() 方法中:

@Override
  public void run() {

     ···
      // 1. 執(zhí)行runWrapped
      runWrapped();

     ···

  }

上一期分析過這個(gè)方法了,我們直接看重點(diǎn)廉侧,* runWrapped();* 的源碼:

  private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
        // 2.找到執(zhí)行的狀態(tài)
        stage = getNextStage(Stage.INITIALIZE);
        // 3.找到具體執(zhí)行器
        currentGenerator = getNextGenerator();
        // 4.開始執(zhí)行
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }

上面分析 willDecodeFromCache() 方法的時(shí)候页响,有??的地方我們知道 Stage.INITIALIZE篓足,所以我們走到 case INITIALIZE:,看一下getNextGenerator 方法的代碼:

  private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE: // 3.1解碼后的資源執(zhí)行器
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE: // 原始數(shù)據(jù)執(zhí)行器
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE: // 新的請求闰蚕,http 執(zhí)行器
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }

通過上面代碼可知栈拖,主要是執(zhí)行 currentGenerator.startNext() 就句代碼,currentGerator 是一個(gè)接口没陡,通過注釋 3.1 我們知道這里它的實(shí)現(xiàn)類是 ResourceCacheGenerator ,那么我們具體看下 ResourceCacheGenerator 的 startNext 函數(shù)涩哟;

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

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

        loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      //3. 獲取一個(gè)數(shù)據(jù)加載器
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      //3.1 為資源緩存文件,構(gòu)建一個(gè)加載器盼玄,這是構(gòu)建出來的是 ByteBufferFileLoader 的內(nèi)部類 ByteBufferFetcher
      loadData = modelLoader.buildLoadData(cacheFile,
          helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        //3.2 利用 ByteBufferFetcher 加載,最后把結(jié)果會通過回調(diào)給 DecodeJob 的 onDataFetcherReady 函數(shù)
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
      
    ...
      
    }

通過上面注釋可以得到幾點(diǎn)信息

  1. 首先根據(jù) 資源 ID 等一些信息拿到資源緩存 Key
  2. 通過 key 拿到緩存文件
  3. 構(gòu)建一個(gè) ByteBufferFetcher 加載緩存文件
  4. 加載完成之后回調(diào)到 DecodeJob 中。
存儲資源數(shù)據(jù)

先來看下面一段代碼:

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

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

通過上面我們知道 http 請求到圖片輸入流之后經(jīng)過一系列處理遂铡,轉(zhuǎn)換得到目標(biāo) Bitmap 資源宴胧,最后通過回調(diào)到 DecodeJob 進(jìn)行緩存起來。

清理資源緩存
  1. 用戶主動(dòng)通過系統(tǒng)來清理
  2. 卸載軟件
  3. 調(diào)用 DisCache.clear();
DiskCacheStrategy.DATA 原始數(shù)據(jù)類型
獲取原始數(shù)據(jù)

參考上小節(jié) DiskCacheStrategy.RESOURCE 獲取資源童番,不同的是把 ResourceCacheGenerator 換成 DataCacheGenerator 加載了精钮。

存儲原始數(shù)據(jù)

這里既然存的是原始數(shù)據(jù)那么我們直接從 http 請求之后的響應(yīng)數(shù)據(jù)開始查看,通過上一篇我們知道是在 HttpUrlFetcher 中請求網(wǎng)絡(luò)剃斧,直接定位到目的地:

public class HttpUrlFetcher implements DataFetcher<InputStream> {
  
    @Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      //1. 通過 loadDataWithRedirects 來進(jìn)行http 請求轨香,返回 InputStream
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      //2. 將請求之后的數(shù)據(jù)返回出去
      callback.onDataReady(result);
    } catch (IOException e) {
          ...
    } finally {
            ...
    }
  }
}

根據(jù)注釋可以得知,這里主要用于網(wǎng)絡(luò)請求幼东,請求響應(yīng)數(shù)據(jù)回調(diào)給 MultiModelLoader 中臂容。我們看下 它具體實(shí)現(xiàn):

class MultiModelLoader<Model, Data> implements ModelLoader<Model, Data> {  
  ...
@Override
    public void onDataReady(@Nullable Data data) {
    //如果數(shù)據(jù)不為空,那么就回調(diào)給 SourceGenerator
      if (data != null) {
        callback.onDataReady(data);
      } else {
        startNextOrFail();
      }
    }
 .... 
}

這里的 callback 指的是 SourceGenerator 筋粗,繼續(xù)跟

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

通過上面注釋可以知道 cb.reschedule(); 最后回調(diào)到 EngineJob 類,會執(zhí)行 reschedule(DecodeJob<?> job) 函數(shù)的 getActiveSourceExecutor().execute(job); 用線程池執(zhí)行任務(wù)娜亿,最后又回到了 DecodeJob 的 run 函數(shù) 拿到執(zhí)行器DataCacheGenerator ,最終會在 SourceGenerator 的 startNext() 函數(shù)丽已,之前流程代碼我就不貼了,上面講了很多次了买决,相信大家應(yīng)該記得了沛婴,我們直接看 startNext() 函數(shù)吧:

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

  /**這個(gè)臨時(shí)的變量就是 http 請求回來的圖片原始數(shù)據(jù)
  */
  private Object dataToCache;


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


  private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
    
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      //存儲原始數(shù)據(jù)
      //通過 StreamEncoder encode 寫入文件
      helper.getDiskCache().put(originalKey, writer);
    
    } finally {
      loadData.fetcher.cleanup();
    }

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

通過上面代碼得知,這里將原始數(shù)據(jù)寫入文件中了督赤。

清理資源緩存
  1. 用戶主動(dòng)通過系統(tǒng)來清理
  2. 卸載軟件
  3. 調(diào)用 DisCache.clear();
磁盤緩存小節(jié)
  • 資源緩存是在把圖片轉(zhuǎn)換完之后才緩存嘁灯;
  • 原始數(shù)據(jù)是網(wǎng)絡(luò)請求成功之后就寫入緩存;

參考文獻(xiàn):
DevYK
郭霖

申明:開始結(jié)束的圖片來源網(wǎng)上躲舌,侵刪

End
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丑婿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羹奉,老刑警劉巖秒旋,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诀拭,居然都是意外死亡迁筛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門耕挨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來细卧,“玉大人,你說我怎么就攤上這事筒占√懊恚” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵赋铝,是天一觀的道長插勤。 經(jīng)常有香客問我,道長革骨,這世上最難降的妖魔是什么农尖? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮良哲,結(jié)果婚禮上盛卡,老公的妹妹穿的比我還像新娘。我一直安慰自己筑凫,他們只是感情好滑沧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巍实,像睡著了一般滓技。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棚潦,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天令漂,我揣著相機(jī)與錄音,去河邊找鬼丸边。 笑死叠必,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的妹窖。 我是一名探鬼主播纬朝,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼骄呼!你這毒婦竟也來了共苛?” 一聲冷哼從身側(cè)響起判没,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎俄讹,沒想到半個(gè)月后哆致,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绕德,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡患膛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耻蛇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踪蹬。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖臣咖,靈堂內(nèi)的尸體忽然破棺而出跃捣,到底是詐尸還是另有隱情,我是刑警寧澤夺蛇,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布疚漆,位于F島的核電站,受9級特大地震影響刁赦,放射性物質(zhì)發(fā)生泄漏娶聘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一甚脉、第九天 我趴在偏房一處隱蔽的房頂上張望丸升。 院中可真熱鬧,春花似錦牺氨、人聲如沸狡耻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夷狰。三九已至,卻和暖如春郊霎,著一層夾襖步出監(jiān)牢的瞬間沼头,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工歹篓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瘫证,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓庄撮,卻偏偏與公主長得像背捌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子洞斯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

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