Glide緩存機(jī)制淺析

如果沒有緩存,在大量的網(wǎng)絡(luò)請求從遠(yuǎn)程獲取圖片時會造成網(wǎng)絡(luò)流量的浪費读恃,尤其是面對高清大圖的加載更是如此蕾域,為了節(jié)省帶寬醒叁,也為了減少用戶等待的時間,合理的緩存方式必不可少枢贿,這也是Glide圖片框架的強大之處瑰抵。另外Glide的緩存機(jī)制可以說是非常高頻的問題暇榴,Glide有幾級緩存橱赠?Glide讀取緩存的順序和時機(jī)是什么尤仍?Glide存放緩存的順序和時機(jī)又是什么?

1.Glide中緩存概念簡述

a29aa5505376d623a985b14658142521.jpg

Glide中的緩存分為兩部分狭姨,內(nèi)存緩存和硬盤緩存宰啦。

1.1 內(nèi)存緩存

內(nèi)存緩存又分為兩級,一級是LruCache緩存送挑,一級是弱引用緩存绑莺。

內(nèi)存緩存的作用:防止應(yīng)用重復(fù)將圖片數(shù)據(jù)讀取到內(nèi)存當(dāng)中暖眼。

LruCache緩存:不在使用中的圖片使用LruCache來進(jìn)行緩存惕耕。

弱引用緩存:把正在使用中的圖片使用弱引用來進(jìn)行緩存

為什么設(shè)計弱引用緩存?

因為內(nèi)存緩存使用LRU算法诫肠,當(dāng)你使用Gilde加載并顯示第一張圖片時司澎,后面又加載了很多圖片,同時你的第一張圖片還在用栋豫。這個時候內(nèi)存緩存根據(jù)LRU算法可能會刪除你正在使用的第一張照片挤安。這樣的后果就是你正在使用的照片找不到,后果就是程序崩潰丧鸯。

1.2 硬盤緩存

硬盤緩存的作用:防止應(yīng)用重復(fù)從網(wǎng)絡(luò)或其他地方重復(fù)下載和讀取數(shù)據(jù);

2.緩存源碼流程

memory cache和disk cache在Glide創(chuàng)建的時候也被創(chuàng)建了蛤铜,并傳給了Engine引擎類,Glide創(chuàng)建的代碼在GlideBuilder.build(Context)方法丛肢。

@NonNull 
Glide build(@NonNull Context context) { 
  if (memoryCache == null) { 
    memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize()); 
  } 
  if (diskCacheFactory == null) { 
    diskCacheFactory = new InternalCacheDiskCacheFactory(context); 
  } 
  if (engine == null) { 
    engine = 
        new Engine( 
            memoryCache, 
            diskCacheFactory, 
            ...); 
  } 
  return new Glide( 
      ... 
      memoryCache, 
      ...); 
} 

1. 內(nèi)存緩存--memoryCache

通過代碼可以看到 memoryCache 被放入 Engine 和 Glide 實例中围肥。在Engine中利用memoryCache進(jìn)行存取操作,Glide 實例中的memoryCache是用來在內(nèi)存緊張的時候蜂怎,通知memoryCache釋放內(nèi)存穆刻。

Glide如何監(jiān)聽內(nèi)存緊張?

