1. 程式人生 > >圖片載入框架Glide的使用及原始碼分析

圖片載入框架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 class 
GlideConfiguration 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.