阅读 Glide 源码的一些浅薄认识

Glide 里一句简单的 Glide.with(this).load(url).into(imageview) 背后其实有着非常复杂的逻辑和工作,这次正好碰巧有郭霖的博客在前方带路,我也顺便来看看 Glide 的源码实现。

在这篇文章里我不打算贴上大段的代码,我不会去仔细分析某段代码的作用,因为我自己或许都不太清楚,这篇文章我打算讲讲 Glide 实现的大体思路,通过这篇文章,你可以很快地知道 Glide 的大体架构,而不会深陷于代码的泥潭(主要还是源码实在太多太复杂了-_-||)。

首先说说 with() 方法,这个方法可以传入任何类型的 Context,Activity,Fragment,虽说是传进去了上下文的引用,但 Glide 并不持有这个引用,所以并不会造成内存泄露。这是为什么呢?我们知道,无论是 Activity 还是 Fragment,里面是可以继续持有 Fragment 的,而 Glide 很聪明,它向传进来的 Activity、Fragment 里面再添加一个 Fragment,因为 Fragment 与持有它的类的生命周期是绑定的,当外部 Activity、Fragment 的生命周期发生变化的时候,Glide 通过它添加的 Fragment 就可以感知到,从而及时停止或取消图片的加载,所以不会造成内存泄露。而如果你传入的是一个 Application 类型的 Context,那么这个图片加载的生命周期就会和 Application 保持一致,所以如果不是一个一直存在的图片,那么最好不要传入 Application 类型的 Context,因为过于长的生命周期会增加 App 同一时间内的负荷,占用更多的资源。总的来说 with() 方法就是用来预设这个图片加载的生命周期的。

然后就是 load() 方法:这个方法支持传入很多类型的资源,无论是 url、uri,还是 File,资源 ID,甚至是 byte 数组。在调用这个方法之后可以继续调用 asBitmap() 或者 asGif() 来设置图片的类型,这两个类型一动一静,始终贯穿在整个 load 过程中,如果不调用这两个方法,那么 load 方法就会自动识别,而一旦设定了具体的图片的类型,那么如果最后得到的图片与你的预设不相同,就会抛出异常。除了设置图片的类型,还可以设置图片的占位符(placeHolder)、错误提示图片(error),在 load 加载的过程中,会先后检查这两个变量的内容,在开始加载之前提供给 into 方法的是 placeHolder 图像,在加载出错的时候提供给 into 的是 error 图像。如果你还设置了变形、缩放、动画等,在 load 过程中其实并不会有什么特殊的处理,因为这些参数和设置只会在 into 过程中被真正的使用,load 过程就像烹饪之前的准备过程,知道我们要做什么菜,为了做这个菜需要哪些食材、需要什么调料、要做成什么口味等,都算是一个准备过程,并不会有具体的加工过程。load 过程中设置的所有参数最后一股脑地抛给 into 过程,由 into 过程来进行具体的解析、加载、加工、裁剪、转换等工作。

既然这样,那么最重要的东西都会在 into 过程中了。按照我的理解,我把这个过程分成以下几个步骤:

  • 构建:我们在 load 过程里设置了那么多的参数,图片类型,图片缩放、动画、翻转、占位符、错误提示等,都会在这里被调用,用这些参数构建出一个完整的 Glide 请求,是的,构建的结果是一个请求,什么请求呢,就是将某个图片加载出来的请求。
  • 解析:请求其实就是一个具体的目标,有了目标,就能够大步向前了。而解析的过程就是执行这个请求,在执行的一开始,就会在 imageview 上显示占位符,而如果执行出错就会显示预设的错误提示图片。在具体的执行过程中,根据不同的资源类型,会执行不同的图片加载过程,比如一开始传进去的是一个 url ,这里就会进行网络请求,获取结果流(inputStream),然后对这个流进行一系列的解码和封装。在这里还会判断图片是静态图还是动态图,分别有不同的解码过程。通过对流的解码会得到具体的 bitmap 对象,这个时候,请求里的其他参数就派上用场了,是否需要缩放,是否要旋转,是否设置动画,都会在这里得到具体的实现。
  • 包装:到这个时候,一个成熟的 bitmap /Gif 就做出来了,虽然这个图片可能就是我们需要的图片,但是还不能直接就显示出来。因为加载出来图片可能是动态地,也可能是静态的,这两种图片的设置的方法可不太一样。所以就需要我前面说到的封装了,而且是一系列的封装,以适应通用的设置图片的过程。
  • 转码:现在到我们手里的就是一个被包装了好几层的图片,而包装类并不直接或间接的继承于可以直接显示的 Drawable 和 Bitmap,那么还怎么显示呢?那就需要转码了。转码的作用的是从包装类中取出里面的 Drawable 或者 Bitmap 对象。那么我们不禁要问了,这又是包装的,又是转码的,最后不还是要用 Drawable 和 Bitmap 对象么?那么包装和转码到底有什么意义呢?问得好,因为 Glide 把你要显示图片的 imageView 也进行了一些封装,用来适应多种不同格式的图片,而为了适应封装过后的 ImageView,就得对图片进行转码。
  • 显示:这个时候就真的可以调用 setImageDrawable 或者 SetImageBitmap 将图片显示出来了,如果图片是 gif,就会不间断的播放 gif 的每一帧。

以上就是一个基本的 Glide 加载图片的完整流程。在这个流程里,完全没有提到图片缓存,就只是一个新图片第一次加载的过程。在郭霖博客的帮助下,我也花了差不多一天的时间来走完这一个流程,不得不说 Glide 的源码真的非常庞杂,随着方法的一层层深入,各种接口的实现,一不小心就会迷失在代码的海洋里,而忘记了一开始追溯到了哪一步。就像 Glide 文档说的,它背后默默完成了成吨的工作量。而阅读源码,不能过于深入,最好不要纠结于具体某一行代码的作用或逻辑,就像郭霖说的,抽丝剥茧,点到为止。先认准一个功能点,围绕这个功能点的流程具体是如何执行和实现的来展开调查,一点一点地进行渗透,最后慢慢的整个框架的思路和架构就会出现在你的脑海里。

希望我这篇很短的小文章对你理解 Glide 有一点点的帮助,如果文中描述有什么不对的,热切期望你能提出来,共同进步。要查看郭霖的更详细的解析,请移步 郭霖的博客