Glide源码学习七:自定义模块功能

但是Glide科学的默认配置并不影响我们去学习自定义Glide模块的功能,因此总有某些情况下,默认的配置可能将无法满足你,这个时候就需要我们自己动手来修改默认配置了。

下面就通过具体的实例来看一下吧。刚才说到,Glide默认的硬盘缓存策略使用的是InternalCacheDiskCacheFactory,这种缓存会将所有Glide加载的图片都存储到当前应用的私有目录下。这是一种非常安全的做法,但同时这种做法也造成了一些不便,因为私有目录下即使是开发者自己也是无法查看的,如果我想要去验证一下图片到底有没有成功缓存下来,这就有点不太好办了。

这种情况下,就非常适合使用自定义模块来更改Glide的默认配置。我们完全可以自己去实现DiskCache.Factory接口来自定义一个硬盘缓存策略,不过却大大没有必要这么做,因为Glide本身就内置了一个ExternalCacheDiskCacheFactory,可以允许将加载的图片都缓存到SD卡。

那么接下来,我们就尝试使用这个ExternalCacheDiskCacheFactory来替换默认的InternalCacheDiskCacheFactory,从而将所有Glide加载的图片都缓存到SD卡上。

由于在前面我们已经创建好了一个自定义模块MyGlideModule,那么现在就可以直接在这里编写逻辑了,代码如下所示:

public class MyGlideModule implements GlideModule {

@Override

public void applyOptions(Context context, GlideBuilder builder) {

builder.setDiskCache(new ExternalCacheDiskCacheFactory(context));

}

@Override

public void registerComponents(Context context, Glide glide) {

}

}

没错,就是这么简单,现在所有Glide加载的图片都会缓存到SD卡上了。

另外,InternalCacheDiskCacheFactory和ExternalCacheDiskCacheFactory的默认硬盘缓存大小都是250M。也就是说,如果你的应用缓存的图片总大小超出了250M,那么Glide就会按照DiskLruCache算法的原则来清理缓存的图片。

当然,我们是可以对这个默认的缓存大小进行修改的,而且修改方式非常简单,如下所示:

public class MyGlideModule implements GlideModule {

public static final int DISK_CACHE_SIZE = 500 * 1024 * 1024;

@Override

public void applyOptions(Context context, GlideBuilder builder) {

builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, DISK_CACHE_SIZE));

}

@Override

public void registerComponents(Context context, Glide glide) {

}

}

只需要向ExternalCacheDiskCacheFactory或者InternalCacheDiskCacheFactory再传入一个参数就可以了,现在我们就将Glide硬盘缓存的大小调整成了500M。

好了,更改Glide配置的功能就是这么简单,那么接下来我们就来验证一下更改的配置到底有没有生效吧。

这里还是使用最基本的Glide加载语句来去加载一张网络图片:

String url = “http://guolin.tech/book.png”;

Glide.with(this)

.load(url)

.into(imageView);

运行一下程序,效果如下图所示:

OK,现在图片已经加载出现了,那么我们去找一找它的缓存吧。

ExternalCacheDiskCacheFactory的默认缓存路径是在sdcard/Android/包名/cache/image_manager_disk_cache目录当中,我们使用文件浏览器进入到这个目录,结果如下图所示。

可以看到,这里有两个文件,其中journal文件是DiskLruCache算法的日志文件,这个文件必不可少,且只会有一个。想了解更多关于DiskLruCache算法的朋友,可以去阅读我的这篇博客 Android DiskLruCache完全解析,硬盘缓存的最佳方案 。

而另外一个文件就是那张缓存的图片了,它的文件名虽然看上去很奇怪,但是我们只需要把这个文件的后缀改成.png,然后用图片浏览器打开,结果就一目了然了,如下图所示。

由此证明,我们已经成功将Glide的硬盘缓存路径修改到SD卡上了。

另外这里再提一点,我们都知道Glide和Picasso的用法是非常相似的,但是有一点差别却很大。Glide加载图片的默认格式是RGB_565,而Picasso加载图片的默认格式是ARGB_8888。ARGB_8888格式的图片效果会更加细腻,但是内存开销会比较大。而RGB_565格式的图片则更加节省内存,但是图片效果上会差一些。

