Glide 三部曲之图片加载流程
- 本文章所使用的 Glide 源码版本:4.11.0
上一篇:Glide 三部曲之请求生命周期管控
开胃菜
- 上一篇我们讲到了 Glide 的生命周期管控是由 with 方法完成,那么加载过程是不是由 load 方法完成的呢?
- 接下来让我们把目光放到 String 参数的 load 方法上
- 再来看看 File 参数的 load 方法
-
经过这两段代码的对比,我们基本可以断定 Glide.load 做的事情很简单,只是对加载的数据源进行封装,并没有进行加载操作
-
其实真正的加载操作是放在 Glide.into 方法,刚刚只是开胃菜,接下来正式进入今天的主题:Glide.into
源码解析
- 看到这里,我们可能对 buildImageViewTarget 方法很好奇,为什么不直接传入 ImageView,而是通过一个 Target 类把 ImageView 包起来了呢,Glide 为什么要这么做?接下来让我们回归 into 方法的源码
-
看到这里我们基本可以猜到使用 Target 的主要作用,它是将网络请求和 ImageView 绑定在一起的那么一个类,如果这个 ImageView 已经有图片加载请求了,那么会先判断现在要进行的图片加载请求是否和上一次的图片加载请求是否是同一个,如果是的话,则复用之前的 Target 对象,如果不是的话则会将上一次的图片加载请求移除掉,再把新的图片加载请求设置给 Target 对象,然后交给 RequestManager 执行该请求
-
那么问题来了,Target 对象是如何和 ImageView 对象绑定在一起的,还有 Glide 为什么要这么做?带着这两个问题我们接着看源码
-
看完了这些代码,我们可以得出一个结论,图片加载请求是通过 ImageView.setTag 和 ImageView 绑定在一起的
-
那 Glide 为什么要这么做呢?又或者说这样做的目的是什么?我们需要考虑一种情况,如果我们向同一个 ImageView 对象同时发送两次图片加载请求,那么哪一次会先加载完成呢?这个真的说不准,有可能是第一次,也有可能是第二次,因为图片加载请求是异步的,我们无法准确预料这一时机,在这两次请求中,如果我们不对原有的请求进行解绑,那么很可能导致图片显示错乱的情况发生,也就是我们希望先加载第一次请求的图片资源再加载第二次请求的图片资源,但是实际情况可能相反,如果在第一张图片资源的大小比第二张图片资源要大的时候,那么这种非正常情况就会大概率会发生,这种情况在 RecyclerView 和 ListView 复用中很常见,所以 Glide 采用了 ImageView 和请求相互绑定的方式来解决这一问题。
-
讲完了这块的内容,我们接着讲 Glide 执行图片加载请求的过程
- 一路跟踪源码到了这里,我们可以看到,Glide 会判断我们有没有指定图片的宽高参数,如果没有指定的话它会对 ImageView 的宽高进行计算
- 看到这里,我们先回顾一下刚刚看过的一段源码
-
我们可以看到,如果我们没有指定图片的宽高,target.getSize 方法最终也会回调到 onSizeReady 方法上,只不过它多了一个对 ImageView 宽高的计算,所以绕了一圈还是回到原来的地方上了
-
接下来让我们继续追踪源码,看看它还干了什么事
-
通过查看这段代码,Glide 会先读取内存中的缓存,如果已经存在缓存则会直接复用,如果没有的话则会进行加载。
-
接下来让我们简单看看 Glide 对内存缓存是怎么做处理的
-
通过分析这段源码,我们可以得出,Glide 会判断有没有指定跳过内存缓存,如果是的话直接返回 null,也就是要从磁盘或者网络上获取,如果不是的话,Glide 会从内存缓存中取,这里有一个地方需要我们注意,Glide 的内存缓存有两种,一种是正在使用的内存缓存,也就是显示在 ImageView 上面的图片资源,另一种是没有在 ImageView 上面显示但是之前有加载过的图片资源
-
我们先来看看一级内存缓存的源码
-
经过查看源码可以得出,一级内存缓存其实就是一个弱引用图片资源的集合
-
接下来让我们看看二级内存缓存的源码
-
Glide 会从二级内存缓存中读取图片资源,如果这个资源不为空,会将这个图片资源从二级内存缓存中移除,然后转移到一级内存缓存中
-
然后再来看看它是怎么实现二级内存缓存的
-
在这里我们可以看到,二级内存缓存无非还是使用了 LruCache 来实现,值得一提的是这个 LruCache 类是 Glide 自定义的,可能和谷歌实现不太一样,但是其中的思想都是一样的,都是在内存缓存达到了设定的最大值,会将使用次数最少的缓存对象移除掉。
-
说完了内存缓存,接下来再讲讲在没有内存缓存的情况下 Glide 会怎么做
- 在这里我们看到了两个类,一个是 EngineJob,另一个是 DecodeJob,那么问题来了,这两个类是什么?有什么作用?
- 从这两个类的注释我们可以看出这两个类的作用,EngineJob 负责管理加载回调的,而 DecodeJob 负责图片资源加载、解码、转换,所以这里我们重点讲一下 DecodeJob 类
-
简单讲一下这三个方法的作用,getNextStage 方法会判断图片的缓存策略返回对应的枚举(缓存原图、缓存 ImageView 大小图片,没有缓存),然后 getNextGenerator 方法再根据不同的缓存策略返回不同的处理策略(读取原图,读取 ImageView 大小图片,从网络上读取),runGenerators 方法则是执行读取操作
-
然后我们接着看它读取完图片资源之后做了什么事
-
通过追踪源码得出结论,Glide 在没有内存缓存的情况下,会读取磁盘上或者网络上的缓存,读取完毕之后不会立即加载到 ImageView,而是先加入一级内存缓存中再进行加载
-
接下来再看看它是怎么加载给 ImageView 对象的,我们先回退几步,然后接着追踪源码
- 经过对源码的追踪,最终还是回到我们最初的 Target 类中,最终根据我们设定的类型给 ImageView 加载图片资源
- Glide 整套加载流程的源码是极其复杂的,类和类之间的关系也是非常复杂,看着看着容易迷路
总结
- Glide 加载图片之前它会先设置一个 Target 对象将图片加载请求和 ImageView 绑定在一起,由此保证一个 ImageView 只有一个图片加载请求并且是最新的,然后获取图片显示在 ImageView 上面的宽高,如果我们没有指定图片的宽高,则会对 ImageView 的宽高进行计算,接着会判断内存缓存中是否已经有图片缓存,它会从两个地方获取,先从正在显示的图片集合中查找,如果没有再从缓存池中查找,这两种方式分别是一级内存缓存和二级内存缓存,两者的区别是,一级内存缓存使用的是一个弱引用的集合来存储,而二级内存缓存使用的是 LruCache 算法来存储。如果内存缓存中没有,会判断图片资源是否有本地缓存,如果有的话直接从磁盘上面读取,如果没有的话会先从网络下载到磁盘上再进行读取,读取完资源后并不会立即加载到 ImageView 上,而是先把图片加载到一级内存缓存中,再通过 Target 类加载到绑定的 ImageView 上面。