Glide原理之史上最全的(一)
**前言
- Glide 基本用法
接下来的讲解将基于 Glide 目前的最新版本 4.11。
Glide 的使用特别简单,首先添加依赖。**
with()
可以传 Applicaiton、Activity 、Fragment 与 view 等类型的参数,加载图片的请求会与该参数的生命周期绑定在一起。
load()
可以传图片的网络地址、Drawable 等。
into()
一般传 ImageView 。
- 内容概览
Glide 加载图片大致可分为三个步骤。
发起加载图片请求
当我们用 into() 方法加载图片时,就是发起了一次图片加载请求;
执行解码任务
我们在 load() 方法中设置的图片来源会传到 DecodeJob 中,DecodeJob 就会被 Engine 提交到线程池中开始执行;
加载图片
当 DecodeJob 对图片解码完成后,就会把图片加载到 ImageView 中;
接下来会以这三个步骤为基础来展开 Glide 的图片加载流程,下面是每个大节讲解的内容。
- 四步启动解码任务
第一大节将会讲解从我们调用 into() 方法到启动 DecodeJob 的 run() 方法的过程中,Glide 做了哪些事情,包含了 Request、Target 、 Engine 和 DecodeJob 等内容。 - 六步加载图片
第二大节会讲解当 DecodeJob 获取到图片数据后,会怎么处理图片数据,也就是解码、加载图片和编码的过程,包括 ModelLoader、ResourceDecoder、Transformation、ResourceTranscoder 以及 ResourceEncoder 的实现。
3. Glide 缓存原理
这里会讲 Glide 的图片缓存相关的实现,包括内存缓存、磁盘缓存、BitmapPool 以及 ArrayPool 等内容。
4. Glide 初始化流程与配置
这一节会讲解 Glide 的初始化流程,包括 Glide 调用配置的方式、AppGlideModule 的两个方法中用到的 Registry 和 GlideBuilder 在 Glide 中的作用。 - Glide 图片加载选项
Glide 开放了非常多的图片加载选项,我们不一定全都用得上,但是了解这些选项,可以让我们在需要的时候能调用对应的选项,不用再自己实现一遍。
1. 四步启动解码任务
这一节我们先来看下从 into() 到启动 DecodeJob 的过程中涉及哪些对象。
从我们调用 into() 方法加载图片到启动解码任务 DecodeJob,大致分为 4 个步骤,涉及下面 4 个对象。
加载请求 Request
加载目标 Target
加载引擎 Engine
解码任务 DecodeJob
1.1 Request
1.1.1 请求构建器 RequestBuilder
- into()
对于我们发起的图片加载请求,Glide 会把这个请求封装为 Request,而 RequestBuilder 就是 Request 的构建器。
当我们用 into() 方法加载图片时,调用的其实是 RequestBuilder 的 into() 方法,这个方法会创建一个 Request ,并把 Request 传给请求管理器。 - Model
Glide 会把我们在 load() 中传入的图片来源数据封装为 Model ,而这个 Model 具体就是 RequestBuilder 中的 model 字段,类型为 Object 。 - 加载选项
RequestBuilder 继承了 BaseRequestOptions 抽象类,我们平时用的 centerCrop() 等方法大部分都是 BaseRequestOptions 的方法,关于图片加载选项在后面会讲到。
1.1.2 请求管理器 RequestManager
RequestManager 有下面几个特点。
绑定生命周期
监听网络状态
创建请求构建器
启动请求
1. 绑定生命周期
在 Glide 中,一个 Context 对应一个 RequestManager,当我们调用 with() 方法时,RequestManager 会用对应的 Context 创建一个 RequestManagerFragment 。
RequestManagerFragment 是一个无布局的 Fragment,主要是用来做生命周期关联的,当这个 Fragment 感知到 Activity 的生命周期发生变化时,就会告诉请求管理器,让它去做暂停请求、继续请求和取消请求等操作。
如果我们用的是 ApplicationContext 加载某张图片,那就意味着这次图片加载操作的生命周期是与应用的生命周期绑定的。
2. 监听网络状态
RequestManager 中有一个网络连接监听器 RequestManagerConnectivityListener ,它实现了ConnectivityListener 接口,每次网络状态切换时,RequestManager 就会重启所有的图片加载请求。
3. 创建请求构建器
我们在加载图片时调用的 load() 方法是 RequestManager 的方法,调用这个方法其实是创建了一个请求构建器 RequestBuilder,RequestManager 中有很多创建 RequestBuilder 的方法,比如 asDrawable()、asBitmap() 、asFile() 等,这些方法对应着不同泛型参数的 RequestBuilder 。
load() 方法支持下面这些类型的参数。
Bitmap
Drawable
String
Uri
URL
File
Integer(resourceId)
byte[]
Object
4. 启动请求
RequestManager 的 track() 方法调用了目标跟踪器 TargetTracker 的 track() 方法,还调用了请求跟踪器 RequestTracker 的 runRequest() 方法 。
TargetTracker
TargetTracker 实现了 LifecycleListener ,它会根据页面生命周期播放和暂停动画,比如暂停 Gif 动画。
RequestTracker
RequestTracker 的 runRequest() 方法调用了 Request.begin() 方法。
在 Request 的 begin() 方法中会获取 View 的尺寸,获取到了尺寸后就会调用 Engine 的 load() 方法启动图片加载请求。
1.1.3 Request 的 6 种状态
前面讲到的 Request 具体就是 SingleRequest ,SingleRequest 中有一个 Status 枚举类,包含了请求的 6 种状态。
1. 待运行 PENDING
当我们通过 into() 创建了一个 SingleRequest 后,该 Request 就进入了待运行状态。
2. 已清除 CLEARED
每次我们用 into() 方法加载图片时,RequestManager 都会先看下我们传入的 Target 是否有对应的 Request ,如果有的话就会调用该 Request 的 clear() 方法释放资源,这时 Request 就进入了已清除状态。
3. 待测量 WAITING_FOR_SIZE
当 RequestManager 调用 RequestTracker 的 runRequest() 方法后,RequestTracker 就会调用 Request 的 begin() 方法,这时请求就进入了待测量状态。
4. 运行中 RUNNING
在 SingleRequest 的 begin() 方法中,调用了 Target 的 getSize() 方法获取 ImageView 的尺寸,获取到尺寸后,SingleRequst 会调用 Engine 的 load() 方法启动图片加载请求,这时 Request 就进入了运行中状态。
5. 已完成 COMPLETE
当 Engine 从内存中加载到资源,或者通过解码任务加载到资源后,就会调用 SingleRequest 的 onResourceReady() 方法,这时 Request 就进入了已完成状态。
6. 失败 FAILED
当解码任务 DecodeJob 在处理图片的过程中遇到异常时,就会调用 EngineJob 的 onLoadFailed() 方法,然后 EngineJob 会调用 SingleRequest 的 onLoadFailed() 方法,这时 SingleRequest 就进入了失败状态。
1.1.4 三种占位图
我们在加载图片时,可以设置 placeholder、error 和 fallback 三种占位图。
placeholder
图片加载完成前显示的占位图;
error
图片加载失败时显示的占位图;
fallback
图片来源为空时显示的占位图;
使用占位图时,要注意占位图是不会使用 Transformation 进行变换的,如果你想弄个圆角或圆形的占位图,可以用 submit().get() 获取对应变换后的占位图的 Drawable 对象,然后传到对应的占位图设置方法中。
1.1.5 Request 相关问题
下面是几个跟 Request 相关的问题,看看你能不能答得上来。
我们平时用 Glide 加载图片调用的 into() 方法是哪个类的方法?
当设备的网络状态发生变化时,是谁负责重启图片加载请求?
Request 有哪几种状态?这些状态是如何流转的?
Glide 有几种占位图?分别在什么时候显示?
1.2 Target
当我们调用 into() 方法,传入 ImageView 后,Glide 会把 ImageView 转化为 Target ,下面我们来看下不同 Target 的作用。
1.2.1 ImageViewTarget
- SizeDeterminer
ImageViewTarget 继承了 ViewTarget ,在 ViewTarget 中有一个用来获取尺寸的 SizeDeterminer ,SizeDeterminer 的 getSize() 方法拿到的尺寸,是把 ImageView 的内边距 padding() 去掉后的尺寸。
在 Glide 中,宽高分为请求宽高和原始宽高 ,而 SizeDeterminer 拿到的尺寸就是请求宽高,Glide 会根据请求宽高对图片进行缩放操作,以减少不必要的内存消耗。 - OnPreDrawListener
当 Request 获取 View 的尺寸失败时,ViewTarget 会通过 ViewTreeObserver 的 OnPreDrawListener 的回调来获取 View 的尺寸,然后再传给 Request。 - setResource()
ImageViewTarget 主要有 BitmapImageViewTarget 和 DrawableImageViewTarget 两个子类,它们两个的区别就在于它们的 setResource() 方法。
BitmapImageViewTarget
setResource() 用的是 ImageView 的 setImageBitmap() 方法;
DrawableImageViewTarget
setResource() 用的是 ImageView 的 setImageDrawable() 方法;
1.2.2 RequestFutureTarget
- submit()
FutureTarget 是一个实现了 Future 和 Target 接口的接口,它只有一个 RequestFutureTarget 子类 ,当我们用 submit() 方法获取 Glide 加载好的图片资源时,就是创建了一个 RequestFutureTarget 。 - Waiter
RequestFutureTarget 是用 wait/notify 的方式来实现等待和通知的,这两个是 Object 的方法,Request 中有一个 Waiter ,当 DecodeJob 加载到图片后,RequestFutureTarget 就会让 Waiter 发出通知,这时我们的 get() 方法就能获取到返回值了。
这就是为什么我们用 RequestFutureTarget 的 get() 方法获取图片时,要把这个操作放在子线程运行。
1.2.3 CustomTarget
给不是 View 的 Target 加载图片时,Glide 都把它作为 CustomTarget 。 - PreloadTarget
预加载 Target 。
当我们调用 preload() 选项预加载图片时,Glide 会把图片交给 PreloadTarget 处理,当 PreloadTarget 接收到图片资源后,就会让 RequestManager 把该请求的资源释放掉。
因为不需要等待资源加载完成,所以我们在用 preload() 预加载图片时,不用像 submit() 一样在子线程中执行。 - AppWidgetTarget
桌面组件 Target 。
当 AppWidgetTarget 接收到处理好的图片资源后,会把它设置给 RemoteView ,然后通过桌面组件管理器 AppWidgetManager 更新桌面组件。 - DelayTarget
GifTarget。
这是加载 Gif 图片时要用到的 Target ,关于 Glide 加载 Gif 图片的流程在后面会讲到。 - NotificationTarget
通知栏 Target 。
这个 Target 有一个 setBitmap 方法,会把图片设置给通知栏的 RemoteView ,然后通过 NotificationManager 更新通知栏中的通知。
1.2.4 Target 相关问题
ImageViewTarget 是用什么来获取请求宽高的?
为什么在用 submit() 获取图片时,要放在子线程中执行?
使用 preload() 预加载图片时,用的是哪个 Target ?该 Target 获取到资源会后做什么?
1.3 Engine
下面我们来看一些与 Engine 相关的实现。
Engine 的作用
Key 的作用
Resource 的作用
BitmapPool
1.3.1 Engine 的作用
Engine 是 Glide 的图片加载引擎,是 Glide 中非常重要的一个类,下面我们来看下 Engine 的作用。
- load()
前面讲到了当我们调用 into() 方法时,就是间接调用了 Request.begin() 方法,而 Request 的 begin() 方法又调用了 Engine 的 load() 方法。
在 load() 方法中,Engine 会先用 EngineKeyFactory 创建资源标识符 Key,然后用这个 Key 去内存缓存中加载资源。
如果从内存中找到了资源,Engine 就会直接把资源回传给 Resource,如果没有加载到资源,Engine 就会创建并启动新的 EngineJob 和解码任务 DecodeJob。
2. EngineKeyFactory
EngineKeyFactory 是 Engine 中一个负责生产 EngineKey 的工厂,EngineKey 是引擎任务资源标识符,关于什么是 Key 后面进一步讲。
在 Engine 启动新的任务加载图片前,会先通过 EngineKeyFactory 创建一个 EngineKey,然后让 DecodeJob 把资源与 EngineKey 进行绑定,这里说的绑定,其实就是把 model 放到 EngineKey 中。
3. 回收资源
Engine 中有一个资源回收器 ResourceRecycler ,Resource 接口中有一个 recycle() 方法,关于 Resource 我们后面再讲。
这里只要知道,当 SingleRequest 被清除,比如在 into() 方法中发现 Target 已经有对应的 Request 时,Request 就会让 Engine 释放资源,具体做释放资源操作的就是 ResourceRecycler。
4. 磁盘缓存提供器
LazyDiskCacheProvider 是 Engine 中的一个静态内部类,是磁盘缓存 DiskCache 的提供器,DiskCache 是一个接口,关于 DiskCache 的实现我们后面再讲。
5. 启动新的解码任务
当 Engine 从内存中找不到对应的 Key 的资源时,就会启动新的解码任务。
Engine 会用加载任务工厂 EngineJobFactory 构建一个加载任务 EngineJob,然后再构建一个解码任务 DecodeJob。
EngineJob 这个名字看起来很霸气,但是实际上它并没有做什么事情,它只是 Engine 与 DecodeJob 之间沟通的桥梁。
当构建了 EngineJob 和 DecodeJob 后,Engine 就会把 DecodeJob 提交到线程池 GlideExecutor 中。
1.3.2 Key
前面讲到了 Engine 会通过 EngineKeyFactory 创建资源标识符 Key ,那什么是 Key ?
Key 是 Glide 中的一个接口,是图片资源的标识符。
1. 避免比较有误
Glide 的内存缓存和磁盘缓存用的都是 Glide 自己实现的 LruCache,LruCache 也就是最近最少使用缓存算法(Least Recently Used),LruCache 中有一个 LinkedHashMap ,这个 HashMap 的 Key 就是 Key 接口,而 Value 则是 Resource 接口。
在用对象作为 HashMap 的 Key 时,要重写 equals() 和 hashCode() 方法。
如果不重写这两个方法,那么当两个 Key 的内存地址不同,但是实际代表的资源相同时,使用父类 Object的 hasCode() 直接用内存地址做比较,那么结果会是不相等。
此外 Object 的 equals() 方法也是拿内存地址作比较,所以也要重写。
比如下面就是 ResourceCacheKey 的 equals() 判断逻辑。
- Key 实现类
下面是几个实现了 Key 接口的类。
DataCacheKey
原始图片数据标识符。
ResourceCacheKey
处理后的图片资源标识符。
AndroidResourceSignature
Android 资源标识符。当我们传入 into() 方法的图片是 R.drawable.xxx 时,Glide 就会把它封装为 AndroidResourceSignature 。
ObjectKey
通用资源标识符。
可以说除了 App 自带的 Android 资源以外的图片资源都会用 ObjectKey 作为标识符,比如本地图片文件。
EngineKey
引擎资源标识符。
这个 Key 是 Engine 对其他 Key 的封装,这时传进来的 Key 是以签名(Signature)的身份存在 EngineKey 中的。
1.3.3 Resource
前面讲到了 Engine 会通过 ResourceRecycler 来回收资源,而 ResourceRecycler 调用了 Resource 的 recycle() 方法。
可能你想起来 Bitmap 就有一个可以回收图片内存的 recycle() 方法,没错,Glide 回收 Bitmap 的方式就是用的 Bitmap 自带的 recycle() 方法,但是这个过程又比这复杂一些。
Resource 是一个接口,其中一个实现类是 BitmapResource ,也就是位图资源,比如网络图片就会转化为 BitmapResource。
在 BitmapResource 中有一个位图池 BitmapPool,这是 Glide 用来复用 Bitmap 的一个接口,具体的实现类是 LruBitmapPool 。
在 BitmapResource 的 recycle() 方法中,会把对应的 Bitmap 通过 put() 方法放到 BitmapPool 中,关于 BitmapPool 在讲 Glide 缓存原理时会进一步讲。
1.3.4 Engine 相关问题
Engine 的 load() 方法首先会做什么?
为什么 Key 要重写 hashCode() 和 equals() 方法?
负责回收 Resource 的是哪个类?
加载 Drawable 资源时,会转化为哪种 Key?
1.4 DecodeJob
前面讲到了 Engine 在缓存中找不到资源时,就会创建新的加载任务 EngineJob 和新的解码任务 DecodeJob ,然后让 EngineJob 启动 DecodeJob。
DecodeJob 实现了 Runnable 接口,EngineJob 启动 DecodeJob 的方式就是把它提交给 GlideExecutor,如果我们没有调整磁盘缓存策略的话,那默认用的就是 diskCacheExecutor ,关于 GlideExecutor 在第 4 大节会讲,下面我们先看下 DecodeJob 的实现。
1.4.1 runWrapped()
DecodeJob 的 run() 方法只是对 runWrapped() 可能遇到的异常进行了捕获,而 runWrapped() 方法会根据不同的运行理由 RunReason 运行不同的数据生成器。
1. 三种运行理由
runWrapped() 会根据下面三种运行理由来执行解码任务。
INITAILIZE
从缓存中获取数据并解码;
SWITCH_TO_SOURCE_SERVICE
从来源获取数据后再进行解码;
DECODE_DATA
当获取数据的线程与 DecodeJob 的线程不同时,比如使用了 OkHttp-Integration 时,DecodeJob 会直接对数据进行解码;
- 初始化
当运行理由为默认状态 INITIALIZE 时,DecodeJob 会从磁盘中获取图片数据并进行解码。 - 从来源获取数据
当 DecodeJob 从缓存中获取不到数据时,就会把运行理由改为 SWITCH_TO_SOURCE_SERVICE ,也就是从来源获取数据,然后运行来源数据生成器 SourceGenerator 。 - 对检索到的数据进行解码
DecodeJob 通过数据生成器获取到数据后,就会调用 decodeFromRetrievedData() 方法来对检索到的数据进行解码。
1.4.2 DecodeJob 数据获取流程
在 DecodeJob 的 getNextStage() 方法中,会根据当前的解码步骤 stage 来判断进行什么操作。
DecodeJob 把提取数据分为了 6 个阶段,这 6 个阶段是 Stage 枚举类中的值。 - INITIALIZE
初始化。
当解码处于这个阶段时,DecodeJob 会根据磁盘缓存策略,判断是否要从磁盘缓存中获取处理过的图片资源,是的话就用 ResourceCacheGenerator 获取图片资源,当用 ResourceCacheGenerator 获取到 Resource 后,就会开始对资源进行解码。
如果磁盘缓存策略设定了不从缓存中获取 Resource,那就会切换到 RESOURCE_CACHE 阶段。 - RESOURCE_CACHE
从缓存中获取处理过的图片资源。
当解码处于这个阶段时,DecodeJob 会根据磁盘缓存策略,判断是否要从磁盘缓存中获取未处理过的图片原始数据,是的话就用 DataCacheGenerator 获取图片数据。 - DATA_CACHE
从缓存中获取原始数据。
如果磁盘缓存策略设定了不获取缓存中的图片资源和原始数据 ,又或者是获取不到数据,DecodeJob 那就会切换到 DATA_CACHE 阶段。
如果我们在加载图片时调用了 onlyRetrieveFromCache(true) ,那么 DecodeJob 就会不会切换到 SOURCE 阶段从来源获取数据,而是会切换到 FINISH 阶段结束数据获取流程。
否则就会切换到 SOURCE 阶段。 - SOURCE
从图片来源获取原始数据。
如果 DecodeJob 在 RESOURCE_CACHE 和 DATA_CACHE 阶段都没有拿到图片数据,那就会用 SourceGenerator 从图片来源获取图片数据。 - ENCODE
编码。
当磁盘缓存策略设定了要对图片资源进行缓存时,那么在获取到数据后,DecodeJob 就会用 ResourceDecoder 对资源进行编码,也就是把图片放到磁盘缓存中。 - FINISH
结束。
1.4.3 三种数据生成器
当 DecodeJob 切换阶段后,会调用 getNextGenerator() 切换不同阶段对应的生成器,这里说的生成器,指的是 DataFetcherGenerator 接口。
DataFetcherGenerator 不是像名字说的那样用来创建 DataFetcher 的,DataFetcherGenerator 与 DataFetcher 是通过 ModelLoader 来关联的。
DataFetcherGenerator 会通过 ModelLoader 构建数据封装对象 LoadData ,然后通过 LoadData 中的 DataFetcher 来加载数据。
LoadData 是 ModelLoader 的内部类,它有来源标识符 Key 和 DataFetcher 两个字段。
在 ModelLoader 中最重要的就是 buildLoadData() 方法,不同类型的 Model 对应的 ModelLoader 所创建出来的 LoadData() 也不同。
下面我们来看下 DataFetcherGenerator ,这个接口中最重要的方法是 startNext() ,具体实现了这个接口有下面三个类。
SourceGenerator
来源数据生成器。
DataCacheGenerator
原始缓存数据生成器。
ResourceCacheGenerator
缓存资源生成器。
以 SourceGenerator 为例,我们来看下 startNext() 方法的处理流程。
1. 是否获取到了需要缓存的数据
当 SourceGenerator 加载完数据后,会再次进入 startNext() 方法,这时就获取到了需要缓存的数据。
2. 是否保存原始数据
如果磁盘缓存策略设定了要保存图片的原始数据,就用数据提取器加载数据,否则就直接把图片加载给 Target 。
3. 加载数据
当需要保存原始数据或数据有加载路径时,SourceGenerator 就会根据 Model 的类型,使用对应的 DataFetcher 来提取数据,比如从网络上下载图片。
4. 是否保存原始数据
当 SourceGenerator 获取到数据后,会再次判断是否要保存原始数据,否则就直接把图片加载给 Target 。
5. 编码
当 SourceGenerator 从 DataFetcher 中拿到数据后,会再走一遍 startNext() 方法,然后用编码器 Encoder 对数据进行编码,也就是把图片放到磁盘缓存中。
6. 从磁盘中获取数据
当 SourceGenerator 把数据保存到磁盘后,不会直接加载图片,而是从磁盘中拿这张图片,然后再进行加载。
1.4.4 onResourceDecoded()
当 DecodeJob 调用 ResourceDecoder 的 decode() 方法,并且获取到编码结果后,会调用 onResourceDecoded() 方法应用变换选项以及初始化编码管理器。
- 应用变换选项
对于处理过的 Resource,onResourceDecoded() 不会再次进行变换,否则就会对图片进行变换操作。 - 回收图片资源
当对资源应用了变换选项后,DecodeJob 会把原来的资源回收掉,因为这个资源接下来也用不上了。 - 缓存变换后图片资源
onResourceDecoded() 方法中,会根据磁盘缓存策略判断是否要对资源进行编码,如果要进行编码的话,会根据不同的编码策略创建不同的 Key 。
Glide 有 SOURCE 和 TRANSFORMED 两种编码策略,分别代表对原始数据进行编码和对变换后资源进行编码。
SOURCE
GIF 编码器 GifDrawableEncoder 中用的编码策略;
TRANSFORMED
位图编码器 BitmapEncoder 中用的编码策略;
- 初始化编码管理器
创建好 Key 后不会直接对图片进行编码,而是会修改编码管理器的 Key ,等到转码完成后再用 ResourceEncoder 进行编码。
1.4.5 DecodeJob 相关问题
DecodeJob 会根据哪些理由来执行任务?
DecodeJob 提取数据的过程分为哪几个阶段?
DataFetcherGenerator 有哪些实现类?
Glide 有几种编码策略?
- 六步加载图片
看完了解码任务启动流程,下面我们来看下当 DecodeJob 获取到图片数据后是怎么处理这些数据的,在文章的开头已经讲过 Glide 解码大致的 5 步,这里再补充一个,就是在把图片加载到 Target 后,DecodeJob 会通过 ResourceEncoder 把图片保存到本地。
其中关于 Target 在 1.2 小节已经讲过,下面就不再多讲了,我们来看下其他的对象。
Glide 对图片解码的过程涉及下面 6 个概念。
数据来源(Model)
原始数据(Data)
资源(Resource)
变换后资源(TransformedResource)
转码后资源(TranscodedResource)
目标(Target)
1. 数据来源(Model)
Glide 会以 Model 的形式封装图片来源 ,Model 可以是 URL、本地文件和网络图片等类型。
2. 原始数据(Data)
Glide 把数据源转换为Model 后,会把它加工成原始数据 Data ,一般就是输入流 InputStream ,Glide 会把这些输入流封装为 Data ,而 ModelLoader 则负责从 Data 获取原始数据。
3. 资源(Resource)
获取到原始数据后,Glide 会用资源解码器 ResourceDecoder 对原始数据进行解码,比如把输入流 InputStream 解码为 Bitmap,解码后的资源就是 Resource 。
4. 变换后资源(TransformedResource)
Glide 会根据我们的变换选项处理 Resource ,比如用 centerCrop() 裁剪就是一种变换,变换后的 Resource 就叫 TransformedResource ,负责转换的就是 Transformation 。
5. 转码后资源(TranscodedResource)
Glide 除了能加载静态图片,还能加载 Gif 动态图,解码后的 Bitmap 和 Gif 的类型不是统一的,为了统一处理静态和动态图片,Glide 会把 Bitmap 转换为 GlideBitmapDrawable ,而负责转码的角色则是 ResourceTranscoder 。
6. 目标(Target)
Glide 最终会把图片显示到目标 Target 上,比如 ImageView 对应的就是 ImageViewTarget 。
2.1 ModelLoader
ModelLoader 是一个接口,负责创建 LoadData ,它有两个泛型参数 Model 和 Data。
Model
代表图片来源的类型,比如图片的网络地址的 Model 类型为 String ;
Data
代表图片的原始数据的类型,比如网络图片对应的类型为 InputStream ;
1. Factory
在 DataFetcherGenerator 获取图片数据时,会调用 ModelLoaderRegistry 的 getModelLoaders() 方法,这个方法中会根据 model 的类型用 MultiModelLoaderFactory 生成对应的 ModelLoader,比如能够解析字符串的 ModelLoader 就有 7 个,关于 ModelLoaderRegistry 在后面讲 Glide 配置的时候会讲到。
此外每一个 ModelLoader 的实现类中都定义了一个实现了 ModelLoaderFactory 接口的静态内部类 。
2. handles()
一个 Model 对应这么多 ModelLoader,每个 ModelLoader 加载数据的方式都不同,这时候就要用 handles() 方法了。
ModelLoader 接口有 handles() 和 buildLoadData() 两个方法,handles() 用于判断某个 Model 是否能被自己处理,比如 HttpUriLoader 的 handles() 会判断传进来的字符串是否以 http 或 https 开头,是的话则可以处理。
3. buildLoadData()
ModelLoader 之间是存在嵌套关系的,比如 HttpUriLoader 的 buildLoadData() 方法就是调用的 HttpGlideUrlLoader 的 buildLoadData() 方法,HttpGlideUrlLoader 会创建一个 HttpUrlFetcher ,然后把它放到 LoadData() 中。
LoadData 是 ModelLoader 中定义的一个类,它只是放置了图片来源的 Key 和要用来提取数据的 DataFetcher ,没有其他方法。
2.2 ResourceDecoder
DataFetcherGenerator 使用 ModelLoader 构建完数据后,就会用 DataRewinder 对数据进行重绕,也就是重置数据,比如 InputStreamRewinder 就会调用 RecyclableBufferedInputStream 的 reset() 方法重置输入流对应的字节数组的位置。
ResourceDecoder 是一个接口,有非常多的实现类,比如网络图片对应的解码器为 StreamBitmapDecoder ,StreamBitmapDecoder 的 decode() 方法调用了降采样器 Downsampler 的 decode() 方法,下图是 Downsampler 的解码逻辑。
1. 设置目标宽高
除非我们通过 override() 方法把尺寸改为 Target.SIZE_ROGINAL ,否则 Glide 默认会把 ImageView 的大小作为加载图片的目标宽高。
2. 计算缩放后宽高
根据不同的变换选项计算缩放后宽高。
3. 创建空 Bitmap
根据计算后的目标宽高创建一个空的 Bitmap 。
4. 使用 BitmapFactory 解码
Downsampler 的解码方式用的是 ImageReader 的 decodeBitmap() 方法,而 ImageReader 又调用了 BitmapFactory 的 decodeStream() 方法,BitmapFactory 最终调用的是 SkImageDecoder 的 decode() 方法。
5. 把 Bitmap 放入 BitmapPool 中
在前面讲 Resource 的时候讲到了 BitmapResource 中有一个 BitmapPool,这个 BitmapPool 是由 Downsampler 传过去的,而 Downsampler 的 BitmapPool 是由 Glide 创建并传进来的。
2.3 Transformation
Transformation 是一个接口,它有一个 transform() 方法,这个方法是在 DecodeJob 中调用的,当 DecodeJob 发现数据源不是缓存中的 Resource 时,就会调用变换选项的 transform() 方法。
Transformation 的其中一个实现类是 BitmapTransformation,我们平时调用的 centerCrop() 就是 BitmapTransformation 的子类,centerCrop() 选项对应的是 CenterCrop 类,它实现了 Transformation 接口,具体的变换实现在 TransformationUtils 中。
- Matrix
以 centerCrop() 为例,TransformationUtils 的 centerCrop() 方法会先创建一个 Matrix 矩阵,然后根据传进来的 Bitmap 计算 Matrix 的缩放比例和平移坐标。 - drawBitmap()
配置好 Matrix 后,就会根据目标宽高创建一个空的目标 Bitmap ,然后把原始 Bitmap、目标 Bitmap 和 Matrix 传给 Canvas 的 drawBitmap() 方法,然后返回 Canvas 处理好的图片。
2.4 ResouceTranscoder
ResourceTranscoder 是一个接口,是 Glide 中的资源转码器,它有两个泛型参数 Z 和 R ,分别代表需要进行原始类型和转码目标类型。
比如 BitmapDrawableTranscoder 的原始类型是 Bitmap,转码目标类型是 BitmapDrawable,在BitmapDrawableTranscoder 的 transcode() 方法中,会把 Bitmap 转换为 BitmapDrawable ,以便 Target 进行处理。
2.5 ResourceEncoder
ResourceEncoder 是一个接口,是 Glide 中的资源编码器,ResourceEncoder 有好几个实现类,比如网络图片对应的编码器为 StreamEncoder。
在转码完成后,DecodeJob 会先把图片加载到 Target 中,然后用 ResourceEncoder 对图片进行编码,比如 StreamEncoder 的编码操作就是把输入流 InputStream 转化为图片文件,然后保存到本地。
2.6 图片加载相关问题