Glide 快取流程

vivo網際網路技術發表於2019-10-14

本文首發於 vivo網際網路技術 微信公眾號 
連結: https://mp.weixin.qq.com/s/cPLkefpEb3w12-uoiqzTig
作者:連凌能

Android上圖片載入的解決方案有多種,但是官方認可的是Glide。Glide提供簡潔易用的api,整個框架也方便擴充套件,比如可以替換網路請求庫,同時也提供了完備的快取機制,應用層不需要自己去管理圖片的快取與獲取,框架會分成記憶體快取,檔案快取和遠端快取。本文不會從簡單的使用著手,會把重點放在快取機制的分析上。

一、綜述

開始之前,關於Glide快取請先思考幾個問題:

  • Glide有幾級快取?

  • Glide記憶體快取之間是什麼關係?

  • Glide本地檔案IO和網路請求是一個執行緒嗎?如果不是,怎麼實現執行緒切換?

  • Glide網路請求回來後資料直接返回給使用者還是先存再返回?

載入開始入口從Engine.load()開始,先看下對這個方法的註釋,

  • 會先檢查(Active Resources),如果有就直接返回,Active Resources沒有被引用的資源會放入Memory Cache,如果Active Resources沒有,會往下走。

  • 檢查Memory Cache中是否有需要的資源,如果有就返回,Memory Cache中沒有就繼續往下走。

  • 檢查當前在執行中的job中是否有改資源的下載,有就在現有的job中直接新增callback返回,不重複下載,當然前提是計算得到的key是一致的,如果還是沒有,就會構造一個新的job開始新的工作。

* Starts a load for the given arguments.
*
* <p>Must be called on the main thread.
*
* <p>The flow for any request is as follows:
* <ul>
*   <li>Check the current set of actively used resources, return the active resource if
*   present, and move any newly inactive resources into the memory cache.</li>
*   <li>Check the memory cache and provide the cached resource if present.</li>
*   <li>Check the current set of in progress loads and add the cb to the in progress load if
*   one is present.</li>
*   <li>Start a new load.</li>

ok, find the source code.

二、記憶體快取

public <R> LoadStatus load(
    GlideContext glideContext,
    Object model,
    Key signature,
    int width,
    int height,
    Class<?> resourceClass,
    Class<R> transcodeClass,
    Priority priority,
    DiskCacheStrategy diskCacheStrategy,
    Map<Class<?>, Transformation<?>> transformations,
    boolean isTransformationRequired,
    boolean isScaleOnlyOrNoTransform,
    Options options,
    boolean isMemoryCacheable,
    boolean useUnlimitedSourceExecutorPool,
    boolean useAnimationPool,
    boolean onlyRetrieveFromCache,
    ResourceCallback cb) {
Util.assertMainThread();
long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);
// focus 1
EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
if (active != null) {
cb.onResourceReady(active, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return null;
}
// focus 2
EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return null;
}
// focus 3
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);
}
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
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);
// focus 4
engineJob.start(decodeJob);
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Started new load", startTime, key);
}
return new LoadStatus(cb, engineJob);
}

先看到 focus 1,這一步會從 ActiveResources 中載入資源,首先判斷是否使用記憶體快取,否的話返回null;否則到 ActiveResources 中取資料:

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

接下來看下ActiveResources, 其實是用過弱引用儲存使用過的資源。

final class ActiveResources {
 
  ...
  private final Handler mainHandler = new Handler(Looper.getMainLooper(), new Callback() {
    @Override
    public boolean handleMessage(Message msg) {
      if (msg.what == MSG_CLEAN_REF) {
        cleanupActiveReference((ResourceWeakReference) msg.obj);
        return true;
      }
      return false;
    }
  });
  @VisibleForTesting
  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
     
  ...
}


成功取到資料後回撥型別也是記憶體快取:

EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
if (cached != null) {
   cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
   return null;
}


接著回到Engine.load()中繼續看到focus 2,如果在cache中找到就是remove掉,然後返回EngineResource,其中需要EngineResource進行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介面的實現,如果沒設定,預設在build的時候是LruResourceCache, 也就是熟悉的LRU Cache:

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


再看下EngineResource,主要是對資源增加了引用計數的功能:

