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

1、概述

前面說的都是如何使用Glide提供的接口來展現圖片資源,今天這篇,咱們來說一下如何改變Glide的配置。android

2、定義GlideModule

2.1 步驟

首先,咱們須要一個實現了GlideModule接口的類,重寫其中的方法來改變Glide的配置,而後讓Glide在構造實例的過程當中,讀取這個類中的配置信息。git

  • 第一步:實現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方法中:github

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)的類型,第二個參數是對應的輸入流,而第三個參數就是定義獲取資源的方式。 咱們也就分兩個部分,在第3、四節,咱們分兩部分討論這兩個回調函數的用法:applyOptions/registerComponentsbash

2.3 注意事項

  • 因爲Glide是經過反射的方法來實例化GlideModule對象的,所以自定義的GlideModule只能有一個無參的構造方法。
  • 能夠看到,上面是支持配置多個GlideModule的,可是GlideModule的讀取順序並不能保證,所以,不要在多個GlideModule對同一個屬性進行不一樣的配置。

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

在第二節中,咱們已經解釋過,這個回調方法的目的就是爲了讓使用者能經過builder定義本身的配置,而所支持的配置也就是GlideBuildersetXXX方法,它們包括:網絡

  • setBitmapPool(BitmapPool bitmapPool) 設置Bitmap的緩存池,用來重用Bitmap,須要實現BitmapPool接口,它的默認實現是LruBitmapPoolapp

  • setMemoryCache(MemoryCache memoryCache) 設置內存緩存,須要實現MemoryCache接口,默認實現是LruResourceCacheide

  • 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

4、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來進行數據的拉取:

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了。

5、小結

這也是咱們Glide學習的最後一章,全部的源碼均可以從下面的連接中找到:

https://github.com/imZeJun/RepoGlideLearn

相關文章
相關標籤/搜索