Glide 知识梳理(5) - 自定义GlideModule

一、概述

前面说的都是如何使用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来进行数据的拉取:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容