圖片載入框架Glide的使用及原始碼分析
1、對比圖片載入框架
Glide和Picasso的對比:
Picasso:compile 'com.squareup.picasso:picasso:2.5.1'
Glide:compile 'com.github.bumptech.glide:glide:3.7.0'
Glide預設載入圖片是RGB_565的,而Picasso預設載入的圖片是ARGB_8888的,會比Glide更佔用記憶體,但是圖片質量要高
我們可以建立一個實現了GlideModule的類,將圖片格式儲存為ARGB_8888的,具體如下:
/** * Created by ping.Zh on 2018/4/11. */ public classGlideConfiguration implements GlideModule { @Override public void applyOptions(Context context, GlideBuilder builder) { builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888); } @Override public void registerComponents(Context context, Glide glide) { } }
同時在manifest中將Module定義為meta-data:
<meta-data android:name="com.xxx.xxx.GlideConfiguration" android:value="GlideModule"/>
Glide的優點是可以計算出任意尺寸的imageview,並將bitmap快取到磁碟中。而Picasso是快取的全尺寸的。Glide會將每種大小的的圖片都快取一次,儘管一張圖片已經快取了一次,但是假如你要在另外一個地方再次以不同尺寸顯示,需要重新下載,調整成新尺寸的大小,然後將這個尺寸的也快取起來。
不過我們可以改變這種策略,讓Glide既快取全尺寸又快取其他尺寸的
Glide.with(this) .load(imageUrl) .diskCacheStrategy(DiskCacheStrategy.ALL) .into(imageView);
那麼下次再要載入別的尺寸的圖片的時候,就會從快取中取出全尺寸的圖片,調整大小載入到imageview上並重新快取,通過這種操作,可以使Glide載入非常快。
Glide還有一個優勢就是可以載入動圖GIF,同時因為Glide和Activity/Fragment的生命週期是一致的,因此gif的動畫也會自動的隨著Activity/Fragment的狀態暫停、重放。Glide 的快取在gif這裡也是一樣,調整大小然後快取。
Glide的缺點是庫比Picasso稍微大點,但是方法數比Picasso多很多,因此在使用Glide的時候必須開啟混淆
2、原始碼分析
Glide.with(this) .load(imageUrl) .diskCacheStrategy(DiskCacheStrategy.ALL) .into(imageView);
重點依據這個線路來分析原始碼:
Glide的with()方法有很多過載的方法
/** * Begin a load with Glide by passing in a context. * * <p> * Any requests started using a context will only have the application level options applied and will not be * started or stopped based on lifecycle events. In general, loads should be started at the level the result * will be used in. If the resource will be used in a view in a child fragment, * the load should be started with {@link #with(android.app.Fragment)}} using that child fragment. Similarly, * if the resource will be used in a view in the parent fragment, the load should be started with * {@link #with(android.app.Fragment)} using the parent fragment. In the same vein, if the resource will be used * in a view in an activity, the load should be started with {@link #with(android.app.Activity)}}. * </p> * * <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); } /** * Begin a load with Glide that will tied to the give {@link android.support.v4.app.FragmentActivity}'s lifecycle * and that uses the given {@link android.support.v4.app.FragmentActivity}'s default options. * * @param activity The activity to use. * @return A RequestManager for the given FragmentActivity that can be used to start a load. */ public static RequestManager with(FragmentActivity activity) { RequestManagerRetriever retriever = RequestManagerRetriever.get(); return retriever.get(activity); } /** * Begin a load with Glide that will be tied to the given {@link android.app.Fragment}'s lifecycle and that uses * the given {@link android.app.Fragment}'s default options. * * @param fragment The fragment to use. * @return A RequestManager for the given Fragment that can be used to start a load. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static RequestManager with(android.app.Fragment fragment) { RequestManagerRetriever retriever = RequestManagerRetriever.get(); return retriever.get(fragment); } /** * Begin a load with Glide that will be tied to the given {@link android.support.v4.app.Fragment}'s lifecycle and * that uses the given {@link android.support.v4.app.Fragment}'s default options. * * @param fragment The fragment to use. * @return A RequestManager for the given Fragment that can be used to start a load. */ public static RequestManager with(Fragment fragment) { RequestManagerRetriever retriever = RequestManagerRetriever.get(); return retriever.get(fragment); }
從上面的過載方法中可以看到,Glide的with中既可以傳入context,也可以傳入activity和fragment。每一個with方法都很簡單,都是先呼叫RequestManagerRetriever的靜態get()方法得到一個RequestManagerRetriever物件,這個靜態get()方法就是一個單例實現,沒什麼需要解釋的。然後再呼叫RequestManagerRetriever的例項get()方法,去獲取RequestManager物件。
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); }
發現get方法會將傳入的context引數根據是否是application來分別分別載入,那麼接下來肯定有很多get的過載方法
public RequestManager get(FragmentActivity activity) { if (Util.isOnBackgroundThread()) { return get(activity.getApplicationContext()); } else { assertNotDestroyed(activity); FragmentManager fm = activity.getSupportFragmentManager(); return supportFragmentGet(activity, fm); } } 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); } }
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; }我們先來看傳入Application引數的情況。如果在Glide.with()方法中傳入的是一個Application物件,那麼這裡就會呼叫帶有Context引數的get()方法過載,然後會在第44行呼叫getApplicationManager()方法來獲取一個RequestManager物件。其實這是最簡單的一種情況,因為Application物件的生命週期即應用程式的生命週期,因此Glide並不需要做什麼特殊的處理,它自動就是和應用程式的生命週期是同步的,如果應用程式關閉的話,Glide的載入也會同時終止。
接下來我們看傳入非Application引數的情況。不管你在Glide.with()方法中傳入的是Activity、FragmentActivity、v4包下的Fragment、還是app包下的Fragment,最終的流程都是一樣的,那就是會向當前的Activity當中新增一個隱藏的Fragment。
那麼這裡為什麼要新增一個隱藏的Fragment呢?因為Glide需要知道載入的生命週期。很簡單的一個道理,如果你在某個Activity上正在載入著一張圖片,結果圖片還沒加載出來,Activity就被使用者關掉了,那麼圖片還應該繼續載入嗎?當然不應該。可是Glide並沒有辦法知道Activity的生命週期,於是Glide就使用了新增隱藏Fragment的這種小技巧,因為Fragment的生命週期和Activity是同步的,如果Activity被銷燬了,Fragment是可以監聽到的,這樣Glide就可以捕獲這個事件並停止圖片載入了。
這裡額外再提一句,從第48行程式碼可以看出,如果我們是在非主執行緒當中使用的Glide,那麼不管你是傳入的Activity還是Fragment,都會被強制當成Application來處理。總體來說,第一個with()方法的原始碼還是比較好理解的。其實就是為了得到一個RequestManager物件而已,然後Glide會根據我們傳入with()方法的引數來確定圖片載入的生命週期,並沒有什麼特別複雜的邏輯。
loadUrl()方法:
/** * 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); }
由於with()方法返回的是一個RequestManager物件,那麼很容易就能想到,load()方法是在RequestManager類當中的,所以說我們首先要看的就是RequestManager這個類。load()方法肯定也是有很多過載的,我們只看load url這個的
/** * Returns a request builder that loads data from {@link String}s using an empty signature. * * <p> * Note - this method caches data using only the given String as the cache key. If the data is a Uri outside of * your control, or you otherwise expect the data represented by the given String to change without the String * identifier changing, Consider using * {@link com.bumptech.glide.GenericRequestBuilder#signature(com.bumptech.glide.load.Key)} to mixin a signature * you create that identifies the data currently at the given String that will invalidate the cache if that data * changes. Alternatively, using {@link com.bumptech.glide.load.engine.DiskCacheStrategy#NONE} and/or * {@link com.bumptech.glide.DrawableRequestBuilder#skipMemoryCache(boolean)} may be appropriate. * </p> * * @see #from(Class) * @see #load(String) */ public DrawableTypeRequest<String> fromString() { return loadGeneric(String.class); }
private <T> DrawableTypeRequest<T> loadGeneric(Class<T> modelClass) { ModelLoader<T, InputStream> streamModelLoader = Glide.buildStreamModelLoader(modelClass, context); 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)); }這個方法中的邏輯是非常簡單的,只有一行程式碼,就是先呼叫了fromString()方法,再呼叫load()方法,然後把傳入的圖片URL地址傳進去。而fromString()方法也極為簡單,就是呼叫了loadGeneric()方法,並且指定引數為String.class,因為load()方法傳入的是一個字串引數。那麼看上去,好像主要的工作都是在loadGeneric()方法中進行的了。
其實loadGeneric()方法也沒幾行程式碼,這裡分別呼叫了Glide.buildStreamModelLoader()方法和Glide.buildFileDescriptorModelLoader()方法來獲得ModelLoader物件。ModelLoader物件是用於載入圖片的,而我們給load()方法傳入不同型別的引數,這裡也會得到不同的ModelLoader物件。不過buildStreamModelLoader()方法內部的邏輯還是蠻複雜的,這裡就不展開介紹了。由於我們剛才傳入的引數是String.class,因此最終得到的是StreamStringLoader物件,它是實現了ModelLoader介面的。
最後我們可以看到,loadGeneric()方法是要返回一個DrawableTypeRequest物件的,因此在loadGeneric()方法的最後又去new了一個DrawableTypeRequest物件,然後把剛才獲得的ModelLoader物件,還有一大堆雜七雜八的東西都傳了進去。具體每個引數的含義和作用就不解釋了,我們只看主線流程。
那麼DrawableTypeRequest是做什麼的呢?接著看
public class DrawableTypeRequest<ModelType> extends DrawableRequestBuilder<ModelType> implements DownloadOptions { private final ModelLoader<ModelType, InputStream> streamModelLoader; private final ModelLoader<ModelType, ParcelFileDescriptor> fileDescriptorModelLoader; private final RequestManager.OptionsApplier optionsApplier; 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) { transcoder = glide.buildTranscoder(resourceClass, transcodedClass); } DataLoadProvider<ImageVideoWrapper, Z> dataLoadProvider = glide.buildDataProvider(ImageVideoWrapper.class, resourceClass); ImageVideoModelLoader<A> modelLoader = new ImageVideoModelLoader<A>(streamModelLoader, fileDescriptorModelLoader); 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; } /** * Attempts to always load the resource as a {@link android.graphics.Bitmap}, even if it could actually be animated. * * @return A new request builder for loading a {@link android.graphics.Bitmap} */ public BitmapTypeRequest<ModelType> asBitmap() { return optionsApplier.apply(new BitmapTypeRequest<ModelType>(this, streamModelLoader, fileDescriptorModelLoader, optionsApplier)); } /** * Attempts to always load the resource as a {@link com.bumptech.glide.load.resource.gif.GifDrawable}. * <p> * If the underlying data is not a GIF, this will fail. As a result, this should only be used if the model * represents an animated GIF and the caller wants to interact with the GIfDrawable directly. Normally using * just an {@link com.bumptech.glide.DrawableTypeRequest} is sufficient because it will determine whether or * not the given data represents an animated GIF and return the appropriate animated or not animated * {@link android.graphics.drawable.Drawable} automatically. * </p> * * @return A new request builder for loading a {@link com.bumptech.glide.load.resource.gif.GifDrawable}. */ public GifTypeRequest<ModelType> asGif() { return optionsApplier.apply(new GifTypeRequest<ModelType>(this, streamModelLoader, optionsApplier)); }這個類中的程式碼本身就不多,我只是稍微做了一點簡化。可以看到,最主要的就是它提供了asBitmap()和asGif()這兩個方法。
好的,那麼我們再回到RequestManager的load()方法中。剛才已經分析過了,fromString()方法會返回一個DrawableTypeRequest物件,接下來會呼叫這個物件的load()方法,把圖片的URL地址傳進去。但是我們剛才看到了,DrawableTypeRequest中並沒有load()方法,那麼很容易就能猜想到,load()方法是在父類當中的。
public class DrawableRequestBuilder<ModelType> extends GenericRequestBuilder<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable> implements BitmapOptions, DrawableOptions { DrawableRequestBuilder(Context context, Class<ModelType> modelClass, LoadProvider<ModelType, ImageVideoWrapper, GifBitmapWrapper, GlideDrawable> loadProvider, Glide glide, RequestTracker requestTracker, Lifecycle lifecycle) { super(context, modelClass, loadProvider, GlideDrawable.class, glide, requestTracker, lifecycle); // Default to animating. crossFade(); } /** * Loads and displays the {@link GlideDrawable} retrieved by the given thumbnail request if it finishes before this * request. Best used for loading thumbnail {@link GlideDrawable}s that are smaller and will be loaded more quickly * than the fullsize {@link GlideDrawable}. There are no guarantees about the order in which the requests will * actually finish. However, if the thumb request completes after the full request, the thumb {@link GlideDrawable} * will never replace the full image. * * @see #thumbnail(float) * * <p> * Note - Any options on the main request will not be passed on to the thumbnail request. For example, if * you want an animation to occur when either the full {@link GlideDrawable} loads or the thumbnail loads, * you need to call {@link #animate(int)} on both the thumb and the full request. For a simpler thumbnail * option where these options are applied to the humbnail as well, see {@link #thumbnail(float)}. * </p> * * <p> * Only the thumbnail call on the main request will be obeyed, recursive calls to this method are ignored. * </p> * * @param thumbnailRequest The request to use to load the thumbnail. * @return This builder object. */ public DrawableRequestBuilder<ModelType> thumbnail( DrawableRequestBuilder<?> thumbnailRequest) { super.thumbnail(thumbnailRequest); return this; } /** * {@inheritDoc} */ @Override public DrawableRequestBuilder<ModelType> thumbnail( GenericRequestBuilder<?, ?, ?, GlideDrawable> thumbnailRequest) { super.thumbnail(thumbnailRequest); return this; } /** * {@inheritDoc} */ @Override public DrawableRequestBuilder<ModelType> thumbnail(float sizeMultiplier) { super.thumbnail(sizeMultiplier); return this; } /** * {@inheritDoc} */ @Override public DrawableRequestBuilder<ModelType> sizeMultiplier(float sizeMultiplier) { super.sizeMultiplier(sizeMultiplier); return this; } /** * {@inheritDoc} */ @Override public DrawableRequestBuilder<ModelType> decoder(ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> decoder) { super.decoder(decoder); return this; } /** * {@inheritDoc} */ @Override public DrawableRequestBuilder<ModelType> cacheDecoder(ResourceDecoder<File, GifBitmapWrapper> cacheDecoder) { super.cacheDecoder(cacheDecoder); return this; } /** * {@inheritDoc} */ @Override public DrawableRequestBuilder<ModelType> encoder(ResourceEncoder<GifBitmapWrapper> encoder) { super.encoder(encoder); return this; } /** * {@inheritDoc} */ @Override public DrawableRequestBuilder<ModelType> priority(Priority priority) { super.priority(priority); return this; } /** * Transform {@link GlideDrawable}s using the given * {@link com.bumptech.glide.load.resource.bitmap.BitmapTransformation}s. * * <p> * Note - Bitmap transformations will apply individually to each frame of animated GIF images and also to * individual {@link Bitmap}s. * </p> * * @see #centerCrop() * @see #fitCenter() * @see #bitmapTransform(com.bumptech.glide.load.Transformation[]) * @see #transform(com.bumptech.glide.load.Transformation[]) * * @param transformations The transformations to apply in order. * @return This request builder. */ public DrawableRequestBuilder<ModelType> transform(BitmapTransformation... transformations) { return bitmapTransform(transformations); } /** * Transform {@link GlideDrawable}s using {@link com.bumptech.glide.load.resource.bitmap.CenterCrop}. * * @see #fitCenter() * @see #transform(com.bumptech.glide.load.resource.bitmap.BitmapTransformation...) * @see #bitmapTransform(com.bumptech.glide.load.Transformation[]) * @see #transform(com.bumptech.glide.load.Transformation[]) * * @return This request builder. */ @SuppressWarnings("unchecked") public DrawableRequestBuilder<ModelType> centerCrop() { return transform(glide.getDrawableCenterCrop()); } /** * Transform {@link GlideDrawable}s using {@link com.bumptech.glide.load.resource.bitmap.FitCenter}. * * @see #centerCrop() * @see #transform(com.bumptech.glide.load.resource.bitmap.BitmapTransformation...) * @see #bitmapTransform(com.bumptech.glide.load.Transformation[]) * @see #transform(com.bumptech.glide.load.Transformation[]) * * @return This request builder.