Glide 知識梳理(5) 自定義GlideModule

澤毛發表於2017-12-21

一、概述

前面說的都是如何使用Glide提供的介面來展示圖片資源,今天這篇,我們來講一下如何改變Glide的配置。

二、定義GlideModule

2.1 步驟

首先,我們需要一個實現了GlideModule介面的類,重寫其中的方法來改變Glide的配置,然後讓Glide在構造例項的過程中,讀取這個類中的配置資訊。

  • 第一步:實現GlideModule介面
public class CustomGlideModule implements GlideModule {

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        //通過builder.setXXX進行配置.
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        //通過glide.register進行配置.
    }
}
複製程式碼
  • 第二步:在AndroidManifest.xml中的<application>標籤下定義<meta-data>,這樣Glide才能知道我們定義了這麼一個類,其中android:name是我們自定義的GlideModule的完整路徑,而android:value就固定寫死GlideModule
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <meta-data
            android:name="com.example.lizejun.repoglidelearn.CustomGlideModule"
            android:value="GlideModule"/>
    </application>
複製程式碼

2.2 原始碼分析

上面2.1所做的工作都是為了在Glide建立時可以讀取我們在兩個回撥中配置的資訊,我們來看一下Glide是如何使用這個自定義的類的,它的整個流程在Glideget方法中:

    public static Glide get(Context context) {
        if (glide == null) {
            synchronized (Glide.class) {
                if (glide == null) {
                    Context applicationContext = context.getApplicationContext();

                    //第一步
                    List<GlideModule> modules = new ManifestParser(applicationContext).parse();
                    
                     //第二步
                    GlideBuilder builder = new GlideBuilder(applicationContext);
                    for (GlideModule module : modules) {
                        //在builder構造出glide之前,讀取使用者自定義的配置.
                        module.applyOptions(applicationContext, builder);
                    }
                    glide = builder.createGlide();

                    //第三步
                    for (GlideModule module : modules) {
                        module.registerComponents(applicationContext, glide);
                    }
                }
            }
        }

        return glide;
    }
複製程式碼

可以看到,整個例項化Glide的過程分為三步:

  • 第一步:去AndroidManifest中查詢meta-dataGlideModule的類,然後通過反射例項化它。
  • 第二步:之後Glide會新建一個GlideBuilder物件,它會先呼叫我們自定義的GlideModuleapplyOptions方法,並把自己傳進去,這樣,自定義的GlideModule就可以通過GlideBuilder提供的介面來設定它內部的引數,在builder.createGlide()的過程中就會根據它內部的引數來構建Glide,假如我們沒有設定相應的引數,那麼在createGlide時,就會採取預設的實現,下面就是memoryCache的例子。
    //我們在applyOptions中,可以通過GlideBuilder的這個方法來設定自己的memoryCache.
    public GlideBuilder setMemoryCache(MemoryCache memoryCache) {
        this.memoryCache = memoryCache;
        return this;
    }

    Glide createGlide() {
        //如果builder中沒有設定memoryCache,那麼採用預設的實現.
        if (memoryCache == null) {
            memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
        }

        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    }
