Glide 源码学习七:自定义模块功能,阿里、百度等大厂技术面试题汇总
...
}
我们来仔细看一下上面这段代码。首先这里使用了一个单例模式来获取 Glide 对象的实例,可以看到,这是一个非常典型的双重锁模式。然后在第 12 行,调用 ManifestParser 的 parse()方法去解析 AndroidManifest.xml 文件中的配置,实际上就是将 AndroidManifest 中所有值为 GlideModule 的 meta-data 配置读取出来,并将相应的自定义模块实例化。由于你可以自定义任意多个模块,因此这里我们将会得到一个 GlideModule 的 List 集合。
接下来在第 13 行创建了一个 GlideBuilder 对象,并通过一个循环调用了每一个 GlideModule 的 applyOptions()方法,同时也把 GlideBuilder 对象作为参数传入到这个方法中。而 applyOptions()方法就是我们可以加入自己的逻辑的地方了,虽然目前为止我们还没有编写任何逻辑。
再往下的一步就非常关键了,这里调用了 GlideBuilder 的 createGlide()方法,并返回了一个 Glide 对象。也就是说,Glide 对象的实例就是在这里创建的了,那么我们跟到这个方法当中瞧一瞧:
public class GlideBuilder {
private final Context context;
private Engine engine;
private BitmapPool bitmapPool;
private MemoryCache memoryCache;
private ExecutorService sourceService;
private ExecutorService diskCacheService;
private DecodeFormat decodeFormat;
private DiskCache.Factory diskCacheFactory;
...
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);
}
}
这个方法中会创建 BitmapPool、MemoryCache、DiskCache、DecodeFormat 等对象的实例,并在最后一行创建一个 Glide 对象的实例,然后将前面创建的这些实例传入到 Glide 对象当中,以供后续的图片加载操作使用。
但是大家有没有注意到一个细节,createGlide()方法中创建任何对象的时候都做了一个空检查,只有在对象为空的时候才会去创建它的实例。也就是说,如果我们可以在 applyOptions()方法中提前就给这些对象初始化并赋值,那么在 createGlide()方法中就不会再去重新创建它们的实例了,从而也就实现了更改 Glide 配置的功能。关于这个功能我们待会儿会进行具体的演示。
现在继续回到 Glide 的 get()方法中,得到了 Glide 对象的实例之后,接下来又通过一个循环调用了每一个 GlideModule 的 registerComponents()方法,在这里我们可以加入替换 Glide 的组件的逻辑。
好了,这就是 Glide 自定义模块的全部工作原理。了解了它的工作原理之后,接下来所有的问题就集中在我们到底如何在 applyOptions()和 registerComponents()这两个方法中加入具体的逻辑了,下面我们马上就来学习一下。
更改 Glide 配置
=========
刚才在分析自定义模式工作原理的时候其实就已经提到了,如果想要更改 Glide 的默认配置,其实只需要在 applyOptions()方法中提前将 Glide 的配置项进行初始化就可以了。那么 Glide 一共有哪些配置项呢?这里我给大家做了一个列举:
setMemoryCache()? ? ?用于配置 Glide 的内存缓存策略,默认配置是 LruResourceCache。
setBitmapPool()? ? ? 用于配置 Glide 的 Bitmap 缓存池,默认配置是 LruBitmapPool。
setDiskCache()? ? ? ?用于配置 Glide 的硬盘缓存策略,默认配置是 InternalCacheDiskCacheFactory。
setDiskCacheService()? ? ? ?用于配置 Glide 读取缓存中图片的异步执行器,默认配置是 FifoPriorityThreadPoolExecutor,也就是先入先出原则。
setResizeService()? ? ? ? 用于配置 Glide 读取非缓存中图片的异步执行器,默认配置也是 FifoPriorityThreadPoolExecutor。
setDecodeFormat()? ? ? 用于配置 Glide 加载图片的解码模式,默认配置是 RGB_565。
其实 Glide 的这些默认配置都非常科学且合理,使用的缓存算法也都是效率极高的,因此在绝大多数情况下我们并不需要去修改这些默认配置,这也是 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<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);
}
}
可以看到,HttpUrlGlideUrlLoader.Factory 是一个内部类,外层的 HttpUrlGlideUrlLoader 类实现了 ModelLoader<GlideUrl, InputStream>这个接口,并重写了 getResourceFetcher()方法。而在 getResourceFetcher()方法中,又创建了一个 HttpUrlFetcher 的实例,在这里才是真正处理具体网络通讯逻辑的地方,代码如下所示:
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 , 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<InputStream>接口,代码如下所示:
public class OkHttpFetcher implements DataFetcher<InputStream> {
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 priori
《Android 学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
ty) 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());
}
最后
如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

欢迎大家一起交流讨论啊~
评论