Glide 是google 开源的 Android 端图片加载库,能够帮助我们下载、缓存、展示多种格式图片,使用简单,在 Gradle 添加依赖后,只需简单的几行代码便可实现图片的加载显示 ,使用如下:
1 | Glide.with(context).load(url).into(imageview) |
本次源码分析基于 Glide 4.8 版本
一、with(xxx)
顺着代码往下看,在Glide.java
类中,with
方法有多个重载,分别接收的入参有:Context、Activity、FragmentActivity、Fragment、View
分别如下:
1 |
|
在 with(xxx)
方法中,调用 getRetriever()
,获取 RequestManager
实例之前,会通过 Glide.java
的 get(context)
方法获取 Glide
实例 ,其中 Glide 实例化时,是通过 initializeGlide
函数实例化,并且会读取 AppGlideModule
和 AndroidMainfest.xml
的配置。
1 |
|
1.1、RequestManagerRetriever
RequestManagerRetriever 是创建 RequestManager
实例的静态方法集合,或者从现有的 Activity/Fragment 取出已经关联的 RequestManager
实例。
getRetriever(context)
方法获取的是 RequestManagerRetriever
实例,RequestManagerRetriever
实例是在initializeGlide
中通过 GlideBuilder.build
实例化,调用 getRetriever
方法返回的是已经实例化好了的对象。
RequestManagerRetriever 有好几个 get 方法,可以根据 Context、Fragment、Activity、view 等不同生命周期创建 RequestManagerFragment
,RequestManagerFragment 实现对各个组件生命周期的自动感知。接着创建 RequestManager
,并与 RequestManagerFragment 绑定。
其中getRetriever(context).get(context)
返回的是一个RequestManager
实例 ,它是在 RequestManagerRetriever
的get(context)
方法实例化。通过代码可以看出,Glide 的和Activity/Fragment 的生命周期监听是通过我们看不见的 SupportRequestManagerFragment
或者 RequestManagerFragment
进行监听的。然后通过一个 Lifecycle
来进行生命周期状态的改变通知,其实就是很关键的 ActivityFragmentLifecycle
或者 ApplicationLifecycle
。
1 |
|
从代码中可以很明显的发现,如果传入的 context
是ApplicationContext
, 使用的为ApplicationLifecycle
, 如果是 view / Activity / Fragment 使用的是 ActivityFragmentLifecycle
1 |
|
1 |
|
有代码可知,RequestManagerRetriever 创建 RequestManager 的流程大致如下:
如果在子线程中,则创建一个生命周期与Application 一样的 RequestManager
根据 Context 或者 View 拿到其所属的 fragment 或者 Activity
从当前 Fragment、Activity 取出 RequestManagerManagerFragment,如果 RequestManagerFragment 不为空则取出关联的 RequestManager;如果为空,则创建一个新的 RequestManagerFragment ,顺便创建一个新的 RequestManager 并将二者关联起来。
问题1:为什么要使用 **pendingRequestManagerFragments ** 来添加 RequestManagerFragment?在 fm 添加后又通过 handler 发送消息将 RequestManagerFragment 重 pendingRequestManagerFragments 中移除?
回答:因为 fm 添加 RequestManagerFragment 不是 add 方法调用就之后就完成了,它有相关生命周期是异步进行的,如果在 add 之后又在相同的 activity 或 fragment 环境中调用get 方法,那么就很有可能有创建一个新的 RequestManagerFragment,而 pendingRequestManagerFragments 就是为了防止重新创建 RequestManagerFragment 而准备的。
问题2:Glide 的get 使用有什么优化的地方?
尽量不在子线程中调用,如果在子线程中调用,意味着生命周期是全局的,不能跟activity 或 fragment 声明周期同步。get的时候最好使用 activity 或 fragment ,这样可以减少查找具体 fragment 的或者 activity 的步骤。
1.2、RequestManagerFragment、SupportRequestManagerFragment
这两个类都是没有界面的 Fragment,用于安全地保存所属Activity/Fragment的 RequstManager,RequestManager可以用于启动、暂停和管理Glide。
当我们在使用 Glide.with(xxx)时,传入的 context 时,通过RequestManagerRetriever
这个桥梁与Activity/Fragment进行绑定,其中会判断是否是Activity、Fragment、View、或者 Application类型的 Context,然后把 RequestManagerFragment、SupportRequestManagerFragment 实例化并附加到相应的Activity/Fragment。
在 RequestManagerFragment 内部,实例化了 ActivityFragmentLifecycle ,通过它,可以把生命周期事件传递给 RequestManager
或者ConnectivityMonitor
。
1.3、Lifecycle、LifecycleListener
Lifecycle 和 LifecycleListener 作为一个监听 Activity/Fragment Lifecycle 的接口,其中 Lifecycle 的实现类有 ApplicationLifecycle、ActivityFragmentLifecycle
Lifecycle 有两个接口方法,分别负责把入参中给定的 LifecycleListener 添加到一个 listener 集合中或者从集合中移除。
从 ActivityFragmentLifecycle
的源码可以看到,其内部有onStart()、onStop、onDestroy()
等几个成员方法,与上面提到的 RequestManagerFragment、SupportRequestManagerFragment
对应的方法关联,当Fragment 的生命周期方法被调用时,ActivityFragmentLifecycle
相应的方法也就会被调用,从而遍历其内部的 LifecycleListener
集合通知到具体的实例,最后实现Glide图片的加载、停止或者销毁与Activity/Fragment 的生命周期同步。
ActivityFragmentLifecycle 内部维持了一个监听者列表,当 RequestManagerFragment 生命周期变化时,通过遍历列表,通知监听者对应的方法。
监听者在什么时候被添加到 ActivityFragmentLifecycle 的监听者列表呢?
答:是在创建 RequestManager 的时候
1 | RequestManager( |
二、load(xxx).into(imageview)
2.1、RequestManager
这是一个负责管理和启动 Glide 请求的类,可以使用 Acitivity/Fragment 和 connectivity 生命周期事件去智能的开始、停止和重启请求。
我们常用的 load 方法就是在这个类中,load() 有多个重载方法。入参可以是Bitmap、Drawable、url、本地uri、File、assert资源id
等等。这些方法中都调用了asDrawable()
方法,返回RequestBuilder
对象,然后调用 RequestBuilder
的load(xx)
方法。
接下来将重点关注 asDrawable()、as()
方法
1 | /** |
通过代码可知,在 as(resourceClass)
方法中,实例化 RequestBuilder
2.2、RequestBuilder
RequestBuilder 作为一个泛型类,可以处理泛型资源类型的设置选项和启动加载。
在RequestBuilder 的一系列 load()
重载方法中,最终调用的方法为 loadGeneric
、apply
下面选取 load(String string)
来看
1 |
|
从代码看,loadGeneric
方法是作为加载图片前的准备, 把 load
入参赋值给一个成员变量 model, 并设置 isModelSet = true, 表示入参源数据已经赋值给 model
1 |
|
into(xxx)
有三个重载方法,上面只贴出关键的两个重载方法。在RequestOptions
设置选项上首先是先拷贝一个备份,然后在备份的 RequestOptions 上设置新的设置选项。
GlideContext.buildImageViewTarget
调用 ImageViewTargetFactory.buildTarget
可以根据不同图片类型,分别对应的实现 DrawableImageViewTarget
、BitmapImageViewTarget
实例,它们都继承于泛型类ImageViewTarget<Z>
,间接继承于 ViewTarget<T extends View, Z>
,实现了Target<R>、LifecycleListener
接口, 用于在 Request 加载完成图片后接受并且把图片展示到 ImageView。
在 ViewTarget 中比较重要的地方是,其内部类SizeDeterminerLayoutListener实现了 ViewTreeObserver.OnPreDrawListener 接口,用于监听 ImageView 在屏幕上绘制之前执行一下操作,调用的方法有:
SizeDeterminer.checkCurrentDimens() -> notifyCbs() -> SingleRequest.onSizeReady()
最后 在onSizeReady 方法中会触发engin.load 去加载图片。
关于 onSizeReady 方法,在后面会说到。
在构建完 Request 后,会先取消之前的任务资源,然后在把request 绑定到 target 上。最后调用 request.tartk
方法, 把 target、request 添加到追踪队列。
1 | public final class TargetTracker implements LifecycleListener { |
targets 保存了当前 activity/fragment 的所有 target, 同时和相应的生命周期做了绑定。
在RequestTracker
中,runRequest
方法调用了 SingleRequest.begin
执行了最终的图片资源加载操作。
2.3、SingleRequest
SingleRequest 一个加载图片资源到给定 target 的请求,起到图片资源加载 engin 和 target 的桥梁作用。
2.3.1、begin
在 begin
方法中,会先判断 model ,然后判断 status 状态。如果model 为空或者 status 为 RUNNING 状态都会抛出异常信息,提示错误。
2.3.2、onSizeReady
onSizeReady 的调用是在 ImageView 绘制到屏幕之前执行。具体在 ViewTarget.notifyCbs()
方法中执行。
在获取到view 的宽高后,调用 Engine.load
方法执行图片资源加载。
2.4、Engine 图片加载引擎
Engine 在 GlideBuilder 的build 方法中实例化,然后在 GlideContext 实例化时,保存在 GlideContext 的内部私有成员变量中,而Glide 单例持有 GlideContext 引用。
Engin 是Glide 的图片加载引擎,主要承担图片的加载工作,是 Glide 中非常重要的一个类
Engine 类实现了一下几个接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 EngineJobListener{
// 加载任务回调
void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource);
void onEngineJobCancelled(EngineJob<?> engineJob, Key key);
}
interface MemoryCache.ResourceRemovedListener{
// bitmap 位图从缓存中移除回调
void onResourceRemoved(; Resource<?> removed)
}
interface EngineResource.ResourceListener{
// 资源释放回调方法
void onResourceReleased(Key key, EngineResource<?> resource);
}
Engine 执行图片加载主要是 load
方法,在 load 方法中,会通过 EngineKeyFactory.build
方法创建一个图片资源标识符 EngineKey
实例 key,然后通过这个key 去活跃的内存缓存 ActiveResources
中查找,如果查找到则返回 Resouce,否则继续从 LruResourceCache
最近最少使用内存缓存中查找。如果查找不到则再创建并启用新的 EnginJob 和解码任务 DecodeJob。
在Glide 中,缓存分为内存缓存和磁盘缓存
内存缓存 又分为以下两级:
- 活跃的内存缓存对应 ActiveResources
- 最近最少使用缓存 LruResourceCache
磁盘缓存 分为以下两级:
- 处理过的图片数据缓存,对应缓存处理类为 ResourceCacheGenerator
- 未处理过的源图片数据缓存,对应的处理类为: DataCacheGenerator
2.4.1、DecodeJob
DecodeJob 主要负责从缓存数据或者原始源数据资源解码,并且应用转换和转码。
在 DecodeJob 中,其主要方法为 runWrapped()
,runWrapped 会根据以下三个理由来执行解码任务。
- INITIALIZE :首次执行初始状态,将会尝试依次从内存缓存->磁盘缓存->原始数据源来获取要加载的图片数据
- SWITCH_TO_SOURCE_SERVICE:当从缓存或磁盘中获取不到数据时,从缓存服务中切换到原始数据源中执行获取数据
- DECODE_DATA:获取图片数据和 DecodeJob 不在同一个线程时,切换回 DecodeJob 线程码数据。调用 decodeFromRetrievedData() 方法来对检索到的数据进行解码
在 getNextState() 方法中,根据当前的解码步骤来判断下一步应该执行的步骤,总共分为 6 个阶段,这两个阶段是 State 枚举类中的值,分别如下:
INITIALIZE :
初始化,默认状态。
最开始解码处于这个状态,然后 DecodeJob 会根据缓存策略,判断是否要从磁盘中获取处理过图片数据缓存资源,如果是,则 生成
ResourceCacheGenerator
实例获取图片资源。如果获取到数据,则进行解码。如果获取不到处理过的图片数据资源,那么就切换到下一个解码阶段 RESOURCE_CACHE。RESOURCE_CACHE:
从磁盘缓存中获取处理过的图片,其对应的缓存资源Generator 为
ResourceCacheGenera
。当解码处于当前阶段时, DecodeJob 会根据缓存策略判断是否要从磁盘中获取 未处理过的图片数据,如果是的话就用 DataCacheGenerator获取数据。
DATA_CACHE:
从缓存中获取原始(未处理过的)数据。
当处于该阶段时,如果在加载图片时调用了 onlyRetrieveFromCache(true) ,那么就不会切换到 SOURCE 阶段从来源获取数据,而直接切换的 FINISHED 阶段结束解码流程。
SOURCE:
从图片来源获取原始数据。
如果 DecodeJob 在 RESOURCE_CACHE 和 DATA_CACHE 阶段都没有拿到图片数据,那就会用 SourceGenerator 从图片来源获取图片数据。
ENCODE :
编码
当磁盘缓存策略设定了要对图片资源进行缓存时,那么在获取到数据后,DecodeJob 就会用 ResourceDecoder 对资源进行编码,也就是把图片放到磁盘缓存中。
FINISHED : 结束
在DecodeJob 中调用 getNextGenerator() 方法,在不同的阶段,根据缓存策略,分别创建和使用不同的数据生成器:
- ResourceCacheGenerator : 缓存数据生成器
- DataCacheGenerator : 原始缓存数据生成器
- SourceGenerator: 来源数据生成器
这三种数据生成器都实现了 DataFetcherGenerator 接口,同时也实现 DataFetcher.DataCallback 接口
DecodeJob 依赖于 DataFetcherGenerator接口,通过 DataFetcherGenerator.FetcherReadyCallback 回调接口来获得数据加载。
DataFetcherGenerator 会通过 ModelLoader 构建数据封装对象 LoadData ,然后通过 LoadData 中的 DataFetcher 来加载数据,此外数据生成器也实现了 DataFetcher.DataCallback 接口,用于接收 DataFetcher 的数据回调。
LoadData 是 ModelLoader 的内部类,拥有 数据来源标识符 Key 和 数据加载器 DataFetcher 两个成员变量。
SourceGenerator 逻辑流程图如下
步骤分析:
是否获取到了需要缓存的数据
当 SourceGenerator 加载完数据后,会再次进入 startNext() 方法,这时就获取到了需要缓存的数据。
是否保存原始数据
如果磁盘缓存策略设定了要保存图片的原始数据,就用数据提取器加载数据,否则就直接把图片加载给 Target 。
加载数据
当需要保存原始数据或数据有加载路径时,SourceGenerator 就会根据 Model 的类型,使用对应的 DataFetcher 来提取数据,比如从网络上下载图
是否保存原始数据
当 SourceGenerator 获取到数据后,会再次判断是否要保存原始数据,否则就直接把图片加载给 Target 。
编码
当 SourceGenerator 从 DataFetcher 中拿到数据后,会再走一遍 startNext() 方法,然后用编码器 Encoder 对数据进行编码,也就是把图片放到磁盘缓存中。
从磁盘中获取数据
当 SourceGenerator 把数据保存到磁盘后,不会直接加载图片,而是从磁盘中拿这张图片,然后再进行加载。
2.4.2、DecodeJob.onResourceDecoded()
在获取到图片资源 Resource 后,最终会调用 DecodeJob.onResourceDecoded() 方法,然后应用变换选项和初始化编码管理器。
应用变换选项
对于已经处理过变换的图片不再进行变换,否则就对图片进行变换操作
回收图片资源
当对资源应用了变换之后,DecodeJob 会把原来的资源回收掉,因为原来的图片资源用不上。
缓存变换后的图片资源
根据磁盘缓存策略判断是否要对资源进行编码,如果要编码,则根据不同的编码策略创建不同的Key。Glide 有 SOURCE 和 TRANSFORMED 两种编码策略,分别对应 原始资源编码 和 变换后资源编码。
SOURCE : GIF 编码器 对应GifDrawableEncoder 中用的编码策略
TRANSFORMED : 位图编码器 BitmapEncoder 中用的编码策略
初始化编码管理器
创建好 Key 后不会直接对图片进行编码,而是会修改编码管理器的 Key ,等到转码完成后再用 ResourceEncoder 进行编码。