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

一、概述

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

二、定義GlideModule

2.1 步驟

首先,我們需要一個(gè)實(shí)現(xiàn)了GlideModule接口的類,重寫其中的方法來改變Glide的配置,然后讓Glide在構(gòu)造實(shí)例的過程中,讀取這個(gè)類中的配置信息。

  • 第一步:實(shí)現(xiàn)GlideModule接口
public class CustomGlideModule implements GlideModule {

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

    @Override
    public void registerComponents(Context context, Glide glide) {
        //通過glide.register進(jìn)行配置.
    }
}
  • 第二步:在AndroidManifest.xml中的<application>標(biāo)簽下定義<meta-data>,這樣Glide才能知道我們定義了這么一個(gè)類,其中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創(chuàng)建時(shí)可以讀取我們在兩個(gè)回調(diào)中配置的信息,我們來看一下Glide是如何使用這個(gè)自定義的類的,它的整個(gè)流程在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構(gòu)造出glide之前,讀取使用者自定義的配置.
                        module.applyOptions(applicationContext, builder);
                    }
                    glide = builder.createGlide();

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

        return glide;
    }

可以看到,整個(gè)實(shí)例化Glide的過程分為三步:

  • 第一步:去AndroidManifest中查找meta-dataGlideModule的類,然后通過反射實(shí)例化它。
  • 第二步:之后Glide會(huì)新建一個(gè)GlideBuilder對象,它會(huì)先調(diào)用我們自定義的GlideModuleapplyOptions方法,并把自己傳進(jìn)去,這樣,自定義的GlideModule就可以通過GlideBuilder提供的接口來設(shè)置它內(nèi)部的參數(shù),在builder.createGlide()的過程中就會(huì)根據(jù)它內(nèi)部的參數(shù)來構(gòu)建Glide,假如我們沒有設(shè)置相應(yīng)的參數(shù),那么在createGlide時(shí),就會(huì)采取默認(rèn)的實(shí)現(xiàn),下面就是memoryCache的例子。
    //我們在applyOptions中,可以通過GlideBuilder的這個(gè)方法來設(shè)定自己的memoryCache.
    public GlideBuilder setMemoryCache(MemoryCache memoryCache) {
        this.memoryCache = memoryCache;
        return this;
    }

    Glide createGlide() {
        //如果builder中沒有設(shè)定memoryCache,那么采用默認(rèn)的實(shí)現(xiàn).
        if (memoryCache == null) {
            memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
        }

        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    }
  • 第三步:在Glide實(shí)例化完畢之后,調(diào)用自定義GlideModuleregisterComponents,并傳入當(dāng)前的Glide實(shí)例來讓使用者注冊自己的組件,其實(shí)在Glide實(shí)例化的過程中已經(jīng)注冊了默認(rèn)的組件,如果用戶定義了相同的組件,那么就會(huì)替換之前的。
    Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) {
        //Glide默認(rèn)注冊的組件.
        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,當(dāng)我們調(diào)用load(xxxx)方法時(shí),應(yīng)該用什么方式來獲取這個(gè)xxxx所指向的資源。因此,我們可以看到register的第一個(gè)參數(shù)就是我們load(xxxx)的類型,第二個(gè)參數(shù)是對應(yīng)的輸入流,而第三個(gè)參數(shù)就是定義獲取資源的方式。
我們也就分兩個(gè)部分,在第三、四節(jié),我們分兩部分討論這兩個(gè)回調(diào)函數(shù)的用法:applyOptions/registerComponents。

2.3 注意事項(xiàng)

  • 由于Glide是通過反射的方法來實(shí)例化GlideModule對象的,因此自定義的GlideModule只能有一個(gè)無參的構(gòu)造方法。
  • 可以看到,上面是支持配置多個(gè)GlideModule的,但是GlideModule的讀取順序并不能保證,因此,不要在多個(gè)GlideModule對同一個(gè)屬性進(jìn)行不同的配置。

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

在第二節(jié)中,我們已經(jīng)解釋過,這個(gè)回調(diào)方法的目的就是為了讓使用者能通過builder定義自己的配置,而所支持的配置也就是GlideBuildersetXXX方法,它們包括:

  • setBitmapPool(BitmapPool bitmapPool)
    設(shè)置Bitmap的緩存池,用來重用Bitmap,需要實(shí)現(xiàn)BitmapPool接口,它的默認(rèn)實(shí)現(xiàn)是LruBitmapPool

  • setMemoryCache(MemoryCache memoryCache)
    設(shè)置內(nèi)存緩存,需要實(shí)現(xiàn)MemoryCache接口,默認(rèn)實(shí)現(xiàn)是LruResourceCache。

  • setDiskCache(DiskCache.Factory diskCacheFactory)
    設(shè)置磁盤緩存,需要實(shí)現(xiàn)DiskCache.Factory,默認(rèn)實(shí)現(xiàn)是InternalCacheDiskCacheFactory

  • setResizeService(ExecutorService service)
    當(dāng)資源不在緩存中時(shí),需要通過這個(gè)Executor發(fā)起請求,默認(rèn)是實(shí)現(xiàn)是FifoPriorityThreadPoolExecutor。

  • setDiskCacheService(ExecutorService service)
    讀取磁盤緩存的服務(wù),默認(rèn)實(shí)現(xiàn)是FifoPriorityThreadPoolExecutor。

  • setDecodeFormat(DecodeFormat decodeFormat)
    用于控制Bitmap解碼的清晰度,DecodeFormat可選的值有PREFER_ARGB_8888/PREFER_RGB_565,默認(rèn)為PREFER_RGB_565。

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

registerComponents相對來說就復(fù)雜了很多,它主要和三個(gè)接口有關(guān):

  • ModelLoaderFactory
  • ModelLoader
  • DataFetcher

為了便于理解,我們先通過它內(nèi)部一個(gè)默認(rèn)Module的實(shí)現(xiàn)來看一下源碼是如何實(shí)現(xiàn)的。

我們選取是通用的加載普通圖片的url的例子,它對應(yīng)的注冊方法是下面這句:

Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) {
        //注冊加載網(wǎng)絡(luò)url的組件.
        register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
    }

