Glide原始碼分析之一 with() + into()解析
相關文章
文章基於3.7.0。主要參考郭神的Glide原始碼解析。
簡單使用
String imgUrl = "https://www.baidu.com/img/bd_logo1.png?where=super"; Glide.with(this).load(imgUrl).into(imageView); Glide.with(getApplicationContext()) .load(imgUrl) .asGif() .asBitmap() .placeholder(R.mipmap.ic_launcher) .error(R.mipmap.ic_launcher) .override(300,300) .fitCenter() .centerCrop() .skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.ALL) .diskCacheStrategy(DiskCacheStrategy.RESULT) .diskCacheStrategy(DiskCacheStrategy.SOURCE) .priority(Priority.HIGH) .into(imageView);
Model -(ModelLoader)-> Data -(Decoder)-> Resource -(Transform)-> TransformedResource -(Transcode)-> TranscodedResource --> Target
with() 到底做了什麼?
==關鍵類== :RequestManagerRetriever、RequestManager
首先需要注意 with方法傳入的context物件將會決定我們Glide存活的生命週期。
/** Begin a load with Glide by passing in a context. * <p> *This method is appropriate for resources that will be used outside of the normal fragment or activity *lifecycle (For example in services, or for notification thumbnails). * </p> * * @see #with(android.app.Activity) * @see #with(android.app.Fragment) * @see #with(android.support.v4.app.Fragment) * @see #with(android.support.v4.app.FragmentActivity) * * @param context Any context, will not be retained. * @return A RequestManager for the top level application that can be used to start a load. */ public static RequestManager with(Context context) { RequestManagerRetriever retriever = RequestManagerRetriever.get(); return retriever.get(context); } /** * Begin a load with Glide that will be tied to the given {@link android.app.Activity}'s lifecycle and that uses the * given {@link Activity}'s default options. * * @param activity The activity to use. * @return A RequestManager for the given activity that can be used to start a load. */ public static RequestManager with(Activity activity) { RequestManagerRetriever retriever = RequestManagerRetriever.get(); return retriever.get(activity); }
可以看到,with()方法的過載種類非常多,既可以傳入Activity,也可以傳入Fragment或者是Context。其實都是先呼叫RequestManagerRetriever的靜態get()方法得到一個RequestManagerRetriever物件,這個靜態get()方法是一個最基礎的單例模式。然後再呼叫RequestManagerRetriever的例項get()方法,去獲取RequestManager物件。其實無非就是兩種情況而已,即傳入Application型別的引數,和傳入非Application型別的引數。
傳入Application型別的引數
程式碼如下:
public static RequestManager with(Context context) { RequestManagerRetriever retriever = RequestManagerRetriever.get(); return retriever.get(context); } public RequestManager get(Context context) { if (context == null) { throw new IllegalArgumentException("You cannot start a load on a null Context"); } else if (Util.isOnMainThread() && !(context instanceof Application)) { if (context instanceof FragmentActivity) { return get((FragmentActivity) context); } else if (context instanceof Activity) { return get((Activity) context); } else if (context instanceof ContextWrapper) { return get(((ContextWrapper) context).getBaseContext()); } } return getApplicationManager(context); } private RequestManager getApplicationManager(Context context) { // Either an application context or we're on a background thread. if (applicationManager == null) { synchronized (this) { if (applicationManager == null) { // Normally pause/resume is taken care of by the fragment we add to the fragment or activity. // However, in this case since the manager attached to the application will not receive lifecycle // events, we must force the manager to start resumed using ApplicationLifecycle. applicationManager = new RequestManager(context.getApplicationContext(), new ApplicationLifecycle(), new EmptyRequestManagerTreeNode()); } } } return applicationManager; }
這裡插一句 不知道有沒有人好奇
RequestManagerTreeNode是幹嘛的呢?
上文提到獲取所有childRequestManagerFragments的RequestManager就是通過該類獲得,就一個方法:getDescendants,作用就是基於給定的Context,獲取所有層級相關的RequestManager。上下文層級由Activity或者Fragment獲得,ApplicationContext的上下文不會提供RequestManager的層級關係,而且Application生命週期過長,所以Glide中對請求的控制只針對於Activity和Fragment。
繼續說,傳入Application型別,其實這是最簡單的一種情況,因為Application物件的生命週期即應用程式的生命週期,因此Glide並不需要做什麼特殊的處理,它自動就是和應用程式的生命週期是同步的,如果應用程式關閉的話,Glide的載入也會同時終止。
傳入非Application引數的情況
程式碼如下:
public RequestManager get(Fragment fragment) { if (fragment.getActivity() == null) { throw new IllegalArgumentException("You cannot start a load on a fragment before it is attached"); } if (Util.isOnBackgroundThread()) { return get(fragment.getActivity().getApplicationContext()); } else { FragmentManager fm = fragment.getChildFragmentManager(); return supportFragmentGet(fragment.getActivity(), fm); } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) public RequestManager get(Activity activity) { if (Util.isOnBackgroundThread() || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { return get(activity.getApplicationContext()); } else { assertNotDestroyed(activity); android.app.FragmentManager fm = activity.getFragmentManager(); return fragmentGet(activity, fm); } }
如果我們是在非主執行緒當中使用的Glide,那麼不管你是傳入的Activity還是Fragment,都會被強制當成Application來處理。
方法中傳入的是Activity、FragmentActivity、v4包下的Fragment、還是app包下的Fragment,最終的流程都是一樣的,那就是會呼叫fragmentGet()方法,向當前的Activity當中新增一個隱藏的RequestManagerFragment。
其實,最終都是呼叫了fragmentGet()這個方法去獲取RequestManager,
@TargetApi(Build.VERSION_CODES.HONEYCOMB) RequestManager fragmentGet(Context context, android.app.FragmentManager fm) { RequestManagerFragment current = getRequestManagerFragment(fm); RequestManager requestManager = current.getRequestManager(); if (requestManager == null) { requestManager = new RequestManager(context, current.getLifecycle(), current.getRequestManagerTreeNode()); //非常重要的一個方法,就是通過這個方法將我們空的fragment關聯到了Requestmanager關聯繫結到了一起 //RequestManager和RequestManagerFragment都是一一對應的 current.setRequestManager(requestManager); } return requestManager; } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) { RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); if (current == null) { current = pendingRequestManagerFragments.get(fm); if (current == null) { current = new RequestManagerFragment(); pendingRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); //這裡 handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); } } return current; }
可以看到RequestManagerRetriever其實就是一個RequestManager的生產類。
那這個RequsetManager是幹什麼的呢?
其實RequestManager是用於管理Glide的圖片載入請求的和完成glide物件的構造。最重要的一點就是用於監聽我們整個元件的生命週期。
那麼這裡為什麼要新增一個隱藏的Fragment呢?
因為Glide需要知道載入的生命週期。很簡單的一個道理,如果你在某個Activity上正在載入著一張圖片,結果圖片還沒加載出來,Activity就被使用者關掉了,那麼圖片還應該繼續載入嗎?當然不應該。可是Glide並沒有辦法知道Activity的生命週期,於是Glide就使用了新增隱藏Fragment的這種小技巧,因為Fragment的生命週期和Activity是同步的,如果Activity被銷燬了,Fragment是可以監聽到的,這樣Glide就可以捕獲這個事件並停止圖片載入了。

生命週期事件的傳遞
Glide精妙設計之一
with()的原始碼設計中比較重要,核心的一點來說就是,將Glide和元件的生命週期相掛鉤。
總體來說,第一個with()方法的原始碼還是比較好理解的。其實就是為了得到一個RequestManager物件而已,然後Glide會根據我們傳入with()方法的引數來確定圖片載入的生命週期,並沒有什麼特別複雜的邏輯,就是一個準備好基礎配置的方法。
load()方法到底做了什麼?
==關鍵詞== :
DrawableTypeRequest,GenericRequestBuilder(是我們在glide當中配置所有引數的父類,也就是說,只要是在glide當中配置引數,就一定是通過這個類或者他的子類來實現的)
ModelLoader(通過資料來源,將資料來源載入成原始資料)
RequestTracker(直譯的話就是請求追蹤器,跟蹤圖片請求的整個週期,可以做取消,重啟一些失敗的圖片請求生命週期的管理主要由RequestTracker和TargetTracker處理。builder.createGlide() 建立Glide物件。
由於with()方法返回的是一個RequestManager物件,那麼很容易就能想到,load()方法是在RequestManager類當中的,所以說我們首先要看的就是RequestManager這個類。
那麼我們先來看load()方法,這個方法中的邏輯是非常簡單的,只有一行程式碼,就是先呼叫了fromString()方法,再呼叫load()方法,然後把傳入的圖片URL地址傳進去。(也可以從原始碼中看出,load有多個過載方法,支援String,file,Integer,byte等各種資料來源)
而fromString()方法也極為簡單,就是呼叫了loadGeneric()方法,並且指定引數為String.class,因為load()方法傳入的是一個字串引數。那麼看上去,好像主要的工作都是在loadGeneric()方法中進行的了。
/** * Returns a request builder to load the given {@link java.lang.String}. * signature. * * @see #fromString() * @see #load(Object) * * @param string A file path, or a uri or url handled by {@link com.bumptech.glide.load.model.UriLoader}. */ public DrawableTypeRequest<String> load(String string) { return (DrawableTypeRequest<String>) fromString().load(string); } public DrawableTypeRequest<String> fromString() { //傳入的是String的class物件 return loadGeneric(String.class); } private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) { ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context);as ModelLoader<T, ParcelFileDescriptor> fileDescriptorModelLoader = Glide.buildFileDescriptorModelLoader(modelClass, context); if (modelClass != null && streamModelLoader == null && fileDescriptorModelLoader == null) { throw new IllegalArgumentException("Unknown type " + modelClass + ". You must provide a Model of a type for" + " which there is a registered ModelLoader, if you are using a custom model, you must first call" + " Glide#register with a ModelLoaderFactory for your custom model class"); } return optionsApplier.apply( new DrawableTypeRequest<T>(modelClass, streamModelLoader, fileDescriptorModelLoader, context, glide, requestTracker, lifecycle, optionsApplier)); }
在loadGeneric()方法第一行可以看到有一句Glide.buildStreamModelLoader(modelClass, context)點進去可以看到他不僅返回了ModelLoader物件,而且還初始化了Glide。點進去看看:
/** * A method to build a {@link ModelLoader} for the given model that produces {@link InputStream}s using a registered * factory. * * @see #buildModelLoader(Class, Class, android.content.Context) */ public static <T> ModelLoader<T, InputStream> buildStreamModelLoader(Class<T> modelClass, Context context) { return buildModelLoader(modelClass, InputStream.class, context); } public static <T, Y> ModelLoader<T, Y> buildModelLoader(Class<T> modelClass, Class<Y> resourceClass, Context context) { if (modelClass == null) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Unable to load null model, setting placeholder only"); } return null; } return Glide.get(context).getLoaderFactory().buildModelLoader(modelClass, resourceClass); } /** * Get the singleton. * * @return the singleton */ public static Glide get(Context context) { if (glide == null) { synchronized (Glide.class) { if (glide == null) { Context applicationContext = context.getApplicationContext(); //解析清單檔案配置的自定義GlideModule的metadata標籤,返回一個GlideModule集合 List<GlideModule> modules = new ManifestParser(applicationContext).parse(); GlideBuilder builder = new GlideBuilder(applicationContext); for (GlideModule module : modules) { module.applyOptions(applicationContext, builder); } //初始化了glide的單例。 glide = builder.createGlide(); for (GlideModule module : modules) { module.registerComponents(applicationContext, glide); } } } } return glide; }
我們看到通過反射的方式獲取我們在清單檔案中宣告的自定義的GlideModule物件。在獲取到GlideModule集合之後,遍歷了集合並呼叫相應的applyOptions和registerComponents方法,而Glide物件的生成是通過GlideBuilder的createGlide方法建立。(底下有例子)
看到這裡不知道大家會不會跟我有一樣的疑問,就是
GlideModule是個啥?幹嘛用的?
可以通過GlideBuilder進行一些延遲的配置和ModelLoaders的註冊。注意:
所有的實現的module必須是public的,並且只擁有一個空的建構函式,以便Glide懶載入的時候可以通過反射呼叫。
GlideModule是不能指定呼叫順序的。因此在建立多個GlideModule的時候,要注意不同Module之間的setting不要衝突了。
接下來看一下glide = builder.createGlide();這句程式碼做了什麼
Glide createGlide() { if (sourceService == null) { //檢視核心執行緒數 final int cores = Math.max(1, Runtime.getRuntime().availableProcessors()); //初始化執行緒池 sourceService = new FifoPriorityThreadPoolExecutor(cores); } if (diskCacheService == null) { diskCacheService = new FifoPriorityThreadPoolExecutor(1); } MemorySizeCalculator calculator = new MemorySizeCalculator(context); //初始化bitmapPool //圖片池用的是targetPoolSize(即一般是快取大小是螢幕的寬高4*4). if (bitmapPool == null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { int size = calculator.getBitmapPoolSize(); bitmapPool = new LruBitmapPool(size); } else { bitmapPool = new BitmapPoolAdapter(); } } ////記憶體快取用的是targetMemoryCacheSize (即一般是快取大小是螢幕的寬 * 高 * 4 * 2) if (memoryCache == null) { memoryCache = new LruResourceCache(calculator.getMemoryCacheSize()); } if (diskCacheFactory == null) { //磁碟快取 預設大小:250 MB,預設目錄:image_manager_disk_cache. //Glide預設是用InternalCacheDiskCacheFactory類來建立硬碟快取的,這個類會在應用的內部快取目錄下面建立一個最大容量250MB的快取資料夾,使用這個快取目錄而不用sd卡,意味著除了本應用之外,其他應用是不能訪問快取的圖片檔案的。 diskCacheFactory = new InternalCacheDiskCacheFactory(context); } if (engine == null) { //引擎初始化 engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService); } if (decodeFormat == null) { decodeFormat = DecodeFormat.DEFAULT; } return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat); }
看到這裡其實大體的glide所做的內容我們已經清楚,其實Glide還支援動態的快取大小調整,在存在大量圖片的Activity/Fragment中,可以通過setMemoryCategory方法來提高Glide的記憶體快取大小,從而加快圖片的載入速度。
Glide.get(getApplicationContext()).setMemoryCategory(MemoryCategory.HIGH);
MemoryCategory有3個值可供選擇:
- MemoryCategory.HIGH(初始快取大小的1.5倍)
- MemoryCategory.NORMAL(初始快取大小的1倍)
- MemoryCategory.LOW(初始快取大小的0.5倍)
Glide磁碟快取策略分為四種,預設的是RESULT:
- ALL:快取原圖和處理圖
- NONE:什麼都不快取
- SOURCE:只快取原圖
- RESULT:只快取處理圖
這麼噁心的ModelLoader到底是幹嘛用的?
ModelLoader物件是用於載入圖片各種資源的,而我們給load()方法傳入不同型別的引數,這裡也會得到不同的ModelLoader物件。該介面有兩個目的:將任意複雜的model轉換為可以被decode的資料型別,允許model結合View的尺寸獲取特定大小的資源
最後我們可以看到,loadGeneric()方法是要返回一個DrawableTypeRequest物件的,因此在loadGeneric()方法的最後又去new了一個DrawableTypeRequest物件,然後把剛才獲得的ModelLoader物件,還有一大堆雜七雜八的東西都傳了進去。
那DrawableTypeRequest是做什麼的呢
DrawableTypeRequest
這個類中的程式碼本身就不多,主要看一下構造方法和我們會用到的兩個比較重要的方法asGif()和asBitmap()。這兩個方法分別是用於強制指定載入靜態圖片和動態圖片。將我們的圖片轉化為BitmapTypeRequest或者GifTypeRequest兩種圖片格式。
asBitmap()與asGif()
不管我們傳入的是一張普通圖片,還是一張GIF圖片,Glide都會自動進行判斷,並且可以正確地把它解析並展示出來。
但是如果我想指定圖片的格式該怎麼辦呢?就比如說,我希望載入的這張圖必須是一張靜態圖片,我不需要Glide自動幫我判斷它到底是靜圖還是GIF圖。
好的我們只需要反向操作下,兩種情況:
1. 傳入gif連結,使用asBitmap()方法,gif圖則無法正常播放,而是會停在第一幀的圖片。 2. 傳入靜態圖片連結,使用asGif()方法,會顯示error()設定的圖片,沒錯,如果指定了只能載入動態圖片,而傳入的圖片卻是一張靜圖的話,那麼結果自然就只有載入失敗。
看一下程式碼:
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; } public BitmapTypeRequest<ModelType> asBitmap() { return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader, fileDescriptorModelLoader, optionsApplier)); } public GifTypeRequest<ModelType> asGif() { return optionsApplier.apply(new GifTypeRequest<ModelType>(this, streamModelLoader, optionsApplier)); }
而從原始碼中可以看出,它們分別又建立了一個BitmapTypeRequest和GifTypeRequest,如果沒有進行強制指定的話,那預設就是使用DrawableTypeRequest。
好的,那麼我們再回到RequestManager的load()方法中。剛才已經分析過了,fromString()方法會返回一個DrawableTypeRequest物件,接下來會呼叫這個物件的load()方法,把圖片的URL地址傳進去。點進去看看load()是在DrawableRequestBuilder類中,我們也可以看到DrawableRequestBuilder是DrawableTypeRequest的父類。看程式碼:
@Override public DrawableRequestBuilder<ModelType> load(ModelType model) { super.load(model); return this; } public GenericRequestBuilder<ModelType, DataType, ResourceType, TranscodeType> load(ModelType model) { this.model = model; isModelSet = true;//注意這個boolean 在into方法時我們會講到 return this; }
其實這個model是什麼,說白了就是我們傳進來的資料物件。就是資料來源,可以支援多種型別,圖片,url,位元組,檔案等。
DrawableTypeRequest的父類是DrawableRequestBuilder,DrawableRequestBuilder中有很多個方法,這些方法其實就是Glide絕大多數的API了。裡面有不少我們在上篇文章中已經用過了,比如說placeholder()方法、error()方法、diskCacheStrategy()方法、override()方法,當然還有最重要的into()方法。其實通過原始碼得知,DrawableRequestBuilder在這些方法中也沒有做什麼處理,主要是通過父類的方法來做相應處理。
最重要的來了,在DrawableRequestBuilder類中有一個into()方法,也就是說,最終load()方法返回的其實就是一個DrawableTypeRequest物件。
@Override public Target<GlideDrawable> into(ImageView view) { return super.into(view); }
Glide精妙設計之二
其實通過Glide支援鏈式呼叫就可以知道,他是使用了建造者模式構建的,類似於我們的Dialog,Retrofit。泛型,介面的使用。