複製程式碼
  • 第三步:在Glide例項化完畢之後,呼叫自定義GlideModuleregisterComponents,並傳入當前的Glide例項來讓使用者註冊自己的元件,其實在Glide例項化的過程中已經註冊了預設的元件,如果使用者定義了相同的元件,那麼就會替換之前的。
    Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) {
        //Glide預設註冊的元件.
        register(File.class, ParcelFileDescriptor.class, new FileDescriptorFileLoader.Factory());
        register(File.class, InputStream.class, new StreamFileLoader.Factory());
        register(int.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
        register(int.class, InputStream.class, new StreamResourceLoader.Factory());
        register(Integer.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
        register(Integer.class, InputStream.class, new StreamResourceLoader.Factory());
        register(String.class, ParcelFileDescriptor.class, new FileDescriptorStringLoader.Factory());
        register(String.class, InputStream.class, new StreamStringLoader.Factory());
        register(Uri.class, ParcelFileDescriptor.class, new FileDescriptorUriLoader.Factory());
        register(Uri.class, InputStream.class, new StreamUriLoader.Factory());
        register(URL.class, InputStream.class, new StreamUrlLoader.Factory());
        register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
        register(byte[].class, InputStream.class, new StreamByteArrayLoader.Factory());
    }
複製程式碼

通俗的來說,註冊元件的目的就是告訴Glide,當我們呼叫load(xxxx)方法時,應該用什麼方式來獲取這個xxxx所指向的資源。因此,我們可以看到register的第一個引數就是我們load(xxxx)的型別,第二個引數是對應的輸入流,而第三個引數就是定義獲取資源的方式。 我們也就分兩個部分,在第三、四節,我們分兩部分討論這兩個回撥函式的用法:applyOptions/registerComponents

2.3 注意事項

  • 由於Glide是通過反射的方法來例項化GlideModule物件的,因此自定義的GlideModule只能有一個無參的構造方法。
  • 可以看到,上面是支援配置多個GlideModule的,但是GlideModule的讀取順序並不能保證,因此,不要在多個GlideModule對同一個屬性進行不同的配置。

三、applyOptions(Context context, GlideBuilder builder)方法詳解

在第二節中,我們已經解釋過,這個回撥方法的目的就是為了讓使用者能通過builder定義自己的配置,而所支援的配置也就是GlideBuildersetXXX方法,它們包括:

  • setBitmapPool(BitmapPool bitmapPool) 設定Bitmap的快取池,用來重用Bitmap,需要實現BitmapPool介面,它的預設實現是LruBitmapPool

  • setMemoryCache(MemoryCache memoryCache) 設定記憶體快取,需要實現MemoryCache介面,預設實現是LruResourceCache

  • setDiskCache(DiskCache.Factory diskCacheFactory) 設定磁碟快取,需要實現DiskCache.Factory,預設實現是InternalCacheDiskCacheFactory

  • setResizeService(ExecutorService service) 當資源不在快取中時,需要通過這個Executor發起請求,預設是實現是FifoPriorityThreadPoolExecutor

  • setDiskCacheService(ExecutorService service) 讀取磁碟快取的服務,預設實現是FifoPriorityThreadPoolExecutor

  • setDecodeFormat(DecodeFormat decodeFormat) 用於控制Bitmap解碼的清晰度,DecodeFormat可選的值有PREFER_ARGB_8888/PREFER_RGB_565,預設為PREFER_RGB_565

四、registerComponents(Context context, Glide glide)方法詳解

registerComponents相對來說就複雜了很多,它主要和三個介面有關:

  • ModelLoaderFactory
  • ModelLoader
  • DataFetcher

為了便於理解,我們先通過它內部一個預設Module的實現來看一下原始碼是如何實現的。

我們選取是通用的載入普通圖片的url的例子,它對應的註冊方法是下面這句:

Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) {
        //註冊載入網路url的元件.
        register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
    }
複製程式碼

4.1 原始碼分析

4.1.1 HttpUrlGlideUrlLoader.Factory

首先看一下HttpUrlGlideUrlLoader的內部工廠類,它實現了ModelLoaderFactory<T, Y>介面

public interface ModelLoaderFactory<T, Y> {
    ModelLoader<T, Y> build(Context context, GenericLoaderFactory factories);
    void teardown();
}
複製程式碼

它要求我們返回一個ModelLoader,我們看一下HttpUrlGlideUrlLoader.Factory是怎麼做的,可以看到,它返回了HttpUrlGlideUrlLoader,而它的兩個泛型引數就是我們register中指定的前兩個引數型別。

    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
        private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<GlideUrl, GlideUrl>(500);

        @Override
        public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new HttpUrlGlideUrlLoader(modelCache);
        }

        @Override
        public void teardown() {}
    }
複製程式碼

4.1.2 HttpUrlGlideUrlLoader

HttpUrlGlideUrlLoader實現了ModelLoader介面:



public interface ModelLoader<T, Y> {
    DataFetcher<Y> getResourceFetcher(T model, int width, int height);
}
複製程式碼

ModelLoader提供了一個DataFetcher,它會去請求這個抽象模型所表示的資料:

  • T:模型的型別。
  • Y:一個可以被ResourceDecoder解析出資料的表示。