當(dāng)應(yīng)用內(nèi)存不足的時候杠步,會回調(diào)ComponentCallbacks2接口的void onTrimMemory(@TrimMemoryLevel int level);或者ComponentCallbacks接口的void onLowMemory();;ComGlide實現(xiàn)了ComponentCallbacks2接口(ComponentCallback2繼承了ComponentCallback)氢伟,在Glide創(chuàng)建完成后榜轿,通過```applicationContext.registerComponentCallbacks(glide)``將其注冊進(jìn)Application,因此Glide 實例可以監(jiān)聽內(nèi)存緊張的信號朵锣。

@Override
  public void onLowMemory() {
    clearMemory();
  }

public void clearMemory() {
    // Engine asserts this anyway when removing resources, fail faster and consistently
    Util.assertMainThread();
    // memory cache needs to be cleared before bitmap pool to clear re-pooled Bitmaps too. See #687.
    memoryCache.clearMemory();
    bitmapPool.clearMemory();
    arrayPool.clearMemory();
  }
// Glide 
@Override 
public void onTrimMemory(int level) { 
  trimMemory(level); 
} 
public void trimMemory(int level) { 
  // Engine asserts this anyway when removing resources, fail faster and consistently 
  Util.assertMainThread(); 
for (RequestManager manager : managers) {
      manager.onTrimMemory(level);
    }
  // memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687. 
  memoryCache.trimMemory(level); 
  bitmapPool.trimMemory(level); 
  arrayPool.trimMemory(level); 
} 

在onlowMemory 中Glide會將一些緩存的內(nèi)存進(jìn)行清除谬盐,方便進(jìn)行內(nèi)存回收,當(dāng)onTrimMemory被調(diào)用的時候诚些,如果level是系統(tǒng)資源緊張设褐,Glide會將Lru緩存和BitMap重用池相關(guān)的內(nèi)容進(jìn)行回收。如果是其他的原因調(diào)用onTrimMemory泣刹,Glide會將緩存的內(nèi)容減小到配置緩存最大內(nèi)容的1/2助析。

memoryCache是一個使用LRU(least recently used)算法實現(xiàn)的內(nèi)存緩存類LruResourceCache,繼承至LruCache類椅您,并實現(xiàn)了MemoryCache接口外冀。LruCache定義了LRU算法實現(xiàn)相關(guān)的操作,而MemoryCache定義的是內(nèi)存緩存相關(guān)的操作掀泳。

LruCache源碼分析
    public class LruCache<K, V> {
    // 數(shù)據(jù)最終存在 LinkedHashMap 中
    private final LinkedHashMap<K, V> map;
    ...
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        // 創(chuàng)建一個LinkedHashMap雪隧,accessOrder 傳true
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
    ...

LruCache 構(gòu)造方法里創(chuàng)建一個LinkedHashMap,accessOrder 參數(shù)傳true员舵,表示按照訪問順序排序脑沿,數(shù)據(jù)存儲基于LinkedHashMap。

LinkedHashMap在HashMap的數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)上马僻,添加了雙向鏈表的數(shù)據(jù)結(jié)構(gòu)庄拇,再加上對 LinkedHashMap 的數(shù)據(jù)操作上鎖,就實現(xiàn)了LruCache的緩存策略韭邓,即最少最近訪問措近。

當(dāng)調(diào)用 put()方法時,就會在集合中添加元素女淑,并調(diào)用
trimToSize()判斷緩存是否已滿瞭郑,如果滿了就用 LinkedHashMap 的迭代器刪除隊尾元素,即近期最少訪問的元素鸭你。

當(dāng)調(diào)用 get()方法訪問緩存對象時屈张,就會調(diào)用 LinkedHashMap 的 get()方法獲得對應(yīng)集合元素,同時會更新該元素到隊頭袱巨。

2.磁盤緩存

diskCacheFactory是創(chuàng)建DiskCache的Factory阁谆,DiskCache接口定義。
DiskCache.Factory的默認(rèn)實現(xiàn)為InternalCacheDiskCacheFactory瓣窄。在InternalCacheDiskCacheFactory中會默認(rèn)會創(chuàng)建一個250M的緩存目錄笛厦,其路徑

/data/data/{package}/cache/image_manager_disk_cache/。

繼續(xù)看其父類DiskLruCacheFactory的代碼:

public class DiskLruCacheFactory implements DiskCache.Factory { 
  // 省略部分代碼
  @Override 
  public DiskCache build() { 
    File cacheDir = cacheDirectoryGetter.getCacheDirectory(); 
    if (cacheDir == null) { 
      return null; 
    } 
    if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) { 
      return null; 
    } 
    return DiskLruCacheWrapper.create(cacheDir, diskCacheSize); 
  } 
} 

DiskLruCacheFactory.build()方法會返回一個DiskLruCacheWrapper類的實例俺夕,看下DiskLruCacheWrapper的實現(xiàn)裳凸。

public class DiskLruCacheWrapper implements DiskCache { 
  private static final String TAG = "DiskLruCacheWrapper"; 
  private static final int APP_VERSION = 1; 
  private static final int VALUE_COUNT = 1; 
  private static DiskLruCacheWrapper wrapper; 
  private final SafeKeyGenerator safeKeyGenerator; 
  private final File directory; 
  private final long maxSize; 
  private final DiskCacheWriteLocker writeLocker = new DiskCacheWriteLocker(); 
  private DiskLruCache diskLruCache; 
  @SuppressWarnings("deprecation") 
  public static DiskCache create(File directory, long maxSize) { 
    return new DiskLruCacheWrapper(directory, maxSize); 
  } 
  @Deprecated 
  @SuppressWarnings({"WeakerAccess", "DeprecatedIsStillUsed"}) 
  protected DiskLruCacheWrapper(File directory, long maxSize) { 
    this.directory = directory; 
    this.maxSize = maxSize; 
    this.safeKeyGenerator = new SafeKeyGenerator(); 
  } 
  private synchronized DiskLruCache getDiskCache() throws IOException { 
    if (diskLruCache == null) { 
      diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize); 
    } 
    return diskLruCache; 
  } 
  @Override 
  public File get(Key key) { 
    String safeKey = safeKeyGenerator.getSafeKey(key); 
    File result = null; 
    try { 
      final DiskLruCache.Value value = getDiskCache().get(safeKey); 
      if (value != null) { 
        result = value.getFile(0); 
      } 
    } catch (IOException e) { 
      ... 
    } 
    return result; 
  } 
  @Override 
  public void put(Key key, Writer writer) { 
    String safeKey = safeKeyGenerator.getSafeKey(key); 
    writeLocker.acquire(safeKey); 
    try { 
      try { 
        DiskLruCache diskCache = getDiskCache(); 
        Value current = diskCache.get(safeKey); 
        ... 
        DiskLruCache.Editor editor = diskCache.edit(safeKey); 
        ... 
        try { 
          File file = editor.getFile(0); 
          if (writer.write(file)) { 
            editor.commit(); 
          } 
        } finally { 
          editor.abortUnlessCommitted(); 
        } 
      } catch (IOException e) { 
        ... 
      } 
    } finally { 
      writeLocker.release(safeKey); 
    } 
  } 
  ... 
} 

里面包裝了一個DiskLruCache贱鄙,該類主要是為DiskLruCache提供了一個根據(jù)Key生成safeKey的SafeKeyGenerator以及寫鎖DiskCacheWriteLocker。

回到GlideBuilder.build(Context)中姨谷,diskCacheFactory會被傳進(jìn)Engine中逗宁,在Engine的構(gòu)造方法中會被包裝成為一個LazyDiskCacheProvider
在被需要的時候調(diào)用getDiskCache()方法梦湘,這樣就會調(diào)用factory的build()方法返回一個DiskCache瞎颗。代碼如下:

# Engine.java
Engine(
      MemoryCache cache,
      DiskCache.Factory diskCacheFactory,
      GlideExecutor diskCacheExecutor,
      GlideExecutor sourceExecutor,
      GlideExecutor sourceUnlimitedExecutor,
      GlideExecutor animationExecutor,
      Jobs jobs,
      EngineKeyFactory keyFactory,
      ActiveResources activeResources,
      EngineJobFactory engineJobFactory,
      DecodeJobFactory decodeJobFactory,
      ResourceRecycler resourceRecycler,
      boolean isActiveResourceRetentionAllowed) {
    this.cache = cache;
    this.diskCacheProvider = new LazyDiskCacheProvider(diskCacheFactory);
    ...
// 省略部分代碼
}
private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider { 
    // 省略部分代碼
    ... 
    @Override 
    public DiskCache getDiskCache() { 
      if (diskCache == null) { 
        synchronized (this) { 
          if (diskCache == null) { 
            diskCache = factory.build(); 
          } 
          if (diskCache == null) { 
            diskCache = new DiskCacheAdapter(); 
          } 
        } 
      } 
      return diskCache; 
    } 
  } 

LazyDiskCacheProvider會在Engine后面的初始化流程中作為入?yún)鞯紻ecodeJobFactory的構(gòu)造器。在DecodeJobFactory創(chuàng)建DecodeJob時也會作為入?yún)鬟M(jìn)去捌议,DecodeJob中會以全局變量保存此LazyDiskCacheProvider哼拔,在資源加載完畢并展示后,會進(jìn)行緩存的存儲瓣颅。同時倦逐,DecodeJob也會在DecodeHelper初始化時,將此DiskCacheProvider設(shè)置進(jìn)去宫补,供ResourceCacheGenerator檬姥、DataCacheGenerator讀取緩存,供SourceGenerator寫入緩存粉怕。

3. ActiveResources

ActiveResources在Engine的構(gòu)造器中被創(chuàng)建健民,在ActiveResources的構(gòu)造器中會啟動一個后臺優(yōu)先級級別(THREAD_PRIORITY_BACKGROUND)的線程,在該線程中會調(diào)用cleanReferenceQueue()方法一直循環(huán)清除ReferenceQueue中的將要被GC的Resource贫贝。

ActiveResources(
      boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
    this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
    this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;

    monitorClearedResourcesExecutor.execute(
        new Runnable() {
          @Override
          public void run() {
            cleanReferenceQueue();
          }
        });
  }

先來看看ActiveResources的activate方法(保存)秉犹、deactivate方法(刪除)的方法。

synchronized void activate(Key key, EngineResource<?> resource) { 
    ResourceWeakReference toPut = 
        new ResourceWeakReference( 
            key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed); 
    ResourceWeakReference removed = activeEngineResources.put(key, toPut); 
    if (removed != null) { 
      removed.reset(); 
    } 
  } 
  synchronized void deactivate(Key key) { 
    ResourceWeakReference removed = activeEngineResources.remove(key); 
    if (removed != null) { 
      removed.reset(); 
    } 
  } 

activate方法會將參數(shù)封裝成為一個ResourceWeakReference平酿,然后放入map中凤优,如果對應(yīng)的key之前有值悦陋,那么調(diào)用之前值的reset方法進(jìn)行清除蜈彼。deactivate方法先在map中移除,然后調(diào)用resource的reset方法進(jìn)行清除俺驶。ResourceWeakReference繼承WeakReference幸逆,內(nèi)部只是保存了Resource的一些屬性。

圖片資源是什么時候從ActiveResource中緩存至內(nèi)存的暮现?

ResourceWeakReference繼承WeakReference还绘,內(nèi)部只是保存了Resource的一些屬性。

static final class ResourceWeakReference extends WeakReference<EngineResource<?>> { 
  @SuppressWarnings("WeakerAccess") @Synthetic final Key key; 
  @SuppressWarnings("WeakerAccess") @Synthetic final boolean isCacheable; 
  @Nullable @SuppressWarnings("WeakerAccess") @Synthetic Resource<?> resource; 
  @Synthetic 
  @SuppressWarnings("WeakerAccess") 
  ResourceWeakReference( 
      @NonNull Key key, 
      @NonNull EngineResource<?> referent, 
      @NonNull ReferenceQueue<? super EngineResource<?>> queue, 
      boolean isActiveResourceRetentionAllowed) { 
   // focus
    super(referent, queue); 
    this.key = Preconditions.checkNotNull(key); 
    this.resource = 
        referent.isCacheable() && isActiveResourceRetentionAllowed 
            ? Preconditions.checkNotNull(referent.getResource()) : null; 
    isCacheable = referent.isCacheable(); 
  } 
} 

其構(gòu)造方法中調(diào)用了super(referent, queue)栖袋,這樣做可以讓將要被GC的對象放入到ReferenceQueue中拍顷。而ActiveResources.cleanReferenceQueue()方法會一直嘗試從queue中獲取將要被GC的resource,然后調(diào)用cleanupActiveReference方法將resource從activeEngineResources中移除塘幅。cleanupActiveReference方法代碼如下:

#ActiveResource.java
void cleanupActiveReference(@NonNull ResourceWeakReference ref) { 
    synchronized (listener) { 
      synchronized (this) { 
        // 移除active資源 
        activeEngineResources.remove(ref.key); 
        if (!ref.isCacheable || ref.resource == null) { 
          return; 
        } 
        // 構(gòu)造新的 Resource 
        EngineResource<?> newResource = 
            new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false); 
        newResource.setResourceListener(ref.key, listener); 
        // 回調(diào)Engine的onResourceReleased方法 
        // 這會導(dǎo)致此資源從active變成memory cache狀態(tài) 
        listener.onResourceReleased(ref.key, newResource); 
      } 
    } 
  } 

Engine實現(xiàn)了EngineResource.ResourceListener昔案,此處的listener就是Engine尿贫,最終會回調(diào)Engine.onResourceReleased。

#Engine.java
@Override 
  public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) { 
    activeResources.deactivate(cacheKey); 
    if (resource.isCacheable()) { 
      cache.put(cacheKey, resource); 
    } else { 
      resourceRecycler.recycle(resource); 
    } 
  } 

如果資源可以被緩存踏揣,則緩存到 memory cache庆亡,否則對資源進(jìn)行回收。

也就是說當(dāng)前圖片資源未在使用中時捞稿,會先從ActiveResource的弱引用緩存中將其移除又谋,然后將該資源添加到內(nèi)存緩存中。

緩存的存取過程

內(nèi)存緩存

現(xiàn)在我們來分析下緩存的存取流程娱局,加載開始入口從Engine.load()開始彰亥,先看下對這個方法的注釋:

  • 會先檢查(Active Resources),如果有就直接返回衰齐,Active Resources沒有被引用的資源會放入Memory Cache剩愧,如果Active Resources沒有,會往下走娇斩。
  • 檢查Memory Cache中是否有需要的資源仁卷,如果有就返回,Memory Cache中沒有就繼續(xù)往下走犬第。
  • 檢查當(dāng)前在運行中的job中是否有該資源的下載锦积,有就在現(xiàn)有的job中直接添加callback返回,不重復(fù)下載歉嗓,當(dāng)然前提是計算得到的key是一致的丰介,如果還是沒有,就會構(gòu)造一個新的job開始新的工作鉴分。
public synchronized <R> LoadStatus load(...) { 
  EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, 
      resourceClass, transcodeClass, options); 
// focus1
  EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); 
  if (active != null) { 
    cb.onResourceReady(active, DataSource.MEMORY_CACHE); 
    return null; 
  } 
// focus2
  EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); 
  if (cached != null) { 
    cb.onResourceReady(cached, DataSource.MEMORY_CACHE); 
    return null; 
  } 
// focus3
  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache); 
  if (current != null) { 
    current.addCallback(cb, callbackExecutor); 
    return new LoadStatus(cb, current); 
  } 
  EngineJob<R> engineJob = 
      engineJobFactory.build(...); 
  DecodeJob<R> decodeJob = 
      decodeJobFactory.build(...); 
  jobs.put(key, engineJob); 
  engineJob.addCallback(cb, callbackExecutor); 
// focus 4
  engineJob.start(decodeJob); 
  return new LoadStatus(cb, engineJob); 
} 

先看到 focus 1,這一步會從 ActiveResources 中加載資源哮幢,首先判斷是否使用內(nèi)存緩存,否的話返回null志珍;否則到 ActiveResources 中取數(shù)據(jù):


// Engine.java
 @Nullable
 private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
   if (!isMemoryCacheable) {
     return null;
   }
   EngineResource<?> active = activeResources.get(key);
   if (active != null) {
     active.acquire();
   }
 
   return active;
 }

接著回到Engine.load()中繼續(xù)看到focus 2,如果在cache中找到就是remove掉橙垢,然后返回EngineResource,其中需要EngineResource進(jìn)行acquire一下伦糯,這個后面再看柜某,然后會把資源移到ActiveResources中,也就是上面提到的緩存:

// Engine.java
  private final MemoryCache cache;
 
  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
 
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }
 
  private EngineResource<?> getEngineResourceFromCache(Key key) {
    Resource<?> cached = cache.remove(key);
 
    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      // Save an object allocation if we've cached an EngineResource (the typical case).
      result = (EngineResource<?>) cached;
    } else {
      result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
    }
    return result;
  }

其中cache是MemoryCache接口的實現(xiàn)敛纲,如果沒設(shè)置喂击,默認(rèn)在build的時候是LruResourceCache, 也就是熟悉的LRU Cache:


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

關(guān)于LruCache,前面已經(jīng)分析過了淤翔。

回到Engine.load方法翰绊,從緩存加載成功后的回調(diào)cb.onResourceReady(cached, DataSource.MEMORY_CACHE);可以看到:active狀態(tài)的資源和memory cache狀態(tài)的資源都是DataSource.MEMORY_CACHE,并且加載的資源都是 EngineResource 對象,該對象內(nèi)部采用了引用計數(shù)去判斷資源是否被釋放监嗜,如果引用計數(shù)為0琳要,那么會調(diào)用listener.onResourceReleased(key, this)方法通知外界此資源已經(jīng)釋放了。這里的listener是ResourceListener類型的接口秤茅,只有一個onResourceReleased(Key key, EngineResource resource)方法稚补,Engine實現(xiàn)了該接口,此處的listener就是Engine框喳。在Engine.onResourceReleased方法中會判斷資源是否可緩存课幕,可緩存則將此資源放入memory cache中,否則回收掉該資源五垮,代碼如下:

public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) { 
    // 從activeResources中移除 
    activeResources.deactivate(cacheKey); 
    if (resource.isCacheable()) { 
      // 存入 MemoryCache 
      cache.put(cacheKey, resource); 
    } else { 
      resourceRecycler.recycle(resource); 
    } 
  } 

思路再拉到Engine.load()的流程中乍惊,第一次加載的時候activeResources和memoryCache中都沒有緩存的,判斷當(dāng)前是否有EngineJob在運行放仗,,如果job已經(jīng)在運行了润绎,那么直接添加一個回調(diào)后返回LoadStatus,這個可以允許用戶取消任務(wù):

// Engine.java
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);
}
 
// LoadStatus
  public static class LoadStatus {
    private final EngineJob<?> engineJob;
    private final ResourceCallback cb;
 
    LoadStatus(ResourceCallback cb, EngineJob<?> engineJob) {
      this.cb = cb;
      this.engineJob = engineJob;
    }
 
    public void cancel() {
      engineJob.removeCallback(cb);
    }
  }

接著往下看到focus 4, 到這里就需要創(chuàng)建后臺任務(wù)去拉取磁盤文件或者發(fā)起網(wǎng)絡(luò)請求。具體就是通過DecodeJob和EngineJob去加載資源诞挨。

DecoceJob實現(xiàn)了Runnable接口莉撇,然后會被EngineJob.start方法提交到對應(yīng)的線程池中去執(zhí)行。在DecoceJob的run方法中惶傻,會依次從ResourceCacheGeneratorDataCacheGenerator中去取緩存數(shù)據(jù)棍郎,當(dāng)這兩者都取不到的情況下,會交給```SourceGenerator````加載網(wǎng)絡(luò)圖片或者本地資源银室。resource資源和data資源都是磁盤緩存中的資源涂佃。

