Glide 快取流程
本文首發於 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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Glide中的快取IDE快取
- Glide 系列-3:Glide 快取的實現原理(4.8.0)IDE快取
- Android圖片快取框架GlideAndroid快取框架IDE
- Glide 快取與解碼複用IDE快取
- Android 圖片載入快取問題:為什麼你的Glide快取沒有起作用?Android快取IDE
- Glide 知識梳理(6) – Glide 原始碼解析之流程剖析IDE原始碼
- Android原始碼分析:手把手帶你深入瞭解Glide的快取機制Android原始碼IDE快取
- Glide 系列-2:主流程原始碼分析(4.8.0)IDE原始碼
- Glide4.8原始碼拆解(一)基本呼叫流程IDE原始碼
- 快取穿透、快取擊穿、快取雪崩、快取預熱快取穿透
- 快取穿透、快取擊穿、快取雪崩快取穿透
- 快取穿透、快取雪崩、快取擊穿快取穿透
- Glide4.8原始碼拆解(二)核心載入流程IDE原始碼
- Redis快取擊穿、快取穿透、快取雪崩Redis快取穿透
- [Redis]快取穿透/快取擊穿/快取雪崩Redis快取穿透
- HTTP快取——協商快取(快取驗證)HTTP快取
- 快取穿透 快取雪崩快取穿透
- 快取問題(一) 快取穿透、快取雪崩、快取併發 核心概念快取穿透
- 快取穿透、快取擊穿、快取雪崩區別快取穿透
- 快取問題(四) 快取穿透、快取雪崩、快取併發 解決案例快取穿透
- ServiceWorker 快取與 HTTP 快取快取HTTP
- mybatis快取-二級快取MyBatis快取
- MyBatis快取機制(一級快取,二級快取)MyBatis快取
- 快取淘汰、快取穿透、快取擊穿、快取雪崩、資料庫快取雙寫一致性快取穿透資料庫
- Redis詳解(十二)------ 快取穿透、快取擊穿、快取雪崩Redis快取穿透
- 什麼是redis快取雪崩、快取穿透、快取擊穿Redis快取穿透
- 分散式快取 - 快取簡介,常用快取演算法分散式快取演算法
- 快取穿透,快取擊穿,快取雪崩解決方案分析快取穿透
- Redis——快取穿透、快取擊穿、快取雪崩、分散式鎖Redis快取穿透分散式
- 快取穿透、快取雪崩和快取擊穿是什麼?快取穿透
- Redis快取穿透、快取雪崩、快取擊穿好好說說Redis快取穿透
- Glide 4.9 原始碼分析(一) —— 一次完整載入流程IDE原始碼
- 快取最佳化(快取穿透)快取穿透
- Web快取 – HTTP協議快取Web快取HTTP協議
- 清理 Conda 快取和 Pip 快取快取
- 如何設計快取系統:快取穿透,快取擊穿,快取雪崩解決方案分析快取穿透
- flutter 獲取應用快取以及清除快取Flutter快取
- 快取穿透、快取擊穿、快取雪崩概念及解決方案快取穿透