Glide原始碼分析之二 into()上篇
相關文章
into()方法做了什麼
== 關鍵詞 ==:
Transform對圖片進行裁剪操作之後進行轉化
Target監控生命週期的
ModelLoader主要是負責從資料來源中獲取原始資料(imputStream)
DataFetcher主要是負責將資料來源中的原始資料轉化成各種我們所需要的不同的資料型別
RescourceTranscoder主要負責將獲取到的原始資料(io流、資料流)解碼成我們的bitmap,解碼之後的資源稱之為Rescource,RescourceTranscoder就是負責完成解碼的物件
Engine負責任務建立,發起,回撥,資源的管理
DataLoaderProvider是負責我們圖片編解碼。(是一個介面)data->rescource data->持久化到本地檔案(磁碟快取)
ImageVideoModelLoader非常重要
into()方法絕對是Glide裡面邏輯最為複雜的地方(所有跟ui相關的都在這個方法進行),沒有之一,下面我們來分析一下。
into()方法中並沒有任何邏輯,只有一句super.into(view)。那麼很顯然,into()方法的具體邏輯都是在DrawableRequestBuilder的父類當中了。
public Target<TranscodeType> into(ImageView view) { //判斷是不是主執行緒 Util.assertMainThread(); if (view == null) { throw new IllegalArgumentException("You must pass in a non null View"); } if (!isTransformationSet && view.getScaleType() != null) { switch (view.getScaleType()) { //center_crop的意思是將我們的圖片尺寸按照imageview的尺寸進行適當縮放展示中心區域 case CENTER_CROP: applyCenterCrop(); break; case FIT_CENTER: case FIT_START: case FIT_END: applyFitCenter(); break; //$CASES-OMITTED$ default: // Do nothing. } } //關鍵句 上面的程式碼主要是圖片處理 return into(glide.buildImageViewTarget(view, transcodeClass)); }
這裡講到了Util.assertMainThread();方法,在之前的with和into裡都沒有見過這個方法。其實從名字我們已經可以看出一二:
/** * Throws an {@link java.lang.IllegalArgumentException} if called on a thread other than the main thread. */ public static void assertMainThread() { if (!isOnMainThread()) { throw new IllegalArgumentException("You must call this method on the main thread"); } }
如果不是主執行緒則會丟擲異常,其實可以表明我們glide從into()開始所有圖片的操作都會在主執行緒中進行。
繼續看into()方法,其實前面的邏輯可以先不用管,從case的名字也可以看出大概就是圖片處理的方式,主要看最後一行into(glide.buildImageViewTarget(view, transcodeClass));他分為兩步:
- glide.buildImageViewTarget(view, transcodeClass)構建出一個Target物件用來最終展示圖片;
`` <R> Target<R> buildImageViewTarget(ImageView imageView, Class<R> transcodedClass) { return imageViewTargetFactory.buildTarget(imageView, transcodedClass); } public class ImageViewTargetFactory { @SuppressWarnings("unchecked") public <Z> Target<Z> buildTarget(ImageView view, Class<Z> clazz) { if (GlideDrawable.class.isAssignableFrom(clazz)) { //其他情況都是GLideDrawableImageViewTarget(正常載入或者asGif()) return (Target<Z>) new GlideDrawableImageViewTarget(view); } else if (Bitmap.class.equals(clazz)) { //如果你使用的是asBitmap()方法則會新建一個BitmapImageViewTarget return (Target<Z>) new BitmapImageViewTarget(view); } else if (Drawable.class.isAssignableFrom(clazz)) { return (Target<Z>) new DrawableImageViewTarget(view); } else { throw new IllegalArgumentException("Unhandled class: " + clazz + ", try .as*(Class).transcode(ResourceTranscoder)"); } }
}
```
這個class引數其實基本上只有兩種情況,
- 如果你在使用Glide載入圖片的時候呼叫了asBitmap()方法,那麼這裡就會構建出BitmapImageViewTarget物件,
- 否則的話構建的都是GlideDrawableImageViewTarget物件。
至於上述程式碼中的DrawableImageViewTarget物件,這個通常都是用不到的,我們可以暫時不用管它。
通過glide.buildImageViewTarget()方法,我們構建出了一個GlideDrawableImageViewTarget物件。那現在回到剛才into()方法的最後一行,可以看到,這裡又將這個引數傳入到了GenericRequestBuilder另一個接收Target物件的into()方法當中了。
- into()方法
public <Y extends Target<TranscodeType>> Y into(Y target) { Util.assertMainThread(); if (target == null) { throw new IllegalArgumentException("You must pass in a non null Target"); } if (!isModelSet) { throw new IllegalArgumentException("You must first set a model (try #load())"); } Request previous = target.getRequest(); if (previous != null) { previous.clear(); requestTracker.removeRequest(previous); previous.recycle(); } //注意這一行 Request request = buildRequest(target); target.setRequest(request); lifecycle.addListener(target); requestTracker.runRequest(request); return target; }
看這段程式碼 第二個if有一個isModelSet物件
那麼他是用來做什麼的呢。
其實這個物件也很好理解官方也給出了很明晰的註釋
// model may occasionally be null, so to enforce that load() was called, set a boolean rather than relying on model // not to be null. private boolean isModelSet;
其實into方法最核心,最關鍵的是通過buildRequest()方法構建出了一個Request物件,還有requestTracker.runRequest(request);來執行這個Request。
那麼Request物件是用來做什麼的?
其實Request是用來發出載入圖片請求的,它是Glide中非常關鍵的一個元件。我們先來看buildRequest()方法是如何構建Request物件的:
private Request buildRequest(Target<TranscodeType> target) { if (priority == null) { priority = Priority.NORMAL; } return buildRequestRecursive(target, null); } private Request buildRequestRecursive(Target<TranscodeType> target, ThumbnailRequestCoordinator parentCoordinator) { if (thumbnailRequestBuilder != null) { if (isThumbnailBuilt) { throw new IllegalStateException("You cannot use a request as both the main request and a thumbnail, " + "consider using clone() on the request(s) passed to thumbnail()"); } // Recursive case: contains a potentially recursive thumbnail request builder. if (thumbnailRequestBuilder.animationFactory.equals(NoAnimation.getFactory())) { thumbnailRequestBuilder.animationFactory = animationFactory; } if (thumbnailRequestBuilder.priority == null) { thumbnailRequestBuilder.priority = getThumbnailPriority(); } if (Util.isValidDimensions(overrideWidth, overrideHeight) && !Util.isValidDimensions(thumbnailRequestBuilder.overrideWidth, thumbnailRequestBuilder.overrideHeight)) { thumbnailRequestBuilder.override(overrideWidth, overrideHeight); } ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator); Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator); // Guard against infinite recursion. isThumbnailBuilt = true; // Recursively generate thumbnail requests. Request thumbRequest = thumbnailRequestBuilder.buildRequestRecursive(target, coordinator); isThumbnailBuilt = false; coordinator.setRequests(fullRequest, thumbRequest); return coordinator; } else if (thumbSizeMultiplier != null) { // Base case: thumbnail multiplier generates a thumbnail request, but cannot recurse. ThumbnailRequestCoordinator coordinator = new ThumbnailRequestCoordinator(parentCoordinator); Request fullRequest = obtainRequest(target, sizeMultiplier, priority, coordinator); Request thumbnailRequest = obtainRequest(target, thumbSizeMultiplier, getThumbnailPriority(), coordinator); coordinator.setRequests(fullRequest, thumbnailRequest); return coordinator; } else { //注意這一行 // Base case: no thumbnail. return obtainRequest(target, sizeMultiplier, priority, parentCoordinator); } }
buildRequest()方法的內部其實又呼叫了buildRequestRecursive()方法,而buildRequestRecursive()方法中的程式碼雖然有點長,但是其中90%的程式碼都是在處理縮圖的。在我們的平時使用中,最主要的應該是最後一行
obtainRequest(target, sizeMultiplier, priority, parentCoordinator);
這裡呼叫了obtainRequest()方法來獲取一個Request物件,而obtainRequest()方法中又去呼叫了GenericRequest的obtain()方法。注意這個obtain()方法需要傳入非常多的引數,而其中很多的引數我們都是比較熟悉的,像什麼placeholderId、errorPlaceholder、diskCacheStrategy等等。因此,我們就有理由猜測,剛才在load()方法中呼叫的所有API,其實都是在這裡組裝到Request物件當中的。
private Request obtainRequest(Target<TranscodeType> target, float sizeMultiplier, Priority priority, RequestCoordinator requestCoordinator) { return GenericRequest.obtain( loadProvider, model, signature, context, priority, target, sizeMultiplier, placeholderDrawable, placeholderId, errorPlaceholder, errorId, fallbackDrawable, fallbackResource, requestListener, requestCoordinator, glide.getEngine(), transformation, transcodeClass, isCacheable, animationFactory, overrideWidth, overrideHeight, diskCacheStrategy); } public static <A, T, Z, R> GenericRequest<A, T, Z, R> obtain( LoadProvider<A, T, Z, R> loadProvider, A model, Key signature, Context context, Priority priority, Target<R> target, float sizeMultiplier, Drawable placeholderDrawable, int placeholderResourceId, Drawable errorDrawable, int errorResourceId, Drawable fallbackDrawable, int fallbackResourceId, RequestListener<? super A, R> requestListener, RequestCoordinator requestCoordinator, Engine engine, Transformation<Z> transformation, Class<R> transcodeClass, boolean isMemoryCacheable, GlideAnimationFactory<R> animationFactory, int overrideWidth, int overrideHeight, DiskCacheStrategy diskCacheStrategy) { @SuppressWarnings("unchecked") GenericRequest<A, T, Z, R> request = (GenericRequest<A, T, Z, R>) REQUEST_POOL.poll(); if (request == null) { request = new GenericRequest<A, T, Z, R>(); } request.init(loadProvider, model, signature, context, priority, target, sizeMultiplier, placeholderDrawable, placeholderResourceId, errorDrawable, errorResourceId, fallbackDrawable, fallbackResourceId, requestListener, requestCoordinator, engine, transformation, transcodeClass, isMemoryCacheable, animationFactory, overrideWidth, overrideHeight, diskCacheStrategy); return request; }
可以看到,這裡其實就是去new了一個GenericRequest物件,並在最後一行返回,也就是說,obtain()方法實際上獲得的就是一個GenericRequest物件。另外這裡又在第35行呼叫了GenericRequest的init(),裡面主要就是一些賦值的程式碼,將傳入的這些引數賦值到GenericRequest的成員變數當中,我們就不再跟進去看了。
好,那現在解決了構建Request物件的問題,接下來我們看一下這個Request物件又是怎麼執行的。回到剛才的into()方法,你會發現在第18行呼叫了requestTracker.runRequest()方法來去執行這個Request,那麼我們跟進去瞧一瞧,如下所示:
public void runRequest(Request request) { requests.add(request); if (!isPaused) { request.begin(); } else { pendingRequests.add(request); } }
這裡有一個簡單的邏輯判斷,就是先判斷Glide當前是不是處理暫停狀態,如果不是暫停狀態就呼叫Request的begin()方法來執行Request,否則的話就先將Request新增到待執行佇列裡面,等暫停狀態解除了之後再執行。
我們重點來看這個begin()方法。由於當前的Request物件是一個GenericRequest,因此這裡就需要看GenericRequest中的begin()方法了,如下所示:
@Override public void begin() { startTime = LogTime.getLogTime(); if (model == null) { //載入錯誤圖片 setErrorPlaceholder onException(null); return; } status = Status.WAITING_FOR_SIZE; //是否呼叫了override()設定了寬高 if (Util.isValidDimensions(overrideWidth, overrideHeight)) { onSizeReady(overrideWidth, overrideHeight); } else { target.getSize(this); } if (!isComplete() && !isFailed() && canNotifyStatusChanged()) { target.onLoadStarted(getPlaceholderDrawable()); } if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("finished run method in " + LogTime.getElapsedMillis(startTime)); } }
這裡我們來注意幾個細節,首先如果model等於null,model也就是我們在第二步load()方法中傳入的圖片URL地址,這個時候會呼叫onException()方法。如果你跟到onException()方法裡面去看看,你會發現它最終會呼叫到一個setErrorPlaceholder()當中,如下所示:
@Override public void onException(Exception e) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "load failed", e); } status = Status.FAILED; //TODO: what if this is a thumbnail request? if (requestListener == null || !requestListener.onException(e, model, target, isFirstReadyResource())) { setErrorPlaceholder(e); } } private void setErrorPlaceholder(Exception e) { if (!canNotifyStatusChanged()) { return; } Drawable error = model == null ? getFallbackDrawable() : null; if (error == null) { error = getErrorDrawable(); } if (error == null) { error = getPlaceholderDrawable(); } target.onLoadFailed(e, error); }
這個方法中會先去獲取一個error的佔位圖,如果獲取不到的話會再去獲取一個loading佔位圖,然後呼叫target.onLoadFailed()方法並將佔位圖傳入。
很簡單,其實就是將這張error佔位圖顯示到ImageView上而已,因為現在出現了異常,沒辦法展示正常的圖片了。而如果你仔細看下剛才begin()方法,你會發現它又呼叫了一個target.onLoadStarted()方法,並傳入了一個loading佔位圖,在也就說,在圖片請求開始之前,會先使用這張佔位圖代替最終的圖片顯示。這也是我們在上一篇文章中學過的placeholder()和error()這兩個佔位圖API底層的實現原理。
好,那麼我們繼續回到begin()方法。剛才講了佔位圖的實現,那麼具體的圖片載入又是從哪裡開始的呢?是在begin()方法的第10行和第12行。這裡要分兩種情況,一種是你使用了override() API為圖片指定了一個固定的寬高,一種是沒有指定。如果指定了的話,就會執行第10行程式碼,呼叫onSizeReady()方法。如果沒指定的話,就會執行第12行程式碼,呼叫target.getSize()方法。這個target.getSize()方法的內部會根據ImageView的layout_width和layout_height值做一系列的計算,來算出圖片應該的寬高。不管是哪種情況,最終都會呼叫到onSizeReady()方法,在這裡進行下一步操作。所以來看看onSizeReady方法到底做了什麼:
/** * A callback method that should never be invoked directly. */ @Override public void onSizeReady(int width, int height) { if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("Got onSizeReady in " + LogTime.getElapsedMillis(startTime)); } if (status != Status.WAITING_FOR_SIZE) { return; } status = Status.RUNNING; width = Math.round(sizeMultiplier * width); height = Math.round(sizeMultiplier * height); //glide程式碼噁心就噁心在這裡,看到這一行的時候有個疑問,loadProvider是什麼時候建立的???loadProvider是個啥? ModelLoader<A, T> modelLoader = loadProvider.getModelLoader(); final DataFetcher<T> dataFetcher = modelLoader.getResourceFetcher(model, width, height); if (dataFetcher == null) { onException(new Exception("Failed to load model: \'" + model + "\'")); return; } ResourceTranscoder<Z, R> transcoder = loadProvider.getTranscoder(); if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("finished setup for calling load in " + LogTime.getElapsedMillis(startTime)); } loadedFromMemoryCache = true; loadStatus = engine.load(signature, width, height, dataFetcher, loadProvider, transformation, transcoder, priority, isMemoryCacheable, diskCacheStrategy, this); loadedFromMemoryCache = resource != null; if (Log.isLoggable(TAG, Log.VERBOSE)) { logV("finished onSizeReady in " + LogTime.getElapsedMillis(startTime)); } }
從這裡開始,真正複雜的地方來了,我們需要慢慢進行分析。看完這一段程式碼,腦子裡的想法就是:
loadProvider是什麼?ModelLoader是什麼?DateFetcher和ResourceTranscoder又是什麼?
首先,要搞清楚這點,需要先回到第二步的load()方法當中。還記得load()方法是返回一個DrawableTypeRequest物件嗎?剛才我們只是分析了DrawableTypeRequest當中的asBitmap()和asGif()方法,並沒有仔細看它的建構函式,現在我們重新來看一下DrawableTypeRequest類的建構函式:
private static <A, Z, R> FixedLoadProvider<A, ImageVideoWrapper, Z, R> buildProvider(Glide glide, ModelLoader<A, InputStream> streamModelLoader, ModelLoader<A, ParcelFileDescriptor> fileDescriptorModelLoader, Class<Z> resourceClass, Class<R> transcodedClass, ResourceTranscoder<Z, R> transcoder) { if (streamModelLoader == null && fileDescriptorModelLoader == null) { return null; } if (transcoder == null) { //用於對圖片進行轉碼的,由於ResourceTranscoder是一個介面,這裡實際會構建出一個GifBitmapWrapperDrawableTranscoder物件。 transcoder = glide.buildTranscoder(resourceClass, transcodedClass); } //它是用於對圖片進行編解碼的,由於DataLoadProvider是一個介面,這裡實際會構建出一個ImageVideoGifDrawableLoadProvider物件。 DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider = glide.buildDataProvider(ImageVideoWrapper.class, resourceClass); ImageVideoModelLoader<A> modelLoader = new ImageVideoModelLoader<A>(streamModelLoader, fileDescriptorModelLoader); //new出一個FixedLoadProvider,並把剛才構建的出來的GifBitmapWrapperDrawableTranscoder、ImageVideoModelLoader、ImageVideoGifDrawableLoadProvider都封裝進去,這個也就是onSizeReady()方法中的loadProvider了。 return new FixedLoadProvider<A, ImageVideoWrapper, Z, R>(modelLoader, transcoder, dataLoadProvider); } DrawableTypeRequest(Class<ModelType> modelClass, ModelLoader<ModelType, InputStream> streamModelLoader, ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader, Context context, Glide glide, RequestTracker requestTracker, Lifecycle lifecycle, RequestManager.OptionsApplier optionsApplier) { super(context, modelClass, buildProvider(glide, streamModelLoader, fileDescriptorModelLoader, GifBitmapWrapper.class, GlideDrawable.class, null), glide, requestTracker, lifecycle); this.streamModelLoader = streamModelLoader; this.fileDescriptorModelLoader = fileDescriptorModelLoader; this.optionsApplier = optionsApplier; }
可以看到建構函式中,呼叫了一個buildProvider()方法,並把streamModelLoader和fileDescriptorModelLoader等引數傳入到這個方法中,這兩個ModelLoader就是之前在loadGeneric()方法中構建出來的。
呼叫了glide.buildTranscoder()方法來構建一個ResourceTranscoder,它是用於對圖片進行轉碼的,由於ResourceTranscoder是一個介面,這裡實際會構建出一個GifBitmapWrapperDrawableTranscoder物件。
接下來呼叫了glide.buildDataProvider()方法來構建一個DataLoadProvider,它是用於對圖片進行編解碼的,由於DataLoadProvider是一個介面,這裡實際會構建出一個ImageVideoGifDrawableLoadProvider物件。
接下來new了一個ImageVideoModelLoader的例項,並把之前loadGeneric()方法中構建的兩個ModelLoader封裝到了ImageVideoModelLoader當中。
最後new出一個FixedLoadProvider,並把剛才構建的出來的GifBitmapWrapperDrawableTranscoder、ImageVideoModelLoader、ImageVideoGifDrawableLoadProvider都封裝進去,這個也就是onSizeReady()方法中的loadProvider了。
那麼我們回到onSizeReady()方法中,在onSizeReady()方法分別呼叫了loadProvider的getModelLoader()方法和getTranscoder()方法,那麼得到的物件也就是剛才我們分析的ImageVideoModelLoader和GifBitmapWrapperDrawableTranscoder了。又呼叫了ImageVideoModelLoader的getResourceFetcher()方法,這裡我們又需要跟進去瞧一瞧了,
@Override public DataFetcher<ImageVideoWrapper> getResourceFetcher(A model, int width, int height) { DataFetcher<InputStream> streamFetcher = null; if (streamLoader != null) { //loadGeneric()方法中構建出的HttpUrlGlideUrlLoader,呼叫它的getResourceFetcher()方法會得到一個HttpUrlFetcher物件 streamFetcher = streamLoader.getResourceFetcher(model, width, height); } DataFetcher<ParcelFileDescriptor> fileDescriptorFetcher = null; if (fileDescriptorLoader != null) { fileDescriptorFetcher = fileDescriptorLoader.getResourceFetcher(model, width, height); } if (streamFetcher != null || fileDescriptorFetcher != null) { //new出了一個ImageVideoFetcher物件,並把獲得的HttpUrlFetcher物件傳進去。 return new ImageVideoFetcher(streamFetcher, fileDescriptorFetcher); } else { return null; } }
可以看到,會先呼叫streamLoader.getResourceFetcher()方法獲取一個DataFetcher,而這個streamLoader其實就是我們在loadGeneric()方法中構建出的HttpUrlGlideUrlLoader(StreamStringLoader郭霖寫的是在這裡獲取),呼叫它的getResourceFetcher()方法會得到一個HttpUrlFetcher物件。然後new出了一個ImageVideoFetcher物件,並把獲得的HttpUrlFetcher物件傳進去。也就是說,ImageVideoModelLoader的getResourceFetcher()方法得到的是一個ImageVideoFetcher。
那麼我們再次回到onSizeReady()方法,在onSizeReady()方中,這裡將剛才獲得的ImageVideoFetcher、GifBitmapWrapperDrawableTranscoder等等一系列的值一起傳入到了Engine的load()方法當中。接下來我們就要看一看,這個Engine的load()方法當中,到底做了什麼?程式碼如下所示:
public <T, Z, R> LoadStatus load(Key signature, int width, int height, DataFetcher<T> fetcher, DataLoadProvider<T, Z> loadProvider, Transformation<Z> transformation, ResourceTranscoder<Z, R> transcoder, Priority priority, boolean isMemoryCacheable, DiskCacheStrategy diskCacheStrategy, ResourceCallback cb) { Util.assertMainThread(); long startTime = LogTime.getLogTime(); final String id = fetcher.getId(); EngineKey key = keyFactory.buildKey(id, signature, width, height, loadProvider.getCacheDecoder(), loadProvider.getSourceDecoder(), transformation, loadProvider.getEncoder(), transcoder, loadProvider.getSourceEncoder()); //從快取中讀取對應的資原始檔(LruCache) EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); if (cached != null) { cb.onResourceReady(cached); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from cache", startTime, key); } return null; } //從可用弱引用中尋找快取 isMemoryCacheable就是是否跳過記憶體快取 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); if (active != null) { cb.onResourceReady(active); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Loaded resource from active resources", startTime, key); } return null; } EngineJob current = jobs.get(key); if (current != null) { current.addCallback(cb); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Added to existing load", startTime, key); } return new LoadStatus(cb, current); } //前面大多都是處理快取邏輯從這裡開始看 EngineJob engineJob = engineJobFactory.build(key, isMemoryCacheable); DecodeJob<T, Z, R> decodeJob = new DecodeJob<T, Z, R>(key, width, height, fetcher, loadProvider, transformation, transcoder, diskCacheProvider, diskCacheStrategy, priority); EngineRunnable runnable = new EngineRunnable(engineJob, decodeJob, priority); jobs.put(key, engineJob); engineJob.addCallback(cb); engineJob.start(runnable); if (Log.isLoggable(TAG, Log.VERBOSE)) { logWithTimeAndKey("Started new load", startTime, key); } return new LoadStatus(cb, engineJob); }

load邏輯
EngineJob幹嘛的?那EngineRunnable,DecodeJob呢?
排程DecodeJob,新增,移除資源回撥,並notify回撥(實現了一個比較重要的介面MainThreadCallback用於Engine任務完成時回撥主執行緒)
DecodeJob排程任務的核心類,整個請求的繁重工作都在這裡完成:處理來自快取或者原始的資源,應用轉換動畫以及transcode。
Glide快取機制來啦
這裡穿插一下 講一下glide的快取機制
Glide是使用了LruCache做快取機制,當然你可以不喜歡你可以自己實現自定義的快取機制,glide是支援自定義快取的。
兩級記憶體快取:硬碟快取和內純快取。(硬碟快取和記憶體快取都是使用了LruCache,只是硬碟快取使用了自定義的DiskLruCache基本沒差)
先從LruCache中尋找,如果找到了快取,將圖片移出LruCache,加入activeResources弱引用快取。如果在LruCache中沒找到的話到activeResources弱引用快取中尋找。如果在記憶體快取中找到,則引用計數加1。使用中的圖片用弱引用快取來管理,沒有使用的圖片用LruCache來管理,判斷圖片有沒有使用的依據之一是引用計數,當引用計數等於0時,將圖片從弱引用快取中移走,加入LruCache中。(acquire()和release()方法)
//快取中 private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } EngineResource<?> cached = getEngineResourceFromCache(key); if (cached != null) { //注意這個方法 如果不為空說明快取中有我們需要的資源,則將他加入到弱引用中,並呼叫acquire方法 ++ cached.acquire(); activeResources.put(key, new ResourceWeakReference(key, cached, getReferenceQueue())); } return cached; } @SuppressWarnings("unchecked") 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 /*isCacheable*/); } return result; } //弱引用 private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) { if (!isMemoryCacheable) { return null; } EngineResource<?> active = null; WeakReference<EngineResource<?>> activeRef = activeResources.get(key); if (activeRef != null) { active = activeRef.get(); if (active != null) { //注意這個方法 active.acquire(); } else { activeResources.remove(key); } } return active; } 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); } }
我們已經搞明白了記憶體快取讀取的原理,接下來的問題就是記憶體快取是在哪裡寫入的呢?這裡我們又要回顧一下上一篇文章中的內容了。還記不記得我們之前分析過,當圖片載入完成之後,會在EngineJob當中通過Handler傳送一條訊息將執行邏輯切回到主執行緒當中,從而執行handleResultOnMainThread()方法。那麼我們現在重新來看一下這個方法,程式碼如下所示:
private void handleResultOnMainThread() { if (isCancelled) { resource.recycle(); return; } else if (cbs.isEmpty()) { throw new IllegalStateException("Received a resource without any callbacks to notify"); } engineResource = engineResourceFactory.build(resource, isCacheable); hasResource = true; // Hold on to resource for duration of request so we don't recycle it in the middle of notifying if it // synchronously released by one of the callbacks. engineResource.acquire(); listener.onEngineJobComplete(key, engineResource); for (ResourceCallback cb : cbs) { if (!isInIgnoredCallbacks(cb)) { engineResource.acquire(); cb.onResourceReady(engineResource); } } // Our request is complete, so we can release the resource. engineResource.release(); } public void onEngineJobComplete(Key key, EngineResource<?> resource) { Util.assertMainThread(); // A null resource indicates that the load failed, usually due to an exception. if (resource != null) { resource.setResourceListener(key, this); if (resource.isCacheable()) { activeResources.put(key, new ResourceWeakReference(key, resource, getReferenceQueue())); } } // TODO: should this check that the engine job is still current? jobs.remove(key); }
再提一點 在Glide當中我們所說的資源其實是分兩種
- 原圖(SOURCE) :原始圖片
- 處理圖(RESULT) :經過壓縮和變形等處理後的圖片
快取結束。