Glide和Picasso各自采取的默认图片格式谈不上熟优熟劣,只能说各自的取舍不一样。但是如果你希望Glide也能使用ARGB_8888的图片格式,这当然也是可以的。我们只需要在MyGlideModule中更改一下默认配置即可,如下所示:

public class MyGlideModule implements GlideModule {

public static final int DISK_CACHE_SIZE = 500 * 1024 * 1024;

@Override

public void applyOptions(Context context, GlideBuilder builder) {

builder.setDiskCache(new ExternalCacheDiskCacheFactory(context, DISK_CACHE_SIZE));

builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888);

}

@Override

public void registerComponents(Context context, Glide glide) {

}

}

通过这样配置之后,使用Glide加载的所有图片都将会使用ARGB_8888的格式,虽然图片质量变好了,但同时内存开销也会明显增大,所以你要做好心理准备哦。

好了,关于更改Glide配置的内容就介绍这么多,接下来就让我们进入到下一个非常重要的主题,替换Glide组件。

替换Glide组件

=========

替换Glide组件功能需要在自定义模块的registerComponents()方法中加入具体的替换逻辑。相比于更改Glide配置,替换Glide组件这个功能的难度就明显大了不少。Glide中的组件非常繁多,也非常复杂,但其实大多数情况下并不需要我们去做什么替换。不过,有一个组件却有着比较大的替换需求,那就是Glide的HTTP通讯组件。

默认情况下,Glide使用的是基于原生HttpURLConnection进行订制的HTTP通讯组件,但是现在大多数的Android开发者都更喜欢使用OkHttp,因此将Glide中的HTTP通讯组件修改成OkHttp的这个需求比较常见,那么今天我们也会以这个功能来作为例子进行讲解。

首先来看一下Glide中目前有哪些组件吧,在Glide类的构造方法当中,如下所示:

public class 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());

}

}

可以看到,这里都是以调用register()方法的方式来注册一个组件,register()方法中传入的参数表示Glide支持使用哪种参数类型来加载图片,以及如何去处理这种类型的图片加载。举个例子:

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

这句代码就表示,我们可以使用Glide.with(context).load(new GlideUrl(“url…”)).into(imageView)的方式来加载图片,而HttpUrlGlideUrlLoader.Factory则是要负责处理具体的网络通讯逻辑。如果我们想要将Glide的HTTP通讯组件替换成OkHttp的话,那么只需要在自定义模块当中重新注册一个GlideUrl类型的组件就行了。

说到这里有的朋友可能会疑问了,我们平时使用Glide加载图片时,大多数情况下都是直接将图片的URL字符串传入到load()方法当中的,很少会将它封装成GlideUrl对象之后再传入到load()方法当中,那为什么只需要重新注册一个GlideUrl类型的组件,而不需要去重新注册一个String类型的组件呢?其实道理很简单,因为load(String)方法只是Glide给我们提供一种简易的API封装而已,它的底层仍然还是调用的GlideUrl组件,因此我们在替换组件的时候只需要直接替换最底层的,这样就一步到位了。

那么接下来我们就开始学习到底如何将Glide的HTTP通讯组件替换成OkHttp。

首先第一步,不用多说,肯定是要先将OkHttp的库引入到当前项目中,如下所示:

dependencies {

compile ‘com.squareup.okhttp3:okhttp:3.9.0’

}

然后接下来该怎么做呢?我们只要依葫芦画瓢就可以了。刚才不是说Glide的网络通讯逻辑是由HttpUrlGlideUrlLoader.Factory来负责的吗,那么我们就来看一下它的源码:

public class HttpUrlGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {

private final ModelCache<GlideUrl, GlideUrl> modelCache;

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() {

}

}

public HttpUrlGlideUrlLoader() {

this(null);

}

public HttpUrlGlideUrlLoader(ModelCache<GlideUrl, GlideUrl> modelCache) {

this.modelCache = modelCache;

}

