Glide 系列(八) Glide配置和自定义模块

Glide在使用的时候都是Glide.with(this).load(url).into(imageView);但是Glide如何对参数设置呢?有哪些参数值Glide直接支持的设置呢?本文将对此分析介绍。

目录:

  • 1.Glide配置和自定义组件流程
  • 2.Glide配置项解析
  • 3.Glide自定义组件流程分析
  • 4.Glide配置项和自定义组件的加载机制
  • 5.Glide已有的开源组件

1、Glide配置和自定义组件流程

Glide中有这样一个接口,可以在app中自定义实现

public interface GlideModule {
    void applyOptions(Context context, GlideBuilder builder);
    void registerComponents(Context context, Glide glide);
}
Glide要配置和自定义模块只需要两步

1.实现GlideModule接口

public class CustomGlideMoudle implements GlideModule{
    private static String TAG = "CustomGlideMoudle";
    /**
     * 更改Glide的配置
     */
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
    }
    /**
     * 替换Glide的组件
     */
    @Override
    public void registerComponents(Context context, Glide glide) {
    }
}

2.在AndroidManifest.xml中进行配置
在<application>标签下加入

        <meta-data
            android:name="xxx.xxx.xxx.glide.configure.CustomGlideMoudle"
            android:value="GlideModule" />

2.Glide配置项解析

Glide的配置即在applyOptions()中的GlideBuilder一共有下面这些项可以配置,我这里的源码是Glide3.6.0.

Glide配置项图

具体代码是这样:

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
    }

1.builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);
代码的意思是将Glide的解码格式设置为ARGB_8888的,Glide的默认设置是RGB_565。
2.builder.setBitmapPool(new BitmapPool(){});
BitmapPool是bitmap缓存池的实现的接口,自定义配置的话,实现这个接口里边的方法就可以了。
BitmapPool的默认实现是在sdk api11之前是空实现,在api11之后是LruBitmapPool这个类实现的,有对自定义实现bitmap缓存池感兴趣的可以用LruBitmapPool作为参考研究。
3. builder.setDiskCache(new DiskCache.Factory(){});
设置磁盘缓存,默认的磁盘路径和大小是image_manager_disk_cache 和 250M,可以重新实现这个接口,修改目录和大小。
4.builder.setDiskCacheService(ExecutorService);
设置磁盘缓存线程执行器,这个方法可以使用app中共用的线程执行器,每个开源组件都有自己的线程池和执行器,避免app的线程过多问题
5.builder.setMemoryCache(new MemoryCache(){});
Glide内存缓存资源的缓存实现,可以按照这个接口自己实现,默认实现是使用的LruCache。
6.builder.setResizeService(ExecutorService);
设置图片从原始图片的尺寸转换到要放到ImageView中的尺寸大小的线程执行器,也可以使用系统共用的线程执行器。

Glide支持的全局配置都在这里了。以上这些配置如果自定义配置了,在Glide实例创建的时候就会优先使用自定义配置,如果没有配置会使用默认的配置项。

3.Glide自定义组件流程分析

    @Override
    public void registerComponents(Context context, Glide glide) {
    }

Glide可以注册的组件

Glide自定义模块配置图

先看下这个register()方法

    public <T, Y> void register(Class<T> modelClass, Class<Y> resourceClass, ModelLoaderFactory<T, Y> factory) {
        ModelLoaderFactory<T, Y> removed = loaderFactory.register(modelClass, resourceClass, factory);
        if (removed != null) {
            removed.teardown();
        }
    }
GenericLoaderFactory类下面有两个map
  • Map<Class, Map<Class, ModelLoaderFactory>> modelClassToResourceFactories 这个map称为map1
  • map1的key是modelClass(register的第一个参数),map1的value是另外一个map(称为map2)**
  • map2的key是resourceClass(register的第二个参数),value是factory(register的第三个参数)**

这个register的意思是:

  • 第一个参数modleClass是请求参数类型
  • 第二个参数resourceClass是将请求参数转换的类型
  • 第三个参数factory就是将第一个参数转换成第二个参数的方法的类型。

用第二个map的原因是第一个map中的key的类型有可能是相同的,但是返回值是不同的,所以需要不同的转换器(转换器是指的factory,register的第三个参数)

将这些类型转换的方法放到GenericLoaderFactory.modelClassToResourceFactories 这个map中存储。引擎在需要类型转换的时候回调用这里的factor的方法找对应类型的MolderLoader去做转换。

ModelLoader使用流程

GenericLoaderFactory.buildModelLoader(modelClass, resourceClass)
这个方法返回值是ModelLoader。

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

调用ModelLoader的getResourceFetcher()方法会返回DataFetcher的对象
DataFetcher中的loadData()方法就是执行转换数据的过程。这种以factory存储类型转换处理的方式可以在自己的app开发中学习使用。
Glide中一共有13个register()方法,只有下面这两个是直接实现的,其他的都是在这两个基础上调用这两个实现的。