ResourceCacheGenerator 、DataCacheGenerator和SourceGenerator

這里ResourceCacheGenerator蜈敢、DataCacheGenerator辜荠、SourceGenerator均實現(xiàn)了DataFetcherGenerator接口。

  • ResourceCacheGenerator:從已經(jīng)完成剪裁或者轉(zhuǎn)換的資源緩存文件中生成DataFetchers抓狭;
  • DataCacheGenerator:從未修改的原始源數(shù)據(jù)的緩存文件中生成DataFetchers伯病;
  • SourceGenerator:從原始數(shù)據(jù)中生成DataFetchers;

這三個類中的前兩個就是Glide三級緩存中的第二級緩存辐宾,文件緩存狱从,前者ResourceCacheGenerator存儲的是經(jīng)過剪裁或轉(zhuǎn)換的圖片數(shù)據(jù),后者DataCacheGenerator存儲的就是未經(jīng)任何修改的原始數(shù)據(jù)叠纹。正好對應(yīng)著磁盤緩存策略中的RESOURCEDATA

什么是DataFetcher敞葛?

就是數(shù)據(jù)加載器誉察,他有個loadData方法用來從資源加載數(shù)據(jù),這個方法必須運行在子線程中惹谐,如果緩存中沒有數(shù)據(jù)持偏,就會通過這個方法去加載原始數(shù)據(jù)驼卖。
舉個例子,比如要通過一個網(wǎng)絡(luò)url圖片地址加載圖片鸿秆,那么需要通過SourceGenerator生成相應(yīng)的DataFetcher通過網(wǎng)絡(luò)獲取數(shù)據(jù)酌畜;從網(wǎng)絡(luò)上獲取到數(shù)據(jù)流后,不進(jìn)行任何改變會被存儲到本地文件緩存中(這個本地文件默認(rèn)的目錄是context.getCacheDir()方法獲取到的目錄卿叽,文件大小250M)桥胞,這個存儲之前先修改runReason=SWITCH_TO_SOURCE_SERVICE,然后重啟一個線程考婴,通過SourceGenerator對象去做存儲操作贩虾,而DataCacheGenerator就表示獲取存儲的數(shù)據(jù),然后經(jīng)過解碼然后將數(shù)據(jù)顯示到imageview沥阱。操作完成后會往內(nèi)存中再存一份數(shù)據(jù)缎罢,那么ResourceCacheGenerator就表示獲取這些已經(jīng)經(jīng)過解碼剪裁的緩存文件。