4.1 源碼分析

4.1.1 HttpUrlGlideUrlLoader.Factory

首先看一下HttpUrlGlideUrlLoader的內(nèi)部工廠類,它實(shí)現(xiàn)了ModelLoaderFactory<T, Y>接口

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

它要求我們返回一個(gè)ModelLoader,我們看一下HttpUrlGlideUrlLoader.Factory是怎么做的,可以看到,它返回了HttpUrlGlideUrlLoader,而它的兩個(gè)泛型參數(shù)就是我們register中指定的前兩個(gè)參數(shù)類型。

    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實(shí)現(xiàn)了ModelLoader接口:



public interface ModelLoader<T, Y> {
    DataFetcher<Y> getResourceFetcher(T model, int width, int height);
}

ModelLoader提供了一個(gè)DataFetcher,它會(huì)去請求這個(gè)抽象模型所表示的數(shù)據(jù):

  • T:模型的類型。
  • Y:一個(gè)可以被ResourceDecoder解析出數(shù)據(jù)的表示。

GlideUrl的實(shí)現(xiàn)如下:

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;
    }

     //最主要的方法,它決定了我們獲取數(shù)據(jù)的方式.
    @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就是我們讀取數(shù)據(jù)的方式,它的關(guān)鍵方法是loadData,該loadData的返回值就是我們register的第二個(gè)參數(shù):

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

HttpUrlFetcher實(shí)現(xiàn)了DataFetcher接口,在它的loadData方法中,通過傳入的url發(fā)起請求,最終返回一個(gè)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 {
        //就是調(diào)用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 小結(jié)

對上面做個(gè)總結(jié),整個(gè)流程如下:通過register傳入一個(gè)ModelLoaderFactory<T, Y>工廠類,該工廠生產(chǎn)的是ModelLoader<T, Y>,而這個(gè)ModelLoader會(huì)根據(jù)T返回一個(gè)DataFetcher<Y>,在DataFetcher<Y>中,我們?nèi)カ@取數(shù)據(jù)。(在上面的例子中T就是GlideUrl,Y就是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返回一個(gè)DataFetcher,我們給它傳入一個(gè)OkHttpClient實(shí)例,讓它通過OkHttpClient發(fā)起請求。
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中注冊這個(gè)組件:

public class CustomGlideModule implements GlideModule {

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

    @Override
    public void registerComponents(Context context, Glide glide) {
        //通過glide.register進(jìn)行配置.
        glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory());
    }
}

接著我們發(fā)起一次請求,通過斷點(diǎn)可以發(fā)現(xiàn),調(diào)用的是OkHttpClient來進(jìn)行數(shù)據(jù)的拉?。?br>

4.3 自定義處理的Module

上面我們分析了如何定義ModuleLoader來關(guān)聯(lián)已有的Module和最終的數(shù)據(jù)類型,下面我們介紹一些如何定義自己的Model,也就是前面在基礎(chǔ)介紹中,所說的load(Module)方法。

  • 第一步:定義Module的接口
public interface AutoSizeModel {
    String requestSizeUrl(int width, int height);
}
  • 第二步:實(shí)現(xiàn)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中進(jìn)行關(guān)聯(lián):
public class CustomGlideModule implements GlideModule {

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

    @Override
    public void registerComponents(Context context, Glide glide) {
        //通過glide.register進(jìn)行配置.
        glide.register(AutoSizeModel.class, InputStream.class, new AutoSizeModelLoader.Factory());
    }
}
  • 第五步:調(diào)用
    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 動(dòng)態(tài)指定ModelLoader

在上面的例子中,我們是在自定義的CustomGlideModule中指定了ModelModuleLoader的關(guān)聯(lián),當(dāng)然,我們也可以采用動(dòng)態(tài)指定ModelLoader的方法,也就是說,我們?nèi)サ?code>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方法,我們就可以在運(yùn)行時(shí)根據(jù)情況為同一個(gè)Module選擇不同類型的ModuleLoader了。

五、小結(jié)

這也是我們Glide學(xué)習(xí)的最后一章,所有的源碼都可以從下面的鏈接中找到:

https://github.com/imZeJun/RepoGlideLearn

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容