register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
register(byte[].class, InputStream.class, new StreamByteArrayLoader.Factory());

例如:

register(File.class, ParcelFileDescriptor.class, new FileDescriptorFileLoader.Factory());

就是调用的factories.buildModelLoader(Uri.class, ParcelFileDescriptor.class)。最终的就是上面那两个实现的。

public class FileDescriptorFileLoader extends FileLoader<ParcelFileDescriptor>
        implements FileDescriptorModelLoader<File> {
    public static class Factory implements ModelLoaderFactory<File, ParcelFileDescriptor> {
        @Override
        public ModelLoader<File, ParcelFileDescriptor> build(Context context, GenericLoaderFactory factories) {
            return new FileDescriptorFileLoader(factories.buildModelLoader(Uri.class, ParcelFileDescriptor.class));
        }
        @Override
        public void teardown() {
            // Do nothing.
        }
    }
    public FileDescriptorFileLoader(Context context) {
        this(Glide.buildFileDescriptorModelLoader(Uri.class, context));
    }
    public FileDescriptorFileLoader(ModelLoader<Uri, ParcelFileDescriptor> uriLoader) {
        super(uriLoader);
    }
}

介绍一下直接实现的register(byte[].class, InputStream.class, new StreamByteArrayLoader.Factory());

public class StreamByteArrayLoader implements StreamModelLoader<byte[]> {
     ... ...
    @Override
    public DataFetcher<InputStream> getResourceFetcher(byte[] model, int width, int height) {
        return new ByteArrayFetcher(model, id);
    }
    public static class Factory implements ModelLoaderFactory<byte[], InputStream> {
        @Override
        public ModelLoader<byte[], InputStream> build(Context context, GenericLoaderFactory factories) {
            return new StreamByteArrayLoader();
        }
     ... ...
    }

这个类最终调用的new ByteArrayFetcher(model, id);

public class ByteArrayFetcher implements DataFetcher<InputStream> {
     ... ...
    @Override
    public InputStream loadData(Priority priority) {
        return new ByteArrayInputStream(bytes);
    }
     ... ...
}

其实就是将字节数组转换成了输入流的方法。
还有另外一个直接实现的方法:

register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());

下面是转换类的源码HttpUrlGlideUrlLoader.java

public class HttpUrlGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
    ... ...
    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
    ... ...
        @Override
        public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
            return new HttpUrlGlideUrlLoader(modelCache);
        }
    }
    ... ...
    @Override
    public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
    ... ...
        return new HttpUrlFetcher(url);
    }
}

下面是HttpUrlFetcher的实现源码,内容就是用HttpURLConnection作网络请求的一个过程。

public class HttpUrlFetcher implements DataFetcher<InputStream> {
    private static final String TAG = "HttpUrlFetcher"; 
    private static final String ENCODING_HEADER = "Accept-Encoding";       private static final String DEFAULT_ENCODING = "identity";
    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);     }

    // Visible for testing.     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 {
        if (redirects >= MAXIMUM_REDIRECTS) {
            throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");         } else {
            // Comparing the URLs using .equals performs additional network I/O and is generally broken. // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.             try {
                if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
                    throw new IOException("In re-direct loop");                 }
            } catch (URISyntaxException e) {
                // Do nothing, this is best effort.             }
        }
        urlConnection = connectionFactory.build(url);         for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
          urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());         }
        // Do our best to avoid gzip since it's both inefficient for images and also makes it more // difficult for us to detect and prevent partial content rendering. See #440.         if (TextUtils.isEmpty(urlConnection.getRequestProperty(ENCODING_HEADER))) {
            urlConnection.setRequestProperty(ENCODING_HEADER, DEFAULT_ENCODING);         }
        urlConnection.setConnectTimeout(2500);         urlConnection.setReadTimeout(2500);         urlConnection.setUseCaches(false);         urlConnection.setDoInput(true);           // Connect explicitly to avoid errors in decoders if connection fails.         urlConnection.connect();         if (isCancelled) {
            return null;         }
        final int statusCode = urlConnection.getResponseCode();         if (statusCode / 100 == 2) {
            return getStreamForSuccessfulRequest(urlConnection);         } else if (statusCode / 100 == 3) {
            String redirectUrlString = urlConnection.getHeaderField("Location");             if (TextUtils.isEmpty(redirectUrlString)) {
                throw new IOException("Received empty or null redirect url");             }
            URL redirectUrl = new URL(url, redirectUrlString);             return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);         } else {
            if (statusCode == -1) {
                throw new IOException("Unable to retrieve response code from HttpUrlConnection.");             }
            throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());         }
    }

    private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
            throws IOException {
        if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
            int contentLength = urlConnection.getContentLength();             stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);         } else {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());             }
            stream = urlConnection.getInputStream();         }
        return stream;     }

    @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() {
        // TODO: we should consider disconnecting the url connection here, but we can't do so directly because cancel is // often called on the main thread.         isCancelled = true;     }

    interface HttpUrlConnectionFactory {
        HttpURLConnection build(URL url) throws IOException;     }

    private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {
        @Override         public HttpURLConnection build(URL url) throws IOException {
            return (HttpURLConnection) url.openConnection();         }
    }
}