磁盤緩存

先構(gòu)造兩個job考杉,一個是EngineJob,另外一個DecodeJob,其中DecodeJob會根據(jù)需要解碼的資源來源分成下面幾個階段:

private enum Stage {
    /** The initial stage. */
    INITIALIZE,
    /** Decode from a cached resource. */
    RESOURCE_CACHE,
    /** Decode from cached source data. */
    DATA_CACHE,
    /** Decode from retrieved source. */
    SOURCE,
    /** Encoding transformed resources after a successful load. */
    ENCODE,
    /** No more viable stages. */
    FINISHED,
  }

在構(gòu)造DecodeJob時會把狀態(tài)置為INITIALIZE策精。

構(gòu)造完兩個 Job 后會調(diào)用 EngineJob.start

(DecodeJob),首先會調(diào)用getNextStage來確定下一個階段崇棠,這里面跟DiskCacheStrategy這個傳入的磁盤緩存策略有關(guān)蛮寂。

磁盤策略有下面幾種:

  • ALL: 緩存原始數(shù)據(jù)和轉(zhuǎn)換后的數(shù)據(jù)
  • NONE: 不緩存
  • DATA: 原始數(shù)據(jù),未經(jīng)過解碼或者轉(zhuǎn)換
  • RESOURCE: 緩存經(jīng)過解碼的數(shù)據(jù)
  • AUTOMATIC(默認(rèn)): 根據(jù)EncodeStrategyDataSource等條件自動選擇合適的緩存方

