Android Glide4 源码解析--框架初始化

Glide

一、前言

在众多的图片加载框架中,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
StreamEncoderInputStream缓存为File
BitmapEncoder将Bitmap数据缓存为File
BitmapDrawableEncoder将BitmapDrawable数据缓存为File
GifDrawableEncoder将GifDrawable数据缓存为File
  • 模型转换注册表(实在太多,只列出了部分)
源数据转换数据转换器
Integer.classInputStream.classResourceLoader.StreamFactory
Integer.classParcelFileDescriptor.classResourceLoader.FileDescriptorFactory
..................
String.classInputStream.classDataUrlLoader.StreamFactory
String.classInputStream.classStringLoader.StreamFactory
..................
Uri.classInputStream.classDataUrlLoader.StreamFactory
Uri.classInputStream.classHttpUriLoader.Factory
Uri.classInputStream.classUriLoader.StreamFactory
URL.classInputStream.classUrlLoader.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加载资源过程的源码分析,敬请期待~