@Override

public DataFetcher 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);

}

}

可以看到,HttpUrlGlideUrlLoader.Factory是一个内部类,外层的HttpUrlGlideUrlLoader类实现了ModelLoader<GlideUrl, InputStream>这个接口,并重写了getResourceFetcher()方法。而在getResourceFetcher()方法中,又创建了一个HttpUrlFetcher的实例,在这里才是真正处理具体网络通讯逻辑的地方,代码如下所示:

public class HttpUrlFetcher implements DataFetcher {

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 , null , 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 {

try {

if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {

throw new IOException(“In re-direct loop”);

}

} catch (URISyntaxException e) {

}

}

urlConnection = connectionFactory.build(url);

for (Map.Entry<String, String> headerEntry : headers.entrySet()) {

urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());

}

urlConnection.setConnectTimeout(2500);

urlConnection.setReadTimeout(2500);

urlConnection.setUseCaches(false);

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 {

stream = urlConnection.getInputStream();

}

return stream;

}

@Override

public void cleanup() {

if (stream != null) {

try {

stream.close();

} catch (IOException e) {

}

}

if (urlConnection != null) {

urlConnection.disconnect();

}

}

@Override

public String getId() {

return glideUrl.getCacheKey();

}

@Override

public void cancel() {

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();

}

}

}

上面这段代码看上去应该不费力吧?其实就是一些HttpURLConnection的用法而已。那么我们只需要仿照着HttpUrlFetcher的代码来写,并且把HTTP的通讯组件替换成OkHttp就可以了。

现在新建一个OkHttpFetcher类,并且同样实现DataFetcher接口,代码如下所示:

public class OkHttpFetcher implements DataFetcher {

private final OkHttpClient client;

private final GlideUrl url;

private InputStream stream;

private ResponseBody responseBody;

private volatile boolean isCancelled;

public OkHttpFetcher(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());

}

requestBuilder.addHeader(“httplib”, “OkHttp”);

Request request = requestBuilder.build();

if (isCancelled) {

return null;

}

Response response = client.newCall(request).execute();

responseBody = response.body();

if (!response.isSuccessful() || responseBody == null) {

throw new IOException("Request failed with code: " + response.code());

}

stream = ContentLengthInputStream.obtain(responseBody.byteStream(),

responseBody.contentLength());

return stream;

}

@Override