默認(rèn)的AUTOMATIC方式是允許解碼緩存的RESOURCE:

public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
 // 省略部分代碼
    @Override
    public boolean decodeCachedResource() {
      return true;
    }
 
    @Override
    public boolean decodeCachedData() {
      return true;
    }
  };

所以在 getNextStage 會先返回

Stage.RESOURCE_CACHE,然后在start中會返回diskCacheExecutor,然后開始執(zhí)行DecodeJob:

// EngineJob.java
public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
 //到這里之前一直都在主線程易茬,下面這句代碼就會切換到子線程
    executor.execute(decodeJob);
}
 
// DecodeJob.java
  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);
    }
  }

DecodeJob會回調(diào)run()開始執(zhí)行, run()中調(diào)用runWrapped執(zhí)行工作,這里runReason還是RunReason.INITIALIZE 酬蹋,根據(jù)前面的分析指導(dǎo)這里會獲得一個ResourceCacheGenerator,然后調(diào)用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:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }
 
  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 中,會調(diào)用 startNext,目前

currentGenerator是ResourceCacheGenerator, 那么就是調(diào)用它的startNext方法:

@Override 
  public boolean startNext() { 
    // list里面只有一個GlideUrl對象 
    List<Key> sourceIds = helper.getCacheKeys(); 
    if (sourceIds.isEmpty()) { 
      return false; 
    } 
    // 獲得了三個可以到達(dá)的registeredResourceClasses 
    // GifDrawable抽莱、Bitmap范抓、BitmapDrawable 
    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()); 
    } 
    // 遍歷sourceIds中的每一個key、resourceClasses中每一個class食铐,以及其他的一些值組成key 
    // 嘗試在磁盤緩存中以key找到緩存文件 
    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. 
      // 構(gòu)造key 
      currentKey = 
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops 
              helper.getArrayPool(), 
              sourceId, 
              helper.getSignature(), 
              helper.getWidth(), 
              helper.getHeight(), 
              transformation, 
              resourceClass, 
              helper.getOptions()); 
      // 查找緩存文件 
      cacheFile = helper.getDiskCache().get(currentKey); 
      // 如果找到了緩存文件匕垫,循環(huán)條件則會為false,退出循環(huán) 
      if (cacheFile != null) { 
        sourceKey = sourceId; 
        // 1. 找出注入時以File.class為modelClass的注入代碼 
        // 2. 調(diào)用所有注入的factory.build方法得到ModelLoader 
        // 3 .過濾掉不可能處理model的ModelLoader 
        // 此時的modelLoaders值為: 
        // [ByteBufferFileLoader, FileLoader, FileLoader, UnitModelLoader] 
        modelLoaders = helper.getModelLoaders(cacheFile); 
        modelLoaderIndex = 0; 
      } 
    } 
    // 如果找到了緩存文件虐呻,hasNextModelLoader()方法則會為true象泵,可以執(zhí)行循環(huán) 
    // 沒有找到緩存文件,則不會進(jìn)入循環(huán)斟叼,會直接返回false 
    loadData = null; 
    boolean started = false; 
    while (!started && hasNextModelLoader()) { 
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++); 
      // 在循環(huán)中會依次判斷某個ModelLoader能不能加載此文件 
      loadData = modelLoader.buildLoadData(cacheFile, 
          helper.getWidth(), helper.getHeight(), helper.getOptions()); 
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { 
        started = true; 
        // 如果某個ModelLoader可以偶惠,那么就調(diào)用其fetcher進(jìn)行加載數(shù)據(jù)        
// 加載成功或失敗會通知自身 
        loadData.fetcher.loadData(helper.getPriority(), this); 
      } 
    } 
    return started; 
  } 

