Android Glide4 源码解析--框架初始化
一、前言
在众多的图片加载框架中,Glide是Google推荐的,并在自家的项目中大量使用的一个非常强大的框架,专注于平滑滚动,并且还提供Gif,本地Vedio首帧的解码和显示。Glide提供了非常便捷的链式调用接口,以及丰富的拓展和自定义功能,开发者可以非常简单地对框架进行配置和图片再加工。
如今Gilde已经更新到4.x,了解其源码对更好的使用Glide,以及学习相关的图片处理技术,学习更优雅的编码会有很大的帮助。
不得不说,Glide整个框架的极其复杂的,特别是在对资源的转换和解码过程中,涉及了许多的嵌套循环,同时也使用了大量的工厂模式用于生产转换模块,编码模块,解码模块等,笔者在阅读过程中,多次迷失在茫茫的代码流中。
为此,萌生了将对Glide的理解记录成文的想法,借以理清思路,也希望这一系列的文章可以帮助到无论是了解,还是准备阅读Glide源码的你,稍微理清一些思路。如有不对的地方,欢迎指正~
那么接下来,我们就先看看Glide是如何进行框架初始化的。
注意:本文源码版本为v4.6.1,不同版本可能存在一些差异!
二、Glide.with发生了什么?
1. Glide单例的加载
使用过Glide的都知道,调用Glide加载一张图片时,第一句代码便是Glide.with(this),这里肯定就是Glide的入口了,通过这句代码,Glide开始了“漫漫的”初始化之路。
Glide重载了多个with的方法,分别用于不同的情境下使用,我们看其中最常用的在Activity中调用的方法,即
首先,跟进getRetriever(activity)
这里首先检查了context是否为空,如果为null,抛出异常。
我们重点来看Glide.get(context)
这里是一个典型的双检锁单例模式。
继续跟进checkAndInitialzeGlide(context)
注意这里,在最后注入了一个GlideBuilder,这个就是Glide的建造器,用于构建Glide的一些参数和配置。
最后,来到真正初始化Glide的方法(代码去除了一些Log打印)。
留意最后将初始化得到的glide赋值给了Glide.glide的单例。
接下里就来看看在这初始化方法中,Glide都加载了哪些配置。
2. GlideModule配置加载
在使用Glide的时候,我们都会有一些想要设置的系统级配置,如设置缓存的存储位置,缓存区的大小,网络加载模块等等,那么我们通常就是使用GldieModule进行配置。在Glide3.x中,我们首先会定义一个继承于GlideModule的类,然后在项目的AndroidMenifest.xml中进行指定:
<meta-data android:name="com.test.GlideConfiguration"
android:value="GlideModule"/>
而在Glide4中,提供另外一个配置的模式,那就是注解,并且不再继承GlideModule,而是继承AppGlideModule和LibraryGlideModule,分别对应Application和Library,使用@GlideModule注解进行标记。而Glide3.x中的配置方式已经建议放弃使用。
@GlideModule
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
//设置缓存到外部存储器
builder.setDiskCache(new ExternalPreferredCacheDiskCacheFactory(context));
}
}
那么,Glide是如何对GlideModule的配置进行初始化的呢?
第二行代码中,getAnnotationGeneratedGlideModules()会获取Glide注解自动生产的一个Glide的Module配置器。如下:
其中‘com.bumptech.glide.GeneratedAppGlideModuleImpl’是在编译时由Glide生成的一个类,主要用于过滤不必要的GlideModule,以及提供一个请求检索器工厂,这个后面会讲到。
接下生成一个Manifest解析器ManifestParser,用于获取配置的GlideModule,并存放在manifestModules中。然后是一个判断
if (annotationGeneratedModule != null
&& !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
......
}
如果条件成立,即编译时自动生成的类中,包含了需要排除的GlideModule,逐个将其移除。
接着以上代码,Glide将逐个调用剩下的GlideModule,并回调applyOptions和registerComponents接口,这时,用户配置的GlideModule就会被调用,同时用户设置的参数也就被配置到Glide中。
在以上代码中,发现一句代码,在回调registerComponents前,首先构建了glide的实例。
这是一句非常重要的代码,整个Glide框架最重要的初始化内容都在其中实现。
Glide glide = builder.build(applicationContext);
3. GlideBuilder构建Glide单例
跳转到GlideBuilder中,看build方法做了哪些事情。代码并不复杂,直接看代码中的注释。
通过以上一系列工具的新建,Glide建立了资源请求线程池,本地缓存加载线程池,动画线程池,内存缓存器,磁盘缓存工具等等,接着构造了Engine数据加载引擎,最后再将Engine注入Glide,构建Glide。
其中还建立了一个请求器索引器,用于索引RequestManger,后面我们再详细讲。
我们进入最后, 构建Glide。
4. 构建Glide,配置数据转换器/解码器/转码器/编码器
回到Glide中,看看Glide的构造函数,这是一个长得变态的构造函数(有200行),但是不必被它吓倒(好吧,其实第一次看到这里,我是被吓倒了,直接略过去了,限于文章篇幅,这里只截取了部分源码,仔细的话可以直接看源码),仔细分析一下,其实整个构造过程并没那么复杂。
其中最重要的是步骤3和步骤4,分别为Glide初始化了模型转换加载器,解码器,转码器,编码器,并将对各种类型进行一一注册,将其列成表格如下:
- 模型转换器
转换器 | 功能 |
---|---|
ResourceLoader.StreamFactory | 将Android资源ID转换为Uri,在加载成为InputStream |
ResourceLoader.UriFactory | 将资源ID转换为Uri |
ResourceLoader.FileDescriptorFactory | 将资源ID转化为ParcelFileDescriptor |
ResourceLoader.AssetFileDescriptorFactory | 将资源ID转化为AssetFileDescriptor |
UnitModelLoader.Factory | 不做任何转换,返回源数据 |
ByteBufferFileLoader.Factory | 将File转换为ByteBuffer |
FileLoader.StreamFactory | 将File转换为InputStream |
FileLoader.FileDescriptorFactory | 将File转化为ParcelFileDescriptor |
DataUrlLoader.StreamFactory | 将Url转化为InputStream |
StringLoader.StreamFactory | 将String转换为InputStream |
StringLoader.AssetFileDescriptorFactory | 将String转换为AssetFileDescriptor |
HttpUriLoader.Factory | 将http/https Uri转换为InputStream |
UriLoader.StreamFactory | 将Uri转换为InputStream |
UriLoader.FileDescriptorFactory | 将Uri转换为ParcelFileDescriptor |
UriLoader.AssetFileDescriptorFactory | 将Uri转换为AssetFileDescriptor |
UrlUriLoader.StreamFactory | 将将http/https的Uri转换为InputStream |
UrlLoader.StreamFactory | 将Url转换为InputStream |
HttpGlideUrlLoader.Factory | 将HttpGlide转换为InputStream |
...... | ...... |
- 解码器
解码器 | 功能 |
---|---|
ByteBufferGifDecoder | 将ByteBuffer解码为GifDrawable |
ByteBufferBitmapDecoder | 将ByteBuffer解码为Bitmap |
ResourceDrawableDecoder | 将资源Uri解码为Drawable |
ResourceBitmapDecoder | 将资源ID解码为Bitmap |
BitmapDrawableDecoder | 将数据解码为BitmapDrawable |
StreamBitmapDecoder | 将InputStreams解码为Bitmap |
StreamGifDecoder | 将InputStream数据转换为BtyeBuffer,再解码为GifDrawable |
GifFrameResourceDecoder | 解码gif帧 |
FileDecoder | 包装File成为FileResource |
UnitDrawableDecoder | 将Drawable包装为DrawableResource |
UnitBitmapDecoder | 包装Bitmap成为BitmapResource |
VideoDecoder | 将本地视频文件解码为Bitmap |
- 转码器
转码器 | 功能 |
---|---|
BitmapDrawableTranscoder | 将Bitmap转码为BitmapDrawable |
BitmapBytesTranscoder | 将Bitmap转码为Byte arrays |
DrawableBytesTranscoder | 将BitmapDrawable转码为Byte arrays |
GifDrawableBytesTranscoder | 将GifDrawable转码为Byte arrays |
- 编码器
编码器 | 功能 |
---|---|
ByteBufferEncoder | 将Byte数据缓存为File |
StreamEncoder | InputStream缓存为File |
BitmapEncoder | 将Bitmap数据缓存为File |
BitmapDrawableEncoder | 将BitmapDrawable数据缓存为File |
GifDrawableEncoder | 将GifDrawable数据缓存为File |
- 模型转换注册表(实在太多,只列出了部分)
源数据 | 转换数据 | 转换器 |
---|---|---|
Integer.class | InputStream.class | ResourceLoader.StreamFactory |
Integer.class | ParcelFileDescriptor.class | ResourceLoader.FileDescriptorFactory |
...... | ...... | ...... |
String.class | InputStream.class | DataUrlLoader.StreamFactory |
String.class | InputStream.class | StringLoader.StreamFactory |
...... | ...... | ...... |
Uri.class | InputStream.class | DataUrlLoader.StreamFactory |
Uri.class | InputStream.class | HttpUriLoader.Factory |
Uri.class | InputStream.class | UriLoader.StreamFactory |
URL.class | InputStream.class | UrlLoader.StreamFactory |
...... | ...... | ...... |
以上模型转换注册表非常重要,在Glide进入解码流程时,将会遍历这里注册的所有可能转换的情形,尝试进行数据转换。
这里只列出部分情形,其它还包括File/Bitmap/Drawable/Byte等等几乎涵括了日常使用的情况。
Glide的加载流程可以概括为以下流程:
model(数据源)-->data(转换数据)-->decode(解码)-->transformed(缩放)-->transcoded(转码)-->encoded(编码保存到本地)
其中,transformed为对解码得到的图片数据进行缩放,如FitCenter、CropCenter等。
到这里,Glide单例就构建完成了,让我们返回到Glide#with中
在构建好Glide后,通过getRequestManagerRetriever()将会得到一个RequestManagerRetriever,即RequestManager的检索器,RequestManagerRetriever#get()将为每个请求页面创建一个RequestManager。
还记得GlideBuilder#build提到的一句代码吗?
RequestManagerRetriever requestManagerRetriever =
new RequestManagerRetriever(requestManagerFactory);
没错,这里获取的就是它。这里就必须要讲到Glide数据请求的生命周期了。
我们都知道Glide会根据页面的生命周期来自动的开启和结束数据的请求,那么Glide是怎么做到的呢?
5. 生命周期管理
我们进入RequestManagerRetriever#get(Activity)方法中。
首先,判断是否为后台线程,如果是,则使用ApplicationContext重新获取。
重点来看else代码块。先断言请求的页面是否已经销毁。否则获取当前页面的FragmentManager,并传给fragmentGet方法。
在fragmentGet中首先通过getRequestManagerFragment()来获取一个命名为FRAGMENT_TAG的fragment,如不存在,则新建一个RequestManagerFragment,并添加到当前页面中。
这里我们就可以猜到了,Glide是通过在页面中添加一个Fragment来动态监听页面的创建和销毁,从而达到依赖页面生命周期,动态管理请求的目的。
在RequestManagerFragment构造函数中,注入了一个生命周期监听器ActivityFragmentLifecycle,并在Fragment各个生命周期回调中,调用了对应的方法。
而ActivityFragmentLifecycle也紧接着会调用lifecycleListener监听器,而这个监听器其实就是RequestManger。如下:
最后,RequestManagerRetriever#fragmentGet,判断这个Fragment的RequestManager是否存在,否则创建一个RequestManager,并将生命周期注入,同时RquestManager构建时,将会通过addListener注入生命周期回调(具体可以查看RequestManger构造函数)。
最后,Glide#with终将得到一个RequestManager。
至此,Glide的加载过程就解析完毕了。总结一下整个流程:
- 通过AndroidManifest和@GlideModule注解获取用户自定义配置GlideModule,并调用其对应的方法
- 通过GlideBuilder构建Glide:
1.新建线程池
2.新建图片缓存池和缓存池
3.新建内存缓存管理器
4.新建默认本地缓存管理器
5.新建请求引擎Engine
6.新建RequestManger检索器
7.新建Glide - Glide构造方法中,新建模型转换器,解码器,转码器,编码器,以及生成Glide上下文GlideContext
- 通过RequestManager检索器,建立生命周期监听,并建立一个RequestManager
- 完成!
三、 Glide与GlideApp
如果在项目中已经使用了Glide3.x,并且想要升级到Glide4.x,那么你会发现,原来使用链式调用进行参数配置的方法已经被修改了,同一个封装到了RequesOptions中,如下:
RequestOptions options = new RequestOptions()
.centerCrop()
.placeholder(R.mipmap.ic_launcher_round)
.error(R.mipmap.ic_launcher)
.priority(Priority.HIGH)
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(this)
.load(ImageConfig.URL_GIF)
.apply(options)
.into(iv);
这样的话升级后将导致大量的修改,当然你也可以自己封装一下,但是Glide已经为我们做好了兼容方案。
还记得初始化是通过@GlideModule注解来注册自定义配置吗?只要在项目中定义这么一个配置,那么Glide将会自动帮我们生成一个GlideApp模块,封装了Glide3.x中的调用方式。
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
}
调用如下,还是原来的配方,还是熟悉的味道~
GlideApp.with(this)
.load(ImageConfig.URL_WEBP)
.sizeMultiplier(0.5f)
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.error(R.mipmap.ic_launcher)
.into(iv);
如果你还觉得不爽,那么你甚至可以把GlideApp直接修改为Glide,实现几乎“无缝对接”。当然,你还是要修改引用路径的。
@GlideModule(glideName="Glide")
public class GlideConfiguration extends AppGlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
}
}
以上,就是Glide4初始化的源码解析了,其实还有许多的知识点可以继续深入,如AndroidManifest解析器,注解的使用(可参考我的另一文Android编译时注解,和重复代码Say No!)等等,有兴趣的话可以把源码下下来看看。
接下来将推出Glide4加载资源过程的源码分析,敬请期待~