4.Glide配置项和自定义组件的加载机制

要想知道Glide配置项和自定义组件是如何加载的,还是要从Glide.with(this).load(url).into(imageView);流程说起。
最开始是Glide.with()

public static RequestManager with(FragmentActivity activity) {
    RequestManagerRetriever retriever = RequestManagerRetriever.get();     return retriever.get(activity); }

获得了RequestManager
在RequestManager的构造方法中有Glide.get(context);

    RequestManager(Context context, final Lifecycle lifecycle, RequestManagerTreeNode treeNode,
            RequestTracker requestTracker, ConnectivityMonitorFactory factory) {
    ... ...
        this.glide = Glide.get(context);
    ... ...
    }

然后进入get方法中

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) {
                    module.applyOptions(applicationContext, builder);                 }
                glide = builder.createGlide();                 for (GlideModule module : modules) {
                    module.registerComponents(applicationContext, glide);                 }
            }
        }
    }
    return glide; 
}

可以看到

1.从AndroidManifest.xml中读取配置信息
2.执行GlideModule.applyOptions()方法的内容
3.glide = builder.createGlide();//下边补上源码
4.执行GlideModule.registerComponents()方法。

3步骤的代码,就是GlideModule.applyOptions()不配置就使用默认的,配置了就使用配置的。

Glide createGlide() {
        if (sourceService == null) {
            final int cores = Math.max(1, Runtime.getRuntime().availableProcessors());
            sourceService = new FifoPriorityThreadPoolExecutor(cores);
        }
        if (diskCacheService == null) {
            diskCacheService = new FifoPriorityThreadPoolExecutor(1);
        }
        MemorySizeCalculator calculator = new MemorySizeCalculator(context);
        if (bitmapPool == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                int size = calculator.getBitmapPoolSize();
                bitmapPool = new LruBitmapPool(size);
            } else {
                bitmapPool = new BitmapPoolAdapter();
            }
        }
        if (memoryCache == null) {
            memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
        }
        if (diskCacheFactory == null) {
            diskCacheFactory = new InternalCacheDiskCacheFactory(context);
        }
        if (engine == null) {
            engine = new Engine(memoryCache, diskCacheFactory, diskCacheService, sourceService);
        }
        if (decodeFormat == null) {
            decodeFormat = DecodeFormat.DEFAULT;
        }
        return new Glide(engine, memoryCache, bitmapPool, context, decodeFormat);
    }

这里边的return new Glide()调用了Glide的构造方法

 Glide(Engine engine, MemoryCache memoryCache, BitmapPool bitmapPool, Context context, DecodeFormat decodeFormat) {
    ... ...
        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构造方法中先注册了一下,之后再执行GlideModule.registerComponents()。根据map的机制,会把之前注册过的替换掉。具体源码在GenericLoaderFactory.register()这里就不贴出来了。

5.Glide已有的开源组件

Glide的http组件已有很多开源的,使用的时候只要配置一下就可以了
使用OkHttp3来作为HTTP通讯组件的配置如下:

dependencies {
    compile 'com.squareup.okhttp3:okhttp:3.9.0'
    compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0@aar'
}

使用OkHttp2来作为HTTP通讯组件的配置如下:

dependencies {
    compile 'com.github.bumptech.glide:okhttp-integration:1.5.0@aar'
    compile 'com.squareup.okhttp:okhttp:2.7.5'
}

使用Volley来作为HTTP通讯组件的配置如下:

dependencies {
    compile 'com.github.bumptech.glide:volley-integration:1.5.0@aar' 
    compile 'com.mcxiaoke.volley:library:1.0.19' 
}

Glide配置和自定义模块就介绍到此。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • Glide笔记 一、简介 在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫Glide的图片加载库,作者是bu...
    AndroidMaster阅读 3,868评论 0 27
  • 一、简介 在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫Glide的图片加载库,作者是bumptech。这...
    天天大保建阅读 7,450评论 2 28
  • 7.1 压缩图片 一、基础知识 1、图片的格式 jpg:最常见的图片格式。色彩还原度比较好,可以支持适当压缩后保持...
    AndroidMaster阅读 2,482评论 0 13
  • 今天黄大大回老家带了鸭舌帽和墨镜 黄大大可以给我签名吗 你买我CD了吗 买了世上独一无二的原版现场版的
    叶公子哥哥阅读 147评论 0 0
  • 刚才加了一位好友,她问为什么加她,我答只为多向她学习,共同进步。于是我们成为微信好友。感觉她是一位饱读诗书的一...
    eecbe00b20fc阅读 153评论 0 0