找緩存時key的類型為ResourceCacheKey,生成ResourceCacheKey之后會根據(jù)key去磁盤緩存中查找cacheFile = helper.getDiskCache().get(currentKey);

helper.getDiskCache()返回DiskCache接口朗涩,它的實現(xiàn)類是DiskLruCacheWrapper忽孽,看下DiskLruCacheWrapper.get方法。

# DiskLruCacheWrapper.java
@Override 
  public File get(Key key) { 
    String safeKey = safeKeyGenerator.getSafeKey(key); 
    ... 
    File result = null; 
    try { 
      final DiskLruCache.Value value = getDiskCache().get(safeKey); 
      if (value != null) { 
        result = value.getFile(0); 
      } 
    } catch (IOException e) { 
      ... 
    } 
    return result; 
  } 

這里調(diào)用SafeKeyGenerator生成了一個String類型的SafeKey,實際上就是對原始key中每個字段都使用SHA-256加密兄一,然后將得到的字節(jié)數(shù)組轉(zhuǎn)換為16進(jìn)制的字符串厘线。生成SafeKey后,接著根據(jù)SafeKey去DiskCache里面找對應(yīng)的緩存文件出革,然后返回文件造壮。

回到ResourceCacheGenerator.startNext方法中,如果找到了緩存會調(diào)用loadData.fetcher.loadData(helper.getPriority(), this);這里的 fetcher 是 ByteBufferFetcher骂束,ByteBufferFetcher的loadData方法中最終會執(zhí)行callback.onDataReady(result)這里callback是ResourceCacheGenerator耳璧。

public void onDataReady(Object data) { 
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, 
        currentKey); 
  } 

ResourceCacheGeneratoronDataReady方法又會回調(diào)DecodeJobonDataFetcherReady方法進(jìn)行后續(xù)的解碼操作。

如果ResourceCacheGenerator沒有找到緩存栖雾,就會交給DataCacheGenerator繼續(xù)查找緩存楞抡。該類大體流程和ResourceCacheGenerator一樣,有點不同的是析藕,DataCacheGenerator的構(gòu)造器有兩個構(gòu)造器召廷,其中的DataCacheGenerator(List, DecodeHelper, FetcherReadyCallback)構(gòu)造器是給SourceGenerator準(zhǔn)備的。因為如果沒有磁盤緩存账胧,那么從源頭加載后竞慢,肯定需要進(jìn)行磁盤緩存操作的。所以治泥,SourceGenerator會將加載后的資源保存到磁盤中筹煮,然后轉(zhuǎn)交給DataCacheGenerator從磁盤中取出交給ImageView展示。

看下DataCacheGenerator.startNext:

public boolean startNext() { 
    while (modelLoaders == null || !hasNextModelLoader()) { 
      sourceIdIndex++; 
      if (sourceIdIndex >= cacheKeys.size()) { 
        return false; 
      } 
      Key sourceId = cacheKeys.get(sourceIdIndex); 
      ... 
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature()); 
      cacheFile = helper.getDiskCache().get(originalKey); 
      ... 
    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; 
  } 

這里的originalKey是DataCacheKey類型的居夹,DataCacheKey構(gòu)造方法如下:

DataCacheKey(Key sourceKey, Key signature)

這里的sourceKey和signature與ResourceCacheKey中的兩個變量一致败潦,從這里就可以看出:DataCache緩存的是原始的數(shù)據(jù),ResourceCache緩存的是是被解碼准脂、轉(zhuǎn)換后的數(shù)據(jù)劫扒。

如果DataCacheGenerator沒有取到緩存,那么會交給SourceGenerator從源頭加載狸膏。

總結(jié)一下:
  • 首先在ResourceCacheGenerator沒有取到緩存沟饥,那么它的startNext就會返回false,在runGenerators中就會進(jìn)入循環(huán)體內(nèi):
  • 接著會重復(fù)上面執(zhí)行g(shù)etNextStage,由于現(xiàn)在Stage已經(jīng)是RESOURCE_CACHE,所以接下來會返回DataCacheGenerator,執(zhí)行邏輯和上面的ResourceCacheGenerator是一樣的湾戳,如果還是沒有找到需要的贤旷,進(jìn)入循環(huán)體內(nèi)。
  • 此時getNextStage會根據(jù)用于是否設(shè)置只從磁盤中獲取資源砾脑,如果是就會通知失敗幼驶,回調(diào)onLoadFailed;如果不是就設(shè)置當(dāng)前Stage為Stage.SOURCE,接著往下走拦止。
  • 狀態(tài)就會進(jìn)入循環(huán)內(nèi)部的if條件邏輯里面县遣,調(diào)用reschedule糜颠。
  • 在reschedule把runReason設(shè)置成SWITCH_TO_SOURCE_SERVICE,然后通過callback回調(diào)汹族。