GlideUrl的實現如下:

public class HttpUrlGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {

    private final ModelCache<GlideUrl, GlideUrl> modelCache;

    public HttpUrlGlideUrlLoader() {
        this(null);
    }

    public HttpUrlGlideUrlLoader(ModelCache<GlideUrl, GlideUrl> modelCache) {
        this.modelCache = modelCache;
    }

     //最主要的方法,它決定了我們獲取資料的方式.
    @Override
    public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
        GlideUrl url = model;
        if (modelCache != null) {
            url = modelCache.get(model, 0, 0);
            if (url == null) {
                modelCache.put(model, 0, 0, model);
                url = model;
            }
        }
        return new HttpUrlFetcher(url);
    }
}
複製程式碼

4.1.3 HttpUrlFetcher

DataFetcher就是我們讀取資料的方式,它的關鍵方法是loadData,該loadData的返回值就是我們register的第二個引數:

public interface DataFetcher<T> {
    T loadData(Priority priority) throws Exception;
    void cleanup();
    String getId();
    void cancel();
}
複製程式碼

HttpUrlFetcher實現了DataFetcher介面,在它的loadData方法中,通過傳入的url發起請求,最終返回一個InputStream

public class HttpUrlFetcher implements DataFetcher<InputStream> {
    private static final String TAG = "HttpUrlFetcher";
    private static final int MAXIMUM_REDIRECTS = 5;
    private static final HttpUrlConnectionFactory DEFAULT_CONNECTION_FACTORY = new DefaultHttpUrlConnectionFactory();

    private final GlideUrl glideUrl;
    private final HttpUrlConnectionFactory connectionFactory;

    private HttpURLConnection urlConnection;
    private InputStream stream;
    private volatile boolean isCancelled;

    public HttpUrlFetcher(GlideUrl glideUrl) {
        this(glideUrl, DEFAULT_CONNECTION_FACTORY);
    }

    HttpUrlFetcher(GlideUrl glideUrl, HttpUrlConnectionFactory connectionFactory) {
        this.glideUrl = glideUrl;
        this.connectionFactory = connectionFactory;
    }

    @Override
    public InputStream loadData(Priority priority) throws Exception {
        return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
    }

    private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers) throws IOException {
        //就是呼叫HttpUrlConnection請求.
    }

    @Override
    public void cleanup() {
        if (stream != null) {
            try {
                stream.close();
            } catch (IOException e) {
                // Ignore
            }
        }
        if (urlConnection != null) {
            urlConnection.disconnect();
        }
    }

    @Override
    public String getId() {
        return glideUrl.getCacheKey();
    }

    @Override
    public void cancel() {
        isCancelled = true;
    }
}
複製程式碼

4.1.4 小結

對上面做個總結,整個流程如下:通過register傳入一個ModelLoaderFactory<T, Y>工廠類,該工廠生產的是ModelLoader<T, Y>,而這個ModelLoader會根據T返回一個DataFetcher<Y>,在DataFetcher<Y>中,我們去獲取資料。(在上面的例子中T就是GlideUrlY就是InputStream

4.2 自定義ModuleLoader示例:用OkHttpClient替換HttpURLConnection

下面的例子來自於這篇文章:

https://futurestud.io/tutorials/glide-module-example-accepting-self-signed-https-certificates

  • 第一步:定義ModelLoaderModelLoader.Factory
public class OkHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {

        @Override
        public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new OkHttpGlideUrlLoader(getOkHttpClient());
        }

        @Override
        public void teardown() {}
    }

    @Override
    public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
        return new OkHttpGlideUrlFetcher(mOkHttpClient, model);
    }
}
複製程式碼
  • 第二步:ModelLoadergetResourceFetcher返回一個DataFetcher,我們給它傳入一個OkHttpClient例項,讓它通過OkHttpClient發起請求。
public class OkHttpGlideUrlFetcher implements DataFetcher<InputStream> {

    public OkHttpGlideUrlFetcher(OkHttpClient client, GlideUrl url) {
        this.client = client;
        this.url = url;
    }