// EngineResource.java
private final boolean isCacheable;
  private final boolean isRecyclable;
  private ResourceListener listener;
  private Key key;
  private int acquired;
  private boolean isRecycled;
  private final Resource<Z> resource;
 
  interface ResourceListener {
    void onResourceReleased(Key key, EngineResource<?> resource);
  }
 
  EngineResource(Resource<Z> toWrap, boolean isCacheable, boolean isRecyclable) {
    resource = Preconditions.checkNotNull(toWrap);
    this.isCacheable = isCacheable;
    this.isRecyclable = isRecyclable;
  }
 
  void setResourceListener(Key key, ResourceListener listener) {
    this.key = key;
    this.listener = listener;
  }
 
  Resource<Z> getResource() {
    return resource;
  }
 
  boolean isCacheable() {
    return isCacheable;
  }
 
  @NonNull
  @Override
  public Class<Z> getResourceClass() {
    return resource.getResourceClass();
  }
 
  @NonNull
  @Override
  public Z get() {
    return resource.get();
  }
 
  @Override
  public int getSize() {
    return resource.getSize();
  }
 
  @Override
  public void recycle() {
    if (acquired > 0) {
      throw new IllegalStateException("Cannot recycle a resource while it is still acquired");
    }
    if (isRecycled) {
      throw new IllegalStateException("Cannot recycle a resource that has already been recycled");
    }
    isRecycled = true;
    if (isRecyclable) {
      resource.recycle();
    }
  }
 
  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後會判斷引用計數是否為0,如果是0就會回撥onResourceReleased,在這裡就是Engine,然後會把資源從ActiveResources中移除,資源預設是可快取的,因此會把資源放到LruCache中。

// Engine.java
  @Override
  public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
    Util.assertMainThread();
    activeResources.deactivate(cacheKey);
    if (resource.isCacheable()) {
      cache.put(cacheKey, resource);
    } else {
      resourceRecycler.recycle(resource);
    }
  }
 
// ActiveResources.java
  void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key,
            resource,
            getReferenceQueue(),
            isActiveResourceRetentionAllowed);
 
    ResourceWeakReference removed = activeEngineResources.put(key, toPut);
    if (removed != null) {
      removed.reset();
    }
  }
 
  void deactivate(Key key) {
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) {
      removed.reset();
    }
  }

如果是回收呢,看看上面的EngineResource,如果引用計數為0並且還沒與回收,就會呼叫真正的Resource.recycle(),看其中的一個BitmapResource是怎麼回收的,就是放到Bitmap池中,也是用的LRU Cache,這個和今天的主題不相關,就不繼續往下擴充。

// BitmapResource.java
 @Override
 public void recycle() {
   bitmapPool.put(bitmap);
 }

思路再拉到Engine.load()的流程中,接下來該看focus 3,這裡再貼一下程式碼,如果job已經在執行了,那麼直接新增一個回撥後返回LoadStatus,這個可以允許使用者取消任務:

// 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, 到這裡就需要建立後臺任務去拉取磁碟檔案或者發起網路請求。

三、磁碟快取

// Engine.java
EngineJob<R> engineJob =
engineJobFactory.build(
key,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache);
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);
return new LoadStatus(cb, engineJob);

先構造兩個job,一個是EngineJob,另外一個DecodeJob,其中DecodeJob會根據需要解碼的資源來源分成下面幾個階段:

// DecodeJob.java
  /**
   * Where we're trying to decode data from.
   */
  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,
  }

在構造DecodeJob時會把狀態置為INITIALIZE。

構造完兩個 Job 後會呼叫 EngineJob.start(DecodeJob),首先會呼叫getNextStage來確定下一個階段,這裡面跟DiskCacheStrategy這個傳入的磁碟快取策略有關。

磁碟策略有下面幾種:

  • **ALL: **快取原始資料和轉換後的資料

  • **NONE: **不快取

  • **DATA: **原始資料,未經過解碼或者轉換

  • **RESOURCE: **快取經過解碼的資料

  • **AUTOMATIC(預設):**根據`EncodeStrategy`和`DataSource`等條件自動選擇合適的快取方

預設的AUTOMATIC方式是允許解碼快取的RESOURCE:

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