#DecodeJob.runGenerators()
 if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }

 public void reschedule() {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }
  • DecodeJob中的callback是EngineJob傳遞過來的萧求,所以現(xiàn)在返回到EngineJob。
    -在EngineJob中通過
    getActiveSourceExecutor切換到網(wǎng)絡(luò)線程池中顶瞒,注意在此之前執(zhí)行文件緩存的線程池一直都是diskCacheExecutor夸政,執(zhí)行DecodeJob,下面就準(zhǔn)備開始發(fā)起網(wǎng)絡(luò)請求榴徐。
#EngineJob.java
@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);
  }

看下SourceGenerator的startNext方法守问。

@Override 
  public boolean startNext() { 
    // 首次運行dataToCache為null 
    if (dataToCache != null) { 
      Object data = dataToCache; 
      dataToCache = null; 
      cacheData(data); 
    } 
    // 首次運行sourceCacheGenerator為null 
    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; 
  } 

這里的DataFetcher其實是HttpUrlFetcher,由它來發(fā)起網(wǎng)絡(luò)請求坑资。


// HttpUrlFetcher.java
@Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      callback.onDataReady(result);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }

加載成功后耗帕,依然會回調(diào)SourceGenerator的onDataReady方法。

@Override 
  public void onDataReady(Object data) { 
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy(); 
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { 
      dataToCache = data; 
      // cb 為 DecodeJob 
      cb.reschedule(); 
    } else { 
      // cb 為 DecodeJob 
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher, 
          loadData.fetcher.getDataSource(), originalKey); 
    } 
  } 

先判斷獲取到的數(shù)據(jù)是否需要進(jìn)行磁盤緩存袱贮,如果需要磁盤緩存仿便,則經(jīng)過DecodeJob、EngineJob的調(diào)度攒巍,重新調(diào)用SourceGenerator.startNext方法嗽仪,此時dataToCache已經(jīng)被賦值,則會調(diào)用cacheData(data);進(jìn)行磁盤緩存的寫入柒莉,并轉(zhuǎn)交給DataCacheGenerator完成后續(xù)的處理;否則就通知DecodeJob已經(jīng)加載成功闻坚。

DataCacheGenerator前面已經(jīng)分析過了,就是用來加載本地原始數(shù)據(jù)的兢孝。

先看下SourceGenerator的startNext方法中調(diào)用的SourceGenerator.cacheData(data)窿凤。

# SourceGenerator.java
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()); 
//根據(jù)data,key,signature 創(chuàng)建緩存key。這個key是DataCacheKey類型的跨蟹,存儲是原始數(shù)據(jù)
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature()); 
//獲取硬盤緩存并開始緩存
      helper.getDiskCache().put(originalKey, writer); 
      ... 
    } finally { 
      loadData.fetcher.cleanup(); 
    } 
//賦值
    sourceCacheGenerator = 
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this); 
  } 

cacheData方法先構(gòu)建了一個DataCacheKey將data寫入了磁盤雳殊,然后new了一個DataCacheGenerator賦值給sourceCacheGenerator∨缡校回到startNext繼續(xù)向下執(zhí)行相种,此時sourceCacheGenerator不為空,就調(diào)用其startNext()方法從磁盤中加載剛寫入磁盤的數(shù)據(jù)品姓,并返回true讓DecodeJob停止嘗試獲取數(shù)據(jù)寝并。此時,從磁盤緩存中讀取數(shù)據(jù)的邏輯已經(jīng)完成腹备,接下來是寫磁盤緩存衬潦。

結(jié)論

Glide加載網(wǎng)絡(luò)數(shù)據(jù)后, 在返回之前植酥,會先緩存一份原始數(shù)據(jù) 镀岛。

磁盤緩存與內(nèi)存緩存的關(guān)聯(lián)

磁盤緩存結(jié)束后弦牡,sourceCacheGenerator被賦值,然后返回漂羊,回到startNext方法驾锰,接著往下走,會執(zhí)行sourceCacheGenerator的startNext方法走越,
此方法最終調(diào)用loadData.fetcher.loadData(helper.getPriority(), this);這句話椭豫。由于data是流的類型,所以看ByteBufferFetcher

package com.bumptech.glide.load.model.ByteBufferFileLoader.ByteBufferFetcher
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) {
  ByteBuffer result;
  try {
  //讀取文件獲取結(jié)果 是ByteBuffer類型
    result = ByteBufferUtil.fromFile(file);
  } catch (IOException e) {
    callback.onLoadFailed(e);
    return;
  }
  //回調(diào) 
  callback.onDataReady(result);
}

最終會調(diào)用到下面的方法

@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;
    //如果不是同一個線程旨指,最終會修改runReason為RunReason.DECODE_DATA;并從起一個線程執(zhí)行decodeFromRetrievedData方法
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      try {
      //如果是同一個線程 直接執(zhí)行decodeFromRetrievedData方法
        decodeFromRetrievedData();
      } finally {
      }
    }
  }

decodeFromRetrievedData中主要調(diào)用了notifyEncodeAndRelease方法赏酥,在后一個方法中會執(zhí)行到notifyComplete方法,notifyComplete最終會調(diào)用到notifyCallbacksOfResult方法,該方法調(diào)用下面的回調(diào)方法:

