Glide原始碼分析之快取機制
2017年9月份,記錄了ofollow,noindex">Glide4.0的整合和使用注意事項 ,後續一直沒有對Glide的原始碼進行深入學習,接下來將對Glide進行逐步深入學習,首先從Glide的快取機制開始。
我們都知道,Glide快取分為記憶體快取和磁碟快取,記憶體快取的主要作用是防止應用重複將圖片資料讀取到記憶體當中,而硬碟快取的主要作用是防止應用重複從網路或其他地方重複下載和讀取資料。正是有這樣的快取機制,一提升了圖片載入速度,從而提升了使用者體驗,二減少了不必要的流量消耗電量消耗等。那麼這樣的快取機制流程是怎樣的呢?接下來就開始對快取機制進行分析吧。
注:以下分析的前提條件是允許記憶體快取和磁碟快取。
記憶體快取
這裡不對Glide執行流程從頭開始分析,直接切入主題,從Glide快取管理類Engine的load方法開始。
1、快取Key
Glide快取是以Key-Value形式儲存。從load方法一開始,通過EngineKeyFactory的bulid方法生成EngineKey物件,這個EngineKey就是Glide中的記憶體快取Key,為了保證Key的唯一性,傳入了8個引數進行構建EngineKey,並且重寫了equals和hashCode方法。最後通過Key從記憶體快取中獲取目標快取資源EngineResource(真正的資源是被封裝到EngineResource中)物件。
//EngineKey.java class EngineKey implements Key { /** * 生成key * model:AppGlide.with(context).load(model).into(imageView) ,請求載入的 model(File, Url, Url) * signature:簽名,可選 * width, heigh:想要的目標資源大小,override(width, heigh) * transformations:資源變換,可選 * resourceClass:修改過的資源型別Class,eg:Bitmap.classGifDrawable.class * transcodeClass:指定載入的圖片格式,eg:asBitmap(),載入的圖片資源格式為Bitmap.classs * options:額外新增的任何選項 */ EngineKey(Object model,Key signature,int width,int height, Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass, Class<?> transcodeClass, Options options) { ...... } } //Engine.java load方法 EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options);
2、讀取快取
Glide記憶體快取策略採用二級快取,分別是ActiveResources和MemoryCache。
ActiveResources儲存的是當前正在使用的資源,內部是通過Map<Key, ResourceWeakReference>來儲存弱引用資源,資源在儲存到HashMap之前會被ResourceWeakReference引用。
//ActiveResources.java final class ActiveResources { ...... @VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>(); /** * 儲存當前正在顯示的資源 */ void activate(Key key, EngineResource<?> resource) { //將資源儲存在弱引用中 ResourceWeakReference toPut = new ResourceWeakReference( key, resource, getReferenceQueue(), isActiveResourceRetentionAllowed); //將若弱引用資源儲存到HashMap中 ResourceWeakReference removed = activeEngineResources.put(key, toPut); if (removed != null) { removed.reset(); } } ...... }
MemoryCache的實現類是LruResourceCache,主要實現是採用了LRU演算法,儲存不使用中的圖片資源(即圖片資源EngineResource中的引用數acquired=0)。
//GlideBuilder.java public final class GlideBuilder { private MemoryCache memoryCache; if (memoryCache == null) { memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize()); } ...... } //------------------------------------------------------------------------ //LruResourceCache.java public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache { ...... } //------------------------------------------------------------------------ //LruCache.java public class LruCache<T, Y> { private final Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true); ...... }
從原始碼中可以瞭解讀取記憶體快取的大致流程:
(1)通過Key從ActiveResources尋找目標資源。
(2)若(1)中未找到目標資源,則通過Key從MemoryCache中尋找目標資源。
(3)若(1)或者(2)中找到目標資源,則通過ResourceCallback的onResourceReady方法將獲取目標資源和快取來源回撥給SingleRequest類(SingleRequest中實現了ResourceCallback介面),在SingleRequest的onResourceReady方法中將目標資源傳遞給我們設定的RequestListener,若沒有RequestListener例項,則傳遞給封裝了ImageView的Target(若into()方法中傳入的是ImageVIew,Glide內部通過ImageViewTargetFactory建立BitmapImageViewTarget或DrawableImageViewTarget),將目標資源設定到ImageView中。
//Engine.java 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){ EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, resourceClass, transcodeClass, options); //通過Key從ActiveResources尋找目標資源。 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); if (active != null) { //通過ResourceCallback的onResourceReady方法將獲取目標資源和快取來源回撥給SingleRequest類 cb.onResourceReady(active, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } //通過Key從MemoryCache中尋找目標資源 EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null) { //通過ResourceCallback的onResourceReady方法將獲取目標資源和快取來源回撥給 cb.onResourceReady(cached, DataSource.MEMORY_CACHE); if (VERBOSE_IS_LOGGABLE) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } ...... } @Nullable private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } EngineResource<?> active = activeResources.get(key); if (active != null) { //目標資源引用數acquired加1 active.acquire(); } return active; } private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } //從MemoryCache查詢到目標資源,刪除MemoryCache集合中的目標資源引用 EngineResource<?> cached = getEngineResourceFromCache(key); if (cached != null) { //目標資源引用數acquired加1 cached.acquire(); //從快取中獲取資源顯示的同時,將資源新增到activeResources activeResources.activate(key, cached); } return cached; } private EngineResource<?> getEngineResourceFromCache(Key key) { //刪除MemoryCache集合中的目標資源 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; } ...... }
//SingleRequest.java public final class SingleRequest<R> implements Request, SizeReadyCallback, ResourceCallback, FactoryPools.Poolable { ...... /** * A callback method that should never be invoked directly. */ @SuppressWarnings("unchecked") @Override public void onResourceReady(Resource<?> resource, DataSource dataSource) { ...... onResourceReady((Resource<R>) resource, (R) received, dataSource); } private void onResourceReady(Resource<R> resource, R result, DataSource dataSource) { // We must call isFirstReadyResource before setting status. boolean isFirstResource = isFirstReadyResource(); status = Status.COMPLETE; this.resource = resource; if (glideContext.getLogLevel() <= Log.DEBUG) { Log.d(GLIDE_TAG, "Finished loading " + result.getClass().getSimpleName() + " from " + dataSource + " for " + model + " with size [" + width + "x" + height + "] in " + LogTime.getElapsedMillis(startTime) + " ms"); } isCallingCallbacks = true; try { boolean anyListenerHandledUpdatingTarget = false; //將資源回撥給我們實現的RequestListener的onResourceReady方法 if (requestListeners != null) { for (RequestListener<R> listener : requestListeners) { anyListenerHandledUpdatingTarget |= listener.onResourceReady(result, model, target, dataSource, isFirstResource); } } anyListenerHandledUpdatingTarget |= targetListener != null && targetListener.onResourceReady(result, model, target, dataSource, isFirstResource); //若存在RequestListener介面例項,則不會將資源回撥給Target(預設target:BitmapImageViewTarget和DrawableImageViewTarget,由ImageViewTargetFactory建立) if (!anyListenerHandledUpdatingTarget) { Transition<? super R> animation = animationFactory.build(dataSource, isFirstResource); target.onResourceReady(result, animation); } } finally { isCallingCallbacks = false; } notifyLoadSuccess(); } ...... }
3、寫入快取
還記得我們在MemoryCache介紹中提到的EngineResource中的圖片資源引用數acquired嗎?當資源被使用時(正在使用的資源儲存到ActiveResources中),會呼叫acquire,將變數值+1,當資源被釋放時,會呼叫release()方法,直到acquired=0,表示資源沒有被使用,這時候通過會呼叫onResourceReleased方法,將資源儲存到MemoryCache中。
//EngineResource.java class EngineResource<Z> implements Resource<Z> { private int acquired; 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) { //ResourceListener介面由Engine實現 listener.onResourceReleased(key, this); } } ...... } //------------------------------------------------------------------------ //Engine.java public class Engine implements EngineJobListener, MemoryCache.ResourceRemovedListener, EngineResource.ResourceListener { @Override public void onResourceReleased(Key cacheKey, EngineResource<?> resource) { Util.assertMainThread(); activeResources.deactivate(cacheKey); if (resource.isCacheable()) { //將不使用的圖片資源快取到MemoryCache中 cache.put(cacheKey, resource); } else { resourceRecycler.recycle(resource); } } ...... }
磁碟快取
1、快取Key
(1)未修改過的本地快取資源Key#ResourceCacheKey
//ResourceCacheKey.java final class ResourceCacheKey implements Key{ ResourceCacheKey(ArrayPool arrayPool,Key sourceKey,Key signature,int width,int height, Transformation<?> appliedTransformation,Class<?> decodedResourceClass,Options options) { ...... } } //ResourceCacheGenerator.javastartNext() currentKey = new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops helper.getArrayPool(), sourceId, helper.getSignature(), helper.getWidth(), helper.getHeight(), transformation, resourceClass, helper.getOptions());
(2)修改過的本地資源快取Key#DataCacheKey
//DataCacheKey.java final class DataCacheKey implements Key{ DataCacheKey(Key sourceKey, Key signature) { this.sourceKey = sourceKey; this.signature = signature; } } //DataCacheGenerator.javastartNext() Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
2、讀取快取
當在記憶體快取中獲取不到目標圖片資源時,在Engine的load方法中,會繼續建立任務EngineJob和DecodeJob來從磁碟快取中查詢目標資源,若磁碟中也找不到,則會從網路下載資源,下面我們進行具體分析。
在Engine的load方法中通過呼叫EngineJob的start方法啟動任務,進入start方法中,可以看到真正去執行任務的是DecodeJob(DecodeJob實現了Runnable介面),通過我們選擇的快取策略,來選擇我們將要提交任務的執行緒池。
//Engine.java load方法 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); //ResourceCallback介面由SingleRequest實現 engineJob.addCallback(cb); //開啟任務 engineJob.start(decodeJob);
通過磁碟快取策略,選擇執行緒池,若快取策略是DiskCacheStrategy.ALL / DiskCacheStrategy.RESOURCE / DiskCacheStrategy.AUTOMATIC / DiskCacheStrategy.DATA,則允許從修改或者未修改的磁碟快取資源中獲取,選擇diskCacheExecutor執行緒池來執行任務。
//EngineJob.java /** * Returns true if this job will attempt to decode a resource from the disk cache, and false if it * will always decode from source. */ public void start(DecodeJob<R> decodeJob) { this.decodeJob = decodeJob; //是否從磁碟快取中解碼資源 GlideExecutor executor = decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor(); executor.execute(decodeJob); } //------------------------------------------------------------------------ //DecodeJob.java /** * 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); //當firstStage是修改或者未修改的磁碟快取資源,返回true return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE; } private Stage getNextStage(Stage current) { switch (current) { case INITIALIZE: //所選的快取策略是否允許從修改過的磁碟快取資源中解碼資源,若是,則返回RESOURCE_CACHE,反之,繼續呼叫getNextStage方法遞迴 return diskCacheStrategy.decodeCachedResource() ? Stage.RESOURCE_CACHE : getNextStage(Stage.RESOURCE_CACHE); case RESOURCE_CACHE: //所選的快取策略是否允許從未修改的原始磁碟快取資源中解碼資源,若是,則返回Stage.DATA_CACHE,反之,繼續呼叫getNextStage方法遞迴 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); } } //------------------------------------------------------------------------ //DiskCacheStrategy.java DiskCacheStrategy類種各快取策略中對decodeCachedResource和decodeCachedData的實現這裡就不貼出來,可以自行檢視。 boolean decodeCachedResource() 返回true的策略有ALL、RESOURCE、AUTOMATIC boolean decodeCachedData() 返回true的策略有AUTOMATIC、ALL、DATA
DecodeJob提交到diskCacheExecutor執行緒池中後,執行緒池為DecodeJob分配執行緒執行任務,DecodeJob實現了Runnable介面,所以最終呼叫的是DecodeJob的run方法。接著呼叫runWrapped方法,方法中的runReason變數在DecodeJob的構造中賦了初始值INITIALIZE。然後,呼叫getNextStage方法,來獲取磁碟的載入策略Stage。最後,在getNextGenerator方法中通過磁碟載入策略來建立對應載入資源的生成器物件,呼叫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: //處理已經load到的資料 decodeFromRetrievedData(); break; default: throw new IllegalStateException("Unrecognized run reason: " + runReason); } }
磁碟的載入策略Stage,由我們在RequestOptions中設定的快取策略決定。runReason 初始預設值是INITIALIZE,所以這裡current最開始傳入的是Stage.INITIALIZE,diskCacheStrategy這個值是由RequestOptions傳入,預設快取策略是DiskCacheStrategy.AUTOMATIC。
若此時的快取策略是DiskCacheStrategy.ALL或 DiskCacheStrategy.RESOURCE或DiskCacheStrategy.AUTOMATIC,diskCacheStrategy.decodeCachedResource()將返回true,成員變數stage賦值為Stage.RESOURCE_CACHE。
若diskCacheStrategy.decodeCachedResource()返回false,繼續呼叫getNextStage方法,current傳入Stage.RESOURCE_CACHE。若快取策略是DiskCacheStrategy.AUTOMATIC或DiskCacheStrategy.ALL或DiskCacheStrategy.DATA,diskCacheStrategy.decodeCachedData()將返回true,成員變數stage賦值為Stage. DATA_CACHE。
若diskCacheStrategy.decodeCachedData()返回false,繼續呼叫getNextStage方法,current傳入Stage. DATA_CACHE,若此時設定了只從磁碟快取中獲取資源,成員變數stage賦值為Stage.FINISHED,反之成員變數stage賦值為Stage. SOURCE。
磁碟載入資料的策略有三種RESOURCE_CACHE,DATA_CACHE,SOURCE。
RESOURCE_CACHE:從修改過的本地資源快取中獲取資料。
DATA_CACHE:從未修改過的本地快取中獲取資料。
SOURCE:從原始的資源中獲取,可能是伺服器,也可能是本地的一些原始資源。
//DecodeJob.java 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.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); } }
獲得生成器物件後,呼叫runGenerators方法執行生成器,主要是呼叫生成器的startNext方法,當startNext啟動了DataFetcher載入資料的方法loadData時,返回true。從while的判斷條件中可知,若任務被取消或者資料載入成功啟動(isCancelled和isStarted其中之一為true),整個while迴圈就會結束,再接下來的判斷中,stage狀態為完成或者任務被取消並且資料載入沒有啟動,則直接notifyFailed,此次載入失敗。若while條件判斷中任務未被取消(isCancelled為true)且載入資料未成功啟動,則執行while迴圈體中的邏輯。
執行迴圈體,若當前的生成器載入資料未成功啟動,則通過getNextStage和getNextGenerator獲取下一個磁碟載入策略和生成器,直到啟動成功或者任務取消,迴圈結束。簡單來說,會依次從ResourceCacheGenerator->DataCacheGenerator->SourceGenerator這樣一個鏈執行,只要其中一個的startNext方法返回為true,則不再尋找下一個Generator。
例如,第一次迴圈currentGenerator為ResourceCacheGenerator,假設未啟動成功,再次進入getNextStage,當前stage為Stage.RESOURCE_CACHE,所以獲取的下一個stage是Stage.DATA_CACHE,通過stage獲取下一個生成器DataCacheGenerator。若第二次迴圈還未啟動成功,並且onlyRetrieveFromCache是false,返回下一個stage為Stage.SOURCE,下一個生成器是SourceGenerator,當stage是Stage.SOURCE,內部迴圈被return,請求reschedule方法重新排程,排程的最終也還是呼叫runGenerators()。
//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(); } // Otherwise a generator started a new load and we expect to be called back in // onDataFetcherReady. }
獲取快取資料,呼叫對應DataFetcherGenerator的startNext方法。
(1)ResourceCacheGenerator#startNext
首先,通過快取key獲取對應快取檔案cacheFile和對應的載入器集合modelLoaders(cacheFile是File型別,在Glide.java中註冊的載入File型別的載入器有FileLoader和ByteBufferFileLoader)。接著,遍歷載入器,通過載入器去構建LoadData物件,LoadData內部會建立一個DataFetcher物件,以FileLoader載入器為例,LoadData內部會建立一個FileFetcher物件。然後,通過呼叫FileFetcher的loadData方法,從快取中載入資料。最後,將獲取的資料通過層層往上回調,顯示在我們的目標ImageView上。
將獲取的快取資料層層回撥順序:FileLoader.java#loadData()—>ResourceCacheGenerator.java#onDataReady()—>DecodeJob.java#onDataFetcherReady() —> EngineJob.java#onResourceReady() ,通過handler切到主執行緒進行處理—>SingleRequest.java#onResourceReady()—>Target.java#onResourceReady()—>ImageView
1)FileLoader.java#loadData()
獲取的快取資料通過DataFetcher.DataCallback的onDataReady()方法向ResourceCacheGenerator傳遞(ResourceCacheGenerator實現了DataFetcher.DataCallback介面)。
2)ResourceCacheGenerator.java#onDataReady()
快取資料通過DataFetcherGenerator.FetcherReadyCallback的onDataFetcherReady()方法向DecodeJob傳遞(DecodeJob實現了DataFetcherGenerator.FetcherReadyCallback介面)
3)DecodeJob.java#onDataFetcherReady()
首先,呼叫decodeFromRetrievedData()對獲取的資料進行處理(解碼/變換等操作)。然後,將處理好的資料傳遞到notifyComplete()方法。最後,在notifyComplete()方法中呼叫DecodeJob.Callback的onResourceReady()方法,將資料傳遞到EngineJob(EngineJob實現了DecodeJob.Callback介面)。
4)EngineJob.java#onResourceReady()
首先,通過Handler將獲取的快取資料傳遞到主執行緒中進行處理。然後,呼叫handleResultOnMainThread()方法,將從快取獲取的資料封裝到EngineResource中。最後,將封裝好的EngineResource通過ResourceCallback的onResourceReady()方法傳遞到SingleRequest(SingleRequest實現了ResourceCallback介面)。
5)SingleRequest.java#onResourceReady()
GlideApp.with(context).load(url).diskCacheStrategy(DiskCacheStrategy.ALL).listener(new RequestListener<Drawable>() {...}).into(imageView/Target);
還記得上面我們載入圖片時設定的listener和into嗎?在onResourceReady中是這樣處理的,若存在RequestListener介面例項,則不會將獲取到的資源回撥給Target,反之 會傳遞給Target。into中若我們設定ImageView,原始碼中會根據我們選擇載入資源的型別(例如:asDrawable()、asBitmap()),為我們建立預設的Target(BitmapImageViewTarget或DrawableImageViewTarget,由ImageViewTargetFactory建立),無論是哪種Target,最終都會將獲取到的資源傳遞給Target的onResourceReady()方法。
6)Target.java#onResourceReady()
以DrawableImageViewTarget為例,首先,獲取的資源傳遞到了它的父類ImageViewTarget中onResourceReady()方法中,然後,呼叫setResourceInternal()方法,在方法中通過呼叫setResource方法設定資源,這個方法在父類中是一個抽象方法,那麼,具體實現在DrawableImageViewTarget類中,最後,在具體實現的setResource方法中將獲取的圖片資源設定到ImageVew中。
//ResourceCacheGenerator.java @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; } throw new IllegalStateException( "Failed to find any load path from " + helper.getModelClass() + " to " + helper.getTranscodeClass()); } 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. 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; }
//FileLoader.java @Override public LoadData<Data> buildLoadData(@NonNull File model, int width, int height, @NonNull Options options) { return new LoadData<>(new ObjectKey(model), new FileFetcher<>(model, fileOpener)); } @Override public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> callback) { try { data = opener.open(file); } catch (FileNotFoundException e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Failed to open file", e); } callback.onLoadFailed(e); return; } callback.onDataReady(data); }
(2)DataCacheGenerator#startNext
與 ResourceCacheGenerator的流程差不多,區別在與它們的快取key不一樣,前者是ResourceCacheKey,後者是DataCacheKey,它們構造key所需的引數不同,具體請看原始碼,這裡就不介紹了。
//DataCacheGenerator.java @Override public boolean startNext() { while (modelLoaders == null || !hasNextModelLoader()) { sourceIdIndex++; if (sourceIdIndex >= cacheKeys.size()) { return false; } Key sourceId = cacheKeys.get(sourceIdIndex); // PMD.AvoidInstantiatingObjectsInLoops The loop iterates a limited number of times // and the actions it performs are much more expensive than a single allocation. @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") Key originalKey = new DataCacheKey(sourceId, helper.getSignature()); cacheFile = helper.getDiskCache().get(originalKey); if (cacheFile != null) { this.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; }
(3)SourceGenerator#startNext
dataToCache初始是null,所以,我們先看While裡面的流程。磁碟中不存在快取,所以這是時候的model是GlideUrl,通過GlideUrl獲取到載入器型別是HttpGlideUrlLoader,LoadData中對應的DataFetcher是HttpUrlFetcher。呼叫HttpUrlFetcher的loadData方法,從網路上或者其他地方下載資源,然後將結果通過DataFetcher.DataCallback的onDataReady()方法向SourceGenerator傳遞(SourceGenerator實現了DataFetcher.DataCallback介面)。在SourceGenerator的onDataReady()中判斷資料來源,若資料不是來自遠端,則和之前所說的將資料層層往上回調傳遞流程是一樣的;若來自遠端,則將資料賦值給dataToCache並且重新排程,最終會再次呼叫SourceGenerator#startNext,這時dataToCache不等於null了。
dataToCache不等於null時,呼叫cacheData()方法,將資料編碼作為未修改的資源儲存到磁碟中,然後,建立DataCacheGenerator生成器,最後,按照DataCacheGenerator#startNext流程執行。
//SourceGenerator.java @Override public boolean startNext() { if (dataToCache != null) { Object data = dataToCache; dataToCache = null; cacheData(data); } if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) { return true; } sourceCacheGenerator = null; loadData = null; boolean started = false; while (!started && hasNextModelLoader()) { loadData = helper.getLoadData().get(loadDataListIndex++); if (loadData != null && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { started = true; loadData.fetcher.loadData(helper.getPriority(), this); } } return started; } 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); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Finished encoding source to cache" + ", key: " + originalKey + ", data: " + dataToCache + ", encoder: " + encoder + ", duration: " + LogTime.getElapsedMillis(startTime)); } } finally { loadData.fetcher.cleanup(); } sourceCacheGenerator = new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this); }
3、寫入快取
上面我們有提到了無磁碟快取資料時,通過SourceGenerator獲取網路或者其他地方的資料,在SourceGenerator的dataToCache不為null時,會呼叫cacheData方法將資源作為未修改過的資源快取到磁碟中。
還有一處寫入磁碟快取的地方,那就我們在層層往上回調資料時,傳到DecodeJob,對獲取的資源資料進行處理後,在notifyEncodeAndRelease()方法中會將經過解碼變換好的資源編碼儲存到磁碟快取中。
//DecodeJob.java private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) { if (resource instanceof Initializable) { ((Initializable) resource).initialize(); } Resource<R> result = resource; LockedResource<R> lockedResource = null; if (deferredEncodeManager.hasResourceToEncode()) { lockedResource = LockedResource.obtain(resource); result = lockedResource; } notifyComplete(result, dataSource); stage = Stage.ENCODE; try { //是否存在編碼器 //Glide.java中預設註冊的編碼器有ByteBufferEncoder、StreamEncoder、BitmapEncoder、BitmapDrawableEncoder、GifDrawableEncoder if (deferredEncodeManager.hasResourceToEncode()) { //將處理好的資源編碼儲存到磁碟快取中 deferredEncodeManager.encode(diskCacheProvider, options); } } finally { if (lockedResource != null) { lockedResource.unlock(); } } // Call onEncodeComplete outside the finally block so that it's not called if the encode process // throws. onEncodeComplete(); } /** * 資源解碼變換 */ @Synthetic @NonNull <Z> Resource<Z> onResourceDecoded(DataSource dataSource, @NonNull Resource<Z> decoded) { @SuppressWarnings("unchecked") Class<Z> resourceSubClass = (Class<Z>) decoded.get().getClass(); Transformation<Z> appliedTransformation = null; Resource<Z> transformed = decoded; if (dataSource != DataSource.RESOURCE_DISK_CACHE) { //圖片資源變換 appliedTransformation = decodeHelper.getTransformation(resourceSubClass); transformed = appliedTransformation.transform(glideContext, decoded, width, height); } // TODO: Make this the responsibility of the Transformation. if (!decoded.equals(transformed)) { decoded.recycle(); } final EncodeStrategy encodeStrategy; final ResourceEncoder<Z> encoder; //Glide.java中預設註冊的編碼器:Encoder:ByteBufferEncoder、StreamEncoder、BitmapEncoder、BitmapDrawableEncoder、GifDrawableEncoder if (decodeHelper.isResourceEncoderAvailable(transformed)) { encoder = decodeHelper.getResultEncoder(transformed); encodeStrategy = encoder.getEncodeStrategy(options); } else { encoder = null; encodeStrategy = EncodeStrategy.NONE; } Resource<Z> result = transformed; boolean isFromAlternateCacheKey = !decodeHelper.isSourceKey(currentSourceKey); if (diskCacheStrategy.isResourceCacheable(isFromAlternateCacheKey, dataSource, encodeStrategy)) { //需要快取變換後的資源 if (encoder == null) { throw new Registry.NoResultEncoderAvailableException(transformed.get().getClass()); } final Key key; switch (encodeStrategy) { case SOURCE: key = new DataCacheKey(currentSourceKey, signature); break; case TRANSFORMED: key = new ResourceCacheKey( decodeHelper.getArrayPool(), currentSourceKey, signature, width, height, appliedTransformation, resourceSubClass, options); break; default: throw new IllegalArgumentException("Unknown strategy: " + encodeStrategy); } LockedResource<Z> lockedResult = LockedResource.obtain(transformed); //這裡的encoder將賦值給toEncode,toEncode!=null時hasResourceToEncode()返回true deferredEncodeManager.init(key, encoder, lockedResult); result = lockedResult; } return result; }