所以在 getNextStage 會先返回Stage.RESOURCE_CACHE,然後在start中會返回diskCacheExecutor,然後開始執行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會回撥run()開始執行, run()中呼叫runWrapped執行工作,這裡runReason還是RunReason.INITIALIZE ,根據前面的分析指導這裡會獲得一個ResourceCacheGenerator,然後呼叫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 中,會呼叫 startNext,目前currentGenerator是ResourceCacheGenerator, 那麼就是呼叫它的startNext方法:

// DecodeJob.java 
private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
// We've run out of stages and generators, give up.
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}
}

看下ResourceCacheGenerator.startNext(), 這裡面就是重點邏輯了,首先從Registry中獲取支援資源型別的ModelLoader(其中ModelLoader是在構造Glide的時候傳進去), 然後從ModelLoader中構造LoadData,接著就能拿到DataFetcher,(關於ModelLoader/LoadData/DataFetcher之間的關係不在本次範圍內,後面有機會再另寫)透過它的loadData方法載入資料:

@Override
public boolean startNext() {
List<Key> sourceIds = helper.getCacheKeys();
if (sourceIds.isEmpty()) {
return false;
}
List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();
if (resourceClasses.isEmpty()) {
if (File.class.equals(helper.getTranscodeClass())) {
return false;
}
}
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(// NOPMD AvoidInstantiatingObjectsInLoops
helper.getArrayPool(),
sourceId,
helper.getSignature(),
helper.getWidth(),
helper.getHeight(),
transformation,
resourceClass,
helper.getOptions());
cacheFile = helper.getDiskCache().get(currentKey);
if (cacheFile != null) {
sourceKey = sourceId;
modelLoaders = helper.getModelLoaders(cacheFile);
modelLoaderIndex = 0;
}
}
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
loadData = modelLoader.buildLoadData(cacheFile,
helper.getWidth(), helper.getHeight(), helper.getOptions());
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}

如果在Resource中找不到需要的資源,那麼startNext就會返回false,在runGenerators中就會進入迴圈體內:

  • 接著會重複上面執行getNextStage,由於現在Stage已經是RESOURCE_CACHE,所以接下來會返回DataCacheGenerator,執行邏輯和上面的ResourceCacheGenerator是一樣的,如果還是沒有找到需要的,進入迴圈體內。

  • 此時getNextStage會根據用於是否設定只從磁碟中獲取資源,如果是就會通知失敗,回撥onLoadFailed;如果不是就設定當前Stage為Stage.SOURCE,接著往下走。

  • 狀態就會進入迴圈內部的if條件邏輯裡面,呼叫reschedule。

  • 在reschedule把runReason設定成SWITCH_TO_SOURCE_SERVICE,然後透過callback回撥。

  • DecodeJob中的callback是EngineJob傳遞過來的,所以現在返回到EngineJob。

  • 在EngineJob中透過getActiveSourceExecutor切換到網路執行緒池中,執行DecodeJob,下面就準備開始發起網路請求。

四、網路快取

在Stage.SOURCE階段,透過getNextGenerator返回的是SourceGenerator,所以目前的currentGenerator就是它。

流程還是一樣的,SourceGenerator還是呼叫startNext方法,獲取到對應的DataFetcher,這裡其實是HttpUrlFetcher,發起網路請求。

// DecodeJob.java 
private void runGenerators() {
...
while (!isCancelled && currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();
if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
...
}
@Override
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}
// EngineJob.java
@Override
public void reschedule(DecodeJob<?> job) {
getActiveSourceExecutor().execute(job);
}

先緩一緩,本文其實到了上面已經可以結束了,Glide涉及到的五級快取都已經涉及到了,是真的就可以結束了嗎?不是的,網路請求回來和快取還有關係嗎?接著看到HttpUrlFetcher,下載成功後回撥onDataReady,其中callback是SourceGenerator:

// 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));
}
}
}
// EngineJob.java
@Override
public void reschedule(DecodeJob<?> job) {
getActiveSourceExecutor().execute(job);
}

正常情況會進入if判斷邏輯裡面,賦值dataToCache,然後回撥cb.reschedule,而cb就是DecodeJob構造SourceGenerator的時候傳入,cb是DecodeJob。

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

