Glide 系列(三) Glide源码整体流程梳理
![](https://img.haomeiwen.com/i5105267/4ca33ea8feb194cd.jpg)
闲聊
回顾了一下历史文章,不知不觉,我们安卓兽已经坚持整整一年时间来维护我们的公众号了,陆陆续续一共发布了7个系列的文章。总觉得不管是对于团队,还是个人,这都是一件非常有意义的事儿。无所谓关注度,无所谓点赞数,无所谓打赏数,只要安卓兽的成员们能够持续成长,记录下自己的成长历程,就好了。
在新的一年,安卓兽们,继续加油~~~
前言
在前面几个不同系列的源码分析之后,今天我们终于来到图片加载框架的源代码分析。先说说为什么要选择Glide,而不选择其他,其实理由说起来很简单也很粗暴:Google推荐,没毛病!
相比较前面几个系列的源代码,Glide的源代码读起来确实还是有些麻烦的,里面涉及的类众多而繁杂,调用链条错综而冗长。如果真的想把每一行代码搞清楚,那势必会花费超长的时间。因此对于Glide的源码分享,我们的整体计划是:本篇先做一个整体的流程梳理,让大家知道Glide在图片加载过程中,到底做了哪几件大事,围绕每件大事又做了哪几件小事;然后接下来的几篇文章,会针对各个特性,进行针对性介绍。
那么现在就开始我们的介绍吧,本篇文章主要基于Glide 3.7.0版本。
主流程梳理
虽然Glide代码量比较大,但经过我们的抽丝剥茧,Glide在图片加载过程中,其实主要只做了几件大事。把这几件大事先梳理清楚,非常有助于我们对于源代码的阅读。具体可参考下图:
![](https://img.haomeiwen.com/i5105267/2a274eebbf9c66cf.jpg)
三件大事:
- “准备数据”。Glide中存在着大量的类和对象,而Glide的做法是在最开始的时候,都尽量把各种将来要用的对象就构造出来,并按需封装起来,然后层层向后面的流程进行传递。漫长的调用链条中,时不时就要用到最开始就创建好的对象,并且可能经过层层的解封装才能拿到真正想要用到的对象。也正是因此,使得读者往往迷失其中,造成了Glide源代码阅读起来较为困难。
但仔细阅读代码,还是可以看出规律,“准备数据”其实可以划分两件小事:第一阶段,构建出GenericRequest对象,该对象封装了大量对象于一身,并且这些对象受用户调用API或者修改配置所影响;第二阶段,从GenericRequest对象中,解封装拿到需要的对象,构建出decodeJob对象,该对象是“异步处理”中的核心对象。可以这样理解:GenericRequest对象面向用户而构建,decodeJob对象面向Glide而构建。 - “异步处理”。经过前面的大量准备工作,这一步,在工作线程中,Glide就要开始大干拳脚了。可以划分为三件小事:发起网络请求,拿到数据流;将数据流解码成bitmap对象;将bitmap对象转码成Drawable对象。
- “切换到主线程”:满足要求的Drawable对象已经拿到,那么我们就要切换回主线程,目的是将其显示出来。这一步主要就是一件小事:显示Drawable。
经过这一番图文讲解,Glide的主流程是不是更清晰了些?对的,万变不离其宗,Glide也一样的,显示图片不过就是经过这么几步而已。
主流程细化
接下来,我们对于主流程进行进一步细化。
对于用户来说,想要显示一张图,只需要简单的调用下面的代码就可以了。
Glide.with(this).load(url).into(imageView);
那么我就从这三个函数入手,看看Glide是如何通过这三个函数,最终跑通以上主流程的。
注意,以下依然通过图文的方式进行介绍,以方便读者理解。另外,以下时序图非常详细,几乎包含了源代码调用的每一处细节,比较难理解的对象,我也基本都写了简单的注释,方便读者追溯其由来,从而疏通整个调用流程。所以我就不贴太多源代码了,读者如果有不清楚的地方,对照一下源代码,应该就可以理解了。
with
with属于“准备数据”之“第一阶段”,我们先展示其时序图:
![](https://img.haomeiwen.com/i5105267/bbd54d9ebab56d7d.jpg)
从上图我们可以看出,with的代码流程,还是比较简单,最终会返回一个RequestManager类型的对象,以供下一步调用。
另外需要说明的是:
- with中传入的参数,决定了图片加载的生命周期。
- 如果在工作线程中使用Glide,不管with中传入参数是什么,Glide生命周期都与Application对象的生命周期一致。
想特别了解的同学可以看一下源码。
load
load属于“准备数据”之“第一阶段”,我们先展示其时序图:![](https://img.haomeiwen.com/i5105267/c8de90a2e5d97d51.jpg)
从上图我们可以看出,load的代码流程,稍微复杂了一些,但是实质上还是做前期的数据准备,主要就是构造对象,封装对象。
另外需要说明的是:
- load中的参数可以多种多样,这里我们以传入String对象为例,进行讲解。
- 图中的streamModelLoader实际上是StreamStringLoader,没有注释由来。简单说一下:主要是因为在Glide对象的构造过程中就注册了对应关系,这里只是通过这层对应关系,找到了StreamStringLoader对象而已。在后面的“替换组件”章节,还会提到这一段代码的。
register(String.class, InputStream.class, new StreamStringLoader.Factory());
into
into比较复杂,其涉及了“准备数据”,“异步处理”,“切换到主线程”这三大步的内容。因为其内容较多,一张时序图实在展示不下,我们会分解成5张时序图,来顺序进行说明。
into-1
into-1涉及了“准备数据”的“第一阶段”和“第二阶段”,我们先展示其时序图:
![](https://img.haomeiwen.com/i5105267/4d626aa3d05d1229.jpg)
从上图我们可以看出,into-1的代码流程,实质上还是做数据准备,主要就是构造对象,封装对象。需要关注的是其构造出了GenericRequest对象。
另外需要说明的是:
- 最后调用的into函数接收Target类型的对象,也就是说我们在外部使用Glide时候,可以一步到位,不必传ImageView类型的对象,而直接传递Target类型的对象。而Target实际上是一个接口,可以让我知道图片加载过程中的各种状态,如开始,结束,失败等等。
- buildRequest函数,最终将所有相关的对象,都封装到一个单一的对象中,即GenericRequest类型的对象。至此“准备数据”的“第一阶段”完成。从begin函数起,进入“准备数据”的“第二阶段”,此阶段会以GenericRequest对象为单位进行后续流程。
- onLoadStarted和onLoadFailed函数可以显示用户设置的占位符。
- 如果使用了override() API为图片指定了一个固定的宽高,那么直接调用onSizeReady函数;如果没有指定,那么调用getSize函数,由Glide帮我们计算出宽高,但最终还是会调用会onSizeReady函数。所以这张图的出口就是onSizeReady函数。
into-2
into-2属于“准备数据”之“第二阶段”,并最终开启了“异步调用”的大门,先展示其时序图:
![](https://img.haomeiwen.com/i5105267/745c69da1189d3b8.jpg)
从上图我们可以看出,into-2的代码流程,实质上还是做数据准备,主要就是构造对象,封装对象。需要注意的是,这里开始使用“第一阶段”生成的GenericRequest对象,从GenericRequest对象取出各种需要的对象,传递给Engine的load函数,最终构造出了decodeJob对象。
另外需要说明的是:
- decodeJob对象是下一步“异步调用”的核心对象,“发起网络请求,拿到数据流”、“将数据流解码成bitmap对象”和“将bitmap对象转码成Drawable对象”,都是从decodeJob对象中发起的。在接下来的into-3和into-4中,读者就可以看到。
- 图中的StreamStringLoader的getResourceFetcher函数返回的是HttpUrlFetcher对象,没有注释由来。简单说一下:主要还是因为在Glide对象的构造过程中就注册了多个对应关系,经过三个对应关系的链式传递,最终返回了HttpUrlFetcher对象。这里贴一下简单的代码,有兴趣的同学,可以根据此线索追踪一下:
register(String.class, InputStream.class, new StreamStringLoader.Factory());
register(Uri.class, InputStream.class, new StreamUriLoader.Factory());
register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
public static class Factory implements ModelLoaderFactory<String, InputStream> {
@Override
public ModelLoader<String, InputStream> build(Context context, GenericLoaderFactory factories) {
return new StreamStringLoader(factories.buildModelLoader(Uri.class, InputStream.class));
}
}
public static class Factory implements ModelLoaderFactory<Uri, InputStream> {
@Override
public ModelLoader<Uri, InputStream> build(Context context, GenericLoaderFactory factories) {
return new StreamUriLoader(context, factories.buildModelLoader(GlideUrl.class, InputStream.class));
}
}
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
@Override
public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
return new HttpUrlGlideUrlLoader(modelCache);
}
}
public class HttpUrlGlideUrlLoader implements ModelLoader<GlideUrl, InputStream> {
@Override
public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
return new HttpUrlFetcher(url);
}
}
into-3
into-3和into-4可以连在一起看。into-3属于“异步处理”之“发起网络请求,拿到数据流”和“将数据流解码成bitmap对象”,先展示其时序图:
![](https://img.haomeiwen.com/i5105267/ba7961161451d685.jpg)
从上图我们可以看出,into-3的代码流程,主要完成了“发起网络请求,拿到数据流”和“将数据流解码成bitmap对象”两件小事。
另外需要说明的是:
- 该时序图涉及的流程中,有些对象比较难追溯,我都写了相关注释,方便读者阅读源代码。
- 拿到bitmap对象之后,并没有直接返回bitmap对象,而是进行了封装,其目的就是为了封装之后,Bitmap图片和Gif图片可以统一处理。
into-4
into-4和into-3可以连在一起看。into-4属于“异步处理”之“将bitmap对象转码成Drawable对象”,先展示其时序图:
![](https://img.haomeiwen.com/i5105267/1eb83ff2f1c7dcb2.jpg)
从上图我们可以看出,into-4的代码流程,主要完成了“异步处理”中的第三件小事:“将bitmap对象转码成Drawable对象”
另外需要说明的是:
- 该时序图涉及的流程中,有些对象比较难追溯,我都写了相关注释,方便读者阅读源代码。
- 转码的原因:只有Bitmap或者Drawable才能显示到ImageView上,而Bitmap转换成Drawable是为了保证静图和动图的类型一致性(动图的类型是Drawable),逻辑上好处理。具体可以参考如下代码:
public Resource<GlideDrawable> transcode(Resource<GifBitmapWrapper> toTranscode) {
GifBitmapWrapper gifBitmap = toTranscode.get();
Resource<Bitmap> bitmapResource = gifBitmap.getBitmapResource();
final Resource<? extends GlideDrawable> result;
if (bitmapResource != null) {
result = bitmapDrawableResourceTranscoder.transcode(bitmapResource);
} else {
result = gifBitmap.getGifResource();
}
// This is unchecked but always safe, anything that extends a Drawable can be safely cast to a Drawable.
return (Resource<GlideDrawable>) result;
}
public Resource<Bitmap> getBitmapResource() {
return bitmapResource;
}
public Resource<GifDrawable> getGifResource() {
return gifResource;
}
into-5
into-5属于“切换到主线程”之“显示Drawable对象”,先展示其时序图:
![](https://img.haomeiwen.com/i5105267/d4d70aa429af00f8.jpg)
从上图我们可以看出,into-5的代码流程,主要完成了在主线程中显示Drawable对象
另外需要说明的是:
- 该时序图涉及的流程中,有些对象比较难追溯,我都写了相关注释,方便读者阅读源代码。
- 通过Handler机制,Glide从工作线程切换到主线程,并最终将Drawable对象显示到ImageView上。
总结
经过一番图文讲解,读者是不是对于Glide源码整体流程有了大致的了解。如果想进一步了解的话,建议读者跟着时序图,走一遍源代码,应该会收获更多。
对于阅读复杂的源码,分享给大家一点经验:先从框架或者主流程入手,然后再逐个细化各个模块,最后再想想作者为什么要这么设计,我们能学习或者借鉴到什么。
好了,这一篇就到这里吧,接下来的几篇文章,会针对各个特性,进行针对性介绍,敬请期待。