    @Override
    public InputStream loadData(Priority priority) throws Exception {
        Request.Builder requestBuilder = new Request.Builder().url(url.toStringUrl());
        for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
            String key = headerEntry.getKey();
            requestBuilder.addHeader(key, headerEntry.getValue());
        }
        Request request = requestBuilder.build();
        Response response = client.newCall(request).execute();
        responseBody = response.body();
        if (!response.isSuccessful()) {
            throw new IOException("Request failed with code: " + response.code());
        }
        long contentLength = responseBody.contentLength();
        stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
        return stream;
    }

}
複製程式碼

第三步:在CustomGlideModule中註冊這個元件:

public class CustomGlideModule implements GlideModule {

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        //通過builder.setXXX進行配置.
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        //通過glide.register進行配置.
        glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory());
    }
}
複製程式碼

接著我們發起一次請求,通過斷點可以發現,呼叫的是OkHttpClient來進行資料的拉取:

Glide 知識梳理(5)   自定義GlideModule

4.3 自定義處理的Module

上面我們分析瞭如何定義ModuleLoader來關聯已有的Module和最終的資料型別,下面我們介紹一些如何定義自己的Model,也就是前面在基礎介紹中,所說的load(Module)方法。

  • 第一步:定義Module的介面
public interface AutoSizeModel {
    String requestSizeUrl(int width, int height);
}
複製程式碼
  • 第二步:實現Module介面
public class AutoSizeModelImpl implements AutoSizeModel {

    String mUrl;

    public AutoSizeModelImpl(String url) {
        mUrl = url;
    }

    @Override
    public String requestSizeUrl(int width, int height) {
        return mUrl;
    }
}
複製程式碼
  • 第三步:定義ModuleLoaderModuleLoader.Factory
public class AutoSizeModelLoader extends BaseGlideUrlLoader<AutoSizeModel> {

    public static class Factory implements ModelLoaderFactory<AutoSizeModel, InputStream> {

        @Override
        public ModelLoader<AutoSizeModel, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new AutoSizeModelLoader(context);
        }

        @Override
        public void teardown() {}
    }

    public AutoSizeModelLoader(Context context) {
        super(context);
    }

    @Override
    protected String getUrl(AutoSizeModel model, int width, int height) {
        return model.requestSizeUrl(width, height);
    }
}
複製程式碼
  • 第四步:在CustomGlideModule中進行關聯:
public class CustomGlideModule implements GlideModule {

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        //通過builder.setXXX進行配置.
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        //通過glide.register進行配置.
        glide.register(AutoSizeModel.class, InputStream.class, new AutoSizeModelLoader.Factory());
    }
}
複製程式碼
  • 第五步:呼叫
    public void loadCustomModule(View view) {
        AutoSizeModelImpl autoSizeModel = new AutoSizeModelImpl("http://i.imgur.com/DvpvklR.png");
        Glide.with(this)
                .load(autoSizeModel)
                .diskCacheStrategy(DiskCacheStrategy.NONE)
                .skipMemoryCache(true)
                .into(mImageView);
    }
複製程式碼

4.4 動態指定ModelLoader

在上面的例子中,我們是在自定義的CustomGlideModule中指定了ModelModuleLoader的關聯,當然,我們也可以採用動態指定ModelLoader的方法,也就是說,我們去掉4.3中的第四步,並把第五步改成下面這樣:

    public void loadDynamicModule(View view) {
        AutoSizeModelImpl autoSizeModel = new AutoSizeModelImpl("http://i.imgur.com/DvpvklR.png");
        AutoSizeModelLoader autoSizeModelLoader = new AutoSizeModelLoader(this);
        Glide.with(this)
                .using(autoSizeModelLoader)
                .load(autoSizeModel)
                .diskCacheStrategy(DiskCacheStrategy.NONE)
                .skipMemoryCache(true)
                .into(mImageView);
    }
複製程式碼

使用using方法,我們就可以在執行時根據情況為同一個Module選擇不同型別的ModuleLoader了。

五、小結

這也是我們Glide學習的最後一章,所有的原始碼都可以從下面的連結中找到:

https://github.com/imZeJun/RepoGlideLearn

相關文章