DecodeJob在reschedule回撥EngineJob,最後還是回到SourceGenerator中的startNext()邏輯。

// DecodeJob.java
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}
@Override
public void reschedule() {
runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
callback.reschedule(this);
}

和第一次進來的邏輯不一樣,現在dataToCache != null,進入第一個if邏輯。

在邏輯裡面呼叫cacheData,邏輯很明顯,保持資料到本地,然後會構造一個DataCacheGenerator。

而DataCacheGenerator前面已經分析過了,就是用來載入本地原始資料的,這回會載入成功,返回true。

// SourceGenerator.java
@Override
public boolean startNext() {
if (dataToCache != null) {
Object data = dataToCache;
dataToCache = null;
cacheData(data);
}
if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
return true;
}
...
}
private void cacheData(Object dataToCache) {
long startTime = LogTime.getLogTime();
try {
Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
DataCacheWriter<Object> writer =
new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
helper.getDiskCache().put(originalKey, writer);
} finally {
loadData.fetcher.cleanup();
}
sourceCacheGenerator =
new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
}

接下來就是一系列的回撥了:

DataCacheGenerator的startNext邏輯裡面會給DataFetcher傳遞自身作為callback,在載入本地資料成功後回撥onDataReady。

// DataCacheGenerator
@Override
public boolean startNext() {
...
loadData = null;
boolean started = false;
while (!started && hasNextModelLoader()) {
...
if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
started = true;
loadData.fetcher.loadData(helper.getPriority(), this);
}
}
return started;
}
@Override
public void onDataReady(Object data) {
cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.DATA_DISK_CACHE, sourceKey);
}

而cb現在是SourceGenerator傳遞過來,SourceGenerator再回撥它自己的cb,是DecodeJob在構造它的時候傳過來。

// SourceGenerator.java
@Override
public void onDataFetcherReady(Key sourceKey, Object data, DataFetcher<?> fetcher,
DataSource dataSource, Key attemptedKey) {
cb.onDataFetcherReady(sourceKey, data, fetcher, loadData.fetcher.getDataSource(), sourceKey);
}
// DecodeJob.java
@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 {
try {
decodeFromRetrievedData();
} finally {
GlideTrace.endSection();
}
}
}

在上面SourceGenerator把DecodeJob切換到ActiveSourceExecutor執行緒中執行,還記得一開始DecodeJob是在哪啟動的嗎?在EngineJob中啟動,然後是把DecodeJob放到diskCacheExecutor中執行。

// EngineJob.java
public void start(DecodeJob<R> decodeJob) {
this.decodeJob = decodeJob;
GlideExecutor executor = decodeJob.willDecodeFromCache()
? diskCacheExecutor
: getActiveSourceExecutor();
executor.execute(decodeJob);
}

所以上面在DecodeJob的onDataFetcherReady會走到第一個if邏輯裡面,然後賦值runReason = RunReason.DECODE_DATA,再一次回撥Engine.reschedule,將工作執行緒切換到ActiveSourceExecutor。

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

然後還是走到DecodeJob, 現在會進入DECODE_DATA分支,在這裡面會呼叫ResourceDecoder把資料解碼:

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

解碼成功後呼叫notifyComplete(result, dataSource);

private void notifyComplete(Resource<R> resource, DataSource dataSource) {
setNotifiedOrThrow();
callback.onResourceReady(resource, dataSource);
}

 

五、總結

現在回答一下開頭的幾個問題。

1、有幾級快取?五級,分別是什麼?

  • 活動資源 (Active Resources)

  • 記憶體快取 (Memory Cache)

  • 資源型別(Resource Disk Cache)

  • 原始資料 (Data Disk Cache)

  • 網路快取

2、Glide記憶體快取之間是什麼關係?

專門畫了一幅圖表明這個關係,言簡意賅。

3、Glide本地檔案IO和網路請求是一個執行緒嗎?

明顯不是,本地IO透過diskCacheExecutor,而網路IO透過ActiveSourceExecutor

4、Glide網路請求回來後資料直接返回給使用者還是先存再返回?

不是直接返回給使用者,會在SourceGenerator中構造一個DataCacheGenerator來取資料。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912579/viewspace-2659292/,如需轉載,請註明出處,否則將追究法律責任。

相關文章