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

原文: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2018/0403/9555.html

这里添加了一些自己的阅读笔记.

版本 4.x

一、前言

在众多的图片加载框架中,Glide是Google推荐的,并在自家的项目中大量使用的一个非常强大的框架,专注于平滑滚动,并且还提供Gif,本地Vedio首帧的解码和显示。Glide提供了非常便捷的链式调用接口,以及丰富的拓展和自定义功能,开发者可以非常简单地对框架进行配置和图片再加工。

如今Gilde已经更新到4.x,了解其源码对更好的使用Glide,以及学习相关的图片处理技术,学习更优雅的编码会有很大的帮助。

再github上放了一个最简单的工程, 可以直接拿来研究代码
https://github.com/shaopx/GlideApp

当然你要是不嫌麻烦可以去官方地址: 要想正常编译还是要费点劲的.
https://github.com/bumptech/glide

不得不说,Glide整个框架的极其复杂的,特别是在对资源的转换和解码过程中,涉及了许多的嵌套循环,同时也使用了大量的工厂模式用于生产转换模块,编码模块,解码模块等,笔者在阅读过程中,多次迷失在茫茫的代码流中。

为此,萌生了将对Glide的理解记录成文的想法,借以理清思路,也希望这一系列的文章可以帮助到无论是了解,还是准备阅读Glide源码的你,稍微理清一些思路。如有不对的地方,欢迎指正~

那么接下来,我们就先看看Glide是如何进行框架初始化的。

注意:本文源码版本为v4.6.1,不同版本可能存在一些差异!

二、Glide.with发生了什么?

1. Glide单例的加载

使用过Glide的都知道,调用Glide加载一张图片时,第一句代码便是Glide.with(this),这里肯定就是Glide的入口了,通过这句代码,Glide开始了“漫漫的”初始化之路。

Glide重载了多个with的方法,分别用于不同的情境下使用,我们看其中最常用的在Activity中调用的方法,即

image

首先,跟进getRetriever(activity)

image

这里首先检查了context是否为空,如果为null,抛出异常。

我们重点来看Glide.get(context)

image

这里是一个典型的双检锁单例模式。

继续跟进checkAndInitialzeGlide(context)

image
image

注意这里,在最后注入了一个GlideBuilder,这个就是Glide的建造器,用于构建Glide的一些参数和配置。

最后,来到真正初始化Glide的方法(代码去除了一些Log打印)。

image

留意最后将初始化得到的glide赋值给了Glide.glide的单例。

接下里就来看看在这初始化方法中,Glide都加载了哪些配置。

2. GlideModule配置加载

在使用Glide的时候,我们都会有一些想要设置的系统级配置,如设置缓存的存储位置,缓存区的大小,网络加载模块等等,那么我们通常就是使用GldieModule进行配置。在Glide3.x中,我们首先会定义一个继承于GlideModule的类,然后在项目的AndroidMenifest.xml中进行指定:

1.  `<meta-data  android:name="com.test.GlideConfiguration"`
2.  `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的配置进行初始化的呢?

image

第二行代码中,getAnnotationGeneratedGlideModules()会获取Glide注解自动生产的一个Glide的Module配置器。如下:

image

其中‘com.bumptech.glide.GeneratedAppGlideModuleImpl’是在编译时由Glide生成的一个类,主要用于过滤不必要的GlideModule,以及提供一个请求检索器工厂,这个后面会讲到。

接下生成一个Manifest解析器ManifestParser,用于获取配置的GlideModule,并存放在manifestModules中。然后是一个判断


    if (annotationGeneratedModule != null
            && !annotationGeneratedModule.getExcludedModuleClasses().isEmpty()) {
        ......
    }

如果条件成立,即编译时自动生成的类中,包含了需要排除的GlideModule,逐个将其移除。

接着以上代码,Glide将逐个调用剩下的GlideModule,并回调applyOptions和registerComponents接口,这时,用户配置的GlideModule就会被调用,同时用户设置的参数也就被配置到Glide中。

image

在以上代码中,发现一句代码,在回调registerComponents前,首先构建了glide的实例。

这是一句非常重要的代码,整个Glide框架最重要的初始化内容都在其中实现。


1.  `Glide glide = builder.build(applicationContext);`

3. GlideBuilder构建Glide单例

跳转到GlideBuilder中,看build方法做了哪些事情。代码并不复杂,直接看代码中的注释。

image

通过以上一系列工具的新建,Glide建立了资源请求线程池,本地缓存加载线程池,动画线程池,内存缓存器,磁盘缓存工具等等,接着构造了Engine数据加载引擎,最后再将Engine注入Glide,构建Glide。

其中还建立了一个请求器索引器,用于索引RequestManger,后面我们再详细讲。

我们进入最后, 构建Glide。

4. 构建Glide,配置数据转换器/解码器/转码器/编码器

回到Glide中,看看Glide的构造函数,这是一个长得变态的构造函数(有200行),但是不必被它吓倒(好吧,其实第一次看到这里,我是被吓倒了,直接略过去了,限于文章篇幅,这里只截取了部分源码,仔细的话可以直接看源码),仔细分析一下,其实整个构造过程并没那么复杂。

image

其中最重要的是步骤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中

image

在构建好Glide后,通过getRequestManagerRetriever()将会得到一个RequestManagerRetriever,即RequestManager的检索器,RequestManagerRetriever#get()将为每个请求页面创建一个RequestManager。

还记得GlideBuilder#build提到的一句代码吗?


1.  `RequestManagerRetriever requestManagerRetriever =`
2.  `new  RequestManagerRetriever(requestManagerFactory);`

没错,这里获取的就是它。这里就必须要讲到Glide数据请求的生命周期了。

我们都知道Glide会根据页面的生命周期来自动的开启和结束数据的请求,那么Glide是怎么做到的呢?

5. 生命周期管理

我们进入RequestManagerRetriever#get(Activity)方法中。

image

首先,判断是否为后台线程,如果是,则使用ApplicationContext重新获取。
重点来看else代码块。先断言请求的页面是否已经销毁。否则获取当前页面的FragmentManager,并传给fragmentGet方法。

image

在fragmentGet中首先通过getRequestManagerFragment()来获取一个命名为FRAGMENT_TAG的fragment,如不存在,则新建一个RequestManagerFragment,并添加到当前页面中。

这里我们就可以猜到了,Glide是通过在页面中添加一个Fragment来动态监听页面的创建和销毁,从而达到依赖页面生命周期,动态管理请求的目的。

image

在RequestManagerFragment构造函数中,注入了一个生命周期监听器ActivityFragmentLifecycle,并在Fragment各个生命周期回调中,调用了对应的方法。

而ActivityFragmentLifecycle也紧接着会调用lifecycleListener监听器,而这个监听器其实就是RequestManger。如下:

image

最后,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初始化的源码解析了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容