@Override
public synchronized void onEngineJobComplete(
    EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
  // A null resource indicates that the load failed, usually due to an exception.
  if (resource != null && resource.isMemoryCacheable()) {
  //將資源緩存到activeResource中谆构,這里就跟前面的”先從內(nèi)存的二級緩存加載數(shù)據(jù)“的邏輯對應(yīng)上了
    activeResources.activate(key, resource);
  }

  jobs.removeIfCurrent(key, engineJob);
}

在這里會將資源放入activeResources中裸扶,資源變?yōu)閍ctive狀態(tài)。后面會使用Executors.mainThreadExecutor()調(diào)用SingleRequest.onResourceReady回調(diào)進(jìn)行資源的顯示搬素。在觸發(fā)回調(diào)前后各有一個地方會對engineResource進(jìn)行acquire()和release()操作呵晨,這兩個操作分別發(fā)生在notifyCallbacksOfResult()方法的incrementPendingCallbacks、decrementPendingCallbacks()調(diào)用中蔗蹋。

CallResourceReady的run方法中也會調(diào)用engineResource.acquire()何荚,上面的代碼調(diào)用結(jié)束后,engineResource的引用計數(shù)為1猪杭。engineResource的引用計數(shù)會在RequestManager.onDestory方法中最終調(diào)用SingleRequest.clear()方法餐塘,SingleRequest.clear()內(nèi)部調(diào)用releaseResource()、Engine.release 進(jìn)行釋放皂吮,這樣引用計數(shù)就變?yōu)?戒傻。引用計數(shù)就變?yōu)?后會通知Engine將此資源從active狀態(tài)變成memory cache狀態(tài)。如果我們再次加載資源時可以從memory cache中加載蜂筹,那么資源又會從memory cache狀態(tài)變成active狀態(tài)需纳。也就是說,在資源第一次顯示后艺挪,我們關(guān)閉頁面不翩,資源會由active變成memory cache;然后我們再次進(jìn)入頁面,加載時會命中memory cache麻裳,從而又變成active狀態(tài)口蝠。

小結(jié)一下

Glide的三級緩存:第一級緩存內(nèi)存緩存,內(nèi)存緩存又分為兩種存儲形式津坑,弱引用緩存和LRU策略的緩存方式妙蔗;第二級緩存是本地文件緩存,緩存的文件大小默認(rèn)是250M疆瑰,本地緩存會分為存儲未經(jīng)修改的源數(shù)據(jù)和經(jīng)過剪裁后的數(shù)據(jù)眉反;第三級緩存就是網(wǎng)絡(luò)/數(shù)據(jù)源緩存昙啄。
關(guān)于緩存及展示數(shù)據(jù):數(shù)據(jù)加載出來之后,會先在緩存到本地文件中寸五,然后再去本地緩存文件中讀取數(shù)據(jù)進(jìn)行編碼梳凛,將編碼后的數(shù)據(jù)再進(jìn)行一次緩存,接著再內(nèi)存中進(jìn)行緩存播歼,最后將數(shù)據(jù)編碼后的圖片文件發(fā)往主線程設(shè)置給target伶跷。

回答以下幾個問題:

Glide網(wǎng)絡(luò)請求回來后數(shù)據(jù)直接返回給用戶還是先存再返回

不是直接返回給用戶掰读,會在SourceGenerator中構(gòu)造一個DataCacheGenerator來取數(shù)據(jù)秘狞。簡單來說就是先緩存到磁盤,再從磁盤里取數(shù)據(jù)返回給ImageView添加圖片

Glide本地文件IO和網(wǎng)絡(luò)請求是一個線程嗎蹈集?

明顯不是烁试,本地IO通過diskCacheExecutor,而網(wǎng)絡(luò)IO通過ActiveSourceExecutor

總結(jié)

讀取內(nèi)存緩存時,先從弱引用機(jī)制的內(nèi)存緩存讀取拢肆,再從LruCache算法機(jī)制的內(nèi)存緩存讀取

寫入內(nèi)存緩存時减响,先寫入 弱引用機(jī)制 的內(nèi)存緩存,等到圖片不再被使用時郭怪,再寫入到 LruCache算法機(jī)制的內(nèi)存緩存;

讀取磁盤緩存時支示,先讀取轉(zhuǎn)換后圖片的緩存,再讀取原始圖片的緩存鄙才。

參考:
Android源碼進(jìn)階之Glide緩存機(jī)制原理詳解
Glide緩存流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颂鸿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子攒庵,更是在濱河造成了極大的恐慌嘴纺,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浓冒,死亡現(xiàn)場離奇詭異栽渴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)稳懒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門闲擦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人场梆,你說我怎么就攤上這事墅冷。” “怎么了辙谜?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵俺榆,是天一觀的道長。 經(jīng)常有香客問我装哆,道長罐脊,這世上最難降的妖魔是什么定嗓? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮萍桌,結(jié)果婚禮上宵溅,老公的妹妹穿的比我還像新娘。我一直安慰自己上炎,他們只是感情好恃逻,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著藕施,像睡著了一般寇损。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上裳食,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天矛市,我揣著相機(jī)與錄音,去河邊找鬼诲祸。 笑死浊吏,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的救氯。 我是一名探鬼主播找田,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼着憨!你這毒婦竟也來了墩衙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤享扔,失蹤者是張志新(化名)和其女友劉穎底桂,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惧眠,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡籽懦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了氛魁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暮顺。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖秀存,靈堂內(nèi)的尸體忽然破棺而出捶码,到底是詐尸還是另有隱情,我是刑警寧澤或链,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布惫恼,位于F島的核電站,受9級特大地震影響澳盐,放射性物質(zhì)發(fā)生泄漏祈纯。R本人自食惡果不足惜令宿,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腕窥。 院中可真熱鬧粒没,春花似錦、人聲如沸簇爆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽入蛆。三九已至响蓉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間安寺,已是汗流浹背涯曲。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工增显, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人什往。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓软能,卻偏偏與公主長得像迎捺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子查排,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

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