public void cleanup() {

try {

if (stream != null) {

stream.close();

}

if (responseBody != null) {

responseBody.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

@Override

public String getId() {

return url.getCacheKey();

}

@Override

public void cancel() {

isCancelled = true;

}

}

上面这段代码完全就是我照着HttpUrlFetcher依葫芦画瓢写出来的,用的也都是一些OkHttp的基本用法,相信不需要再做什么解释了吧。可以看到,使用OkHttp来编写网络通讯的代码要比使用HttpURLConnection简单很多,代码行数也少了很多。注意在第22行,我添加了一个httplib: OkHttp的请求头,这个是待会儿我们用来进行测试验证的,大家实际项目中的代码无须添加这个请求头。

那么我们就继续发挥依葫芦画瓢的精神,仿照着HttpUrlGlideUrlLoader再写一个OkHttpGlideUrlLoader吧。新建一个OkHttpGlideUrlLoader类,并且实现ModelLoader<GlideUrl, InputStream>接口,代码如下所示:

public class OkHttpGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {

private OkHttpClient okHttpClient;

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

private OkHttpClient client;

public Factory() {

}

public Factory(OkHttpClient client) {

this.client = client;

}

private synchronized OkHttpClient getOkHttpClient() {

if (client == null) {

client = new OkHttpClient();

}

return client;

}

@Override

public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {

return new OkHttpGlideUrlLoader(getOkHttpClient());

}

@Override

public void teardown() {

}

}

public OkHttpGlideUrlLoader(OkHttpClient client) {

this.okHttpClient = client;

}

@Override

public DataFetcher getResourceFetcher(GlideUrl model, int width, int height) {

return new OkHttpFetcher(okHttpClient, model);

}

}

注意这里的Factory我提供了两个构造方法,一个是不带任何参数的,一个是带OkHttpClient参数的。如果对OkHttp不需要进行任何自定义的配置,那么就调用无参的Factory构造函数即可,这样会在内部自动创建一个OkHttpClient实例。但如果你需要想添加拦截器,或者修改OkHttp的默认超时等等配置,那么就自己创建一个OkHttpClient的实例,然后传入到Factory的构造方法当中就行了。

好了,现在就只差最后一步,将我们刚刚创建的OkHttpGlideUrlLoader和OkHttpFetcher注册到Glide当中,将原来的HTTP通讯组件给替换掉,如下所示:

public class MyGlideModule implements GlideModule {

@Override

public void registerComponents(Context context, Glide glide) {

glide.register(GlideUrl.class, InputStream.class, new OkHttpGlideUrlLoader.Factory());

}

}

可以看到,这里也是调用了Glide的register()方法来注册组件的。register()方法中使用的Map类型来存储已注册的组件,因此我们这里重新注册了一遍GlideUrl.class类型的组件,就把原来的组件给替换掉了。

理论上来说,现在我们已经成功将Glide的HTTP通讯组件替换成OkHttp了,现在唯一的问题就是我们该如何去验证一下到底有没有替换成功呢?

验证的方式我倒是想了很多种,比如添加OkHttp拦截器,或者自己架设一个测试用的服务器都是可以的。不过为了让大家最直接地看到验证结果,这里我准备使用Fiddler这个抓包工具来进行验证。这个工具的用法非常简单,但是限于篇幅我就不在本篇文章中介绍这个工具的用法了,还没用过这个工具的朋友们可以通过 这篇文章 了解一下。

在开始验证之前,我们还得要再修改一下Glide加载图片的代码才行,如下所示:

String url = “http://guolin.tech/book.png”;

Glide.with(this)

.load(url)

.skipMemoryCache(true)

.diskCacheStrategy(DiskCacheStrategy.NONE)

.into(imageView);

这里我把Glide的内存缓存和硬盘缓存都禁用掉了,不然的话,Glide可能会直接读取刚才缓存的图片,而不会再重新发起网终请求。

好的,现在我们重新使用Glide加载一下图片,然后观察Fiddler中的抓包情况,如下图所示。

可以看到,在HTTP请求头中确实有我们刚才自己添加的httplib: OkHttp。也就说明,Glide的HTTP通讯组件的确被替换成功了。

更简单的组件替换

========

上述方法是我们纯手工地将Glide的HTTP通讯组件进行了替换,如果你不想这么麻烦也是可以的,Glide官方给我们提供了非常简便的HTTP组件替换方式。并且除了支持OkHttp3之外,还支持OkHttp2和Volley。

我们只需要在gradle当中添加几行库的配置就行了。比如使用OkHttp3来作为HTTP通讯组件的配置如下:

分享读者

作者2013年java转到Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括腾讯,以及字节跳动,阿里,华为,小米,等一线互联网公司主流架构技术。

腾讯T3架构师学习专题资料

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!

我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
网终请求。

好的,现在我们重新使用Glide加载一下图片,然后观察Fiddler中的抓包情况,如下图所示。

可以看到,在HTTP请求头中确实有我们刚才自己添加的httplib: OkHttp。也就说明,Glide的HTTP通讯组件的确被替换成功了。

更简单的组件替换

========

上述方法是我们纯手工地将Glide的HTTP通讯组件进行了替换,如果你不想这么麻烦也是可以的,Glide官方给我们提供了非常简便的HTTP组件替换方式。并且除了支持OkHttp3之外,还支持OkHttp2和Volley。

我们只需要在gradle当中添加几行库的配置就行了。比如使用OkHttp3来作为HTTP通讯组件的配置如下:

分享读者

作者2013年java转到Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括腾讯,以及字节跳动,阿里,华为,小米,等一线互联网公司主流架构技术。

[外链图片转存中…(img-DlVHVnZR-1714901706242)]

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!

我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值