Glide 源码分析
前言
Glide 是一个快速、高效的 Android 图片加载库,它提供了易用的 API,高性能、可扩展的图片解码管道以及自动的资源池技术。
Glide 的缓存术语:
- Active 内存缓存:正在使用的图片对应的内存缓存
- Cache 内存缓存:不在使用的图片对应的内存缓存
- Data 磁盘缓存:原始数据对应的磁盘缓存
- Resource 磁盘缓存:解码后数据对应的磁盘缓存
文档目录
基本使用
1 | Glide.with(context).load(url).into(view) |
Glide 初始化
初始化
Glide 使用单例模式进行管理,所以第一次调用 Glide.with(context)
时,会触发 Glide 的初始化。
Glide 初始化的工作主要是:
GlideContext
,Engine
,Registry
等对象的创建- 向
Registry
中注册各种工具类:ModelLoaderRegistry
:注册的数据类型是ModelLoaderFactory
,可以用于构建ModelLoader
对象,用于加载图片数据EncodeRegistry
:注册的数据类型是Encoder
,用于对原始图片数据编码保存ResourceDecoderRegistry
:注册的数据类型是ResourceDecoder
,用于解码图片数据ResourceEncoderRegistry
:注册的数据类型是ResourceEncoder
,用于对解码后的数据编码保存ResourceTranscoderRegistry
:注册的数据类型是ResourceTranscoder
,用于对解码后的Resource
数据进行转码
请求管理
RequestManager
的职责:用于管理请求和启动请求,但是它并不直接管理Request
,而是交由RequestTracker
管理Request
的启动,取消,暂停等Request
的职责:为Target
加载Resource
,它定义了请求的开始、结束、状态获取等。
生命周期
Glide 通过 Glide.with(context)
中传入的 context
管理生命周期,主要原理是在 Context
对应的 Activity
中添加一个透明的 Fragment
- SupportRequestManagerFragment
Activity
生命周期变化 ->SupportRequestManagerFragment
生命周期变化 ->ActivityFragmentLifecycle
相应的方法 -> 遍历LifecycleListener
集合,回调对应的方法Glide.with
中会创建RequestManager
对象,RequestManager
实现了LifecycleListener
接口,在其构造方法中会添加到ActivityFragmentLifecycle
持有的LifecycleListener
集合中
生命周期无效的场景:
- 当 Glide.with 传入的是
Application
时,是没有生命周期管理的 - 当 Glide.with 调用线程是子线程时,也是没有生命周期管理的
- 当 Glide.with 传入的是
View
时,会先找到其所在的Activity
,如果找不到就会使用Application
作为参数,就会转变成场景 1
View 如果没有 attach,用 Glide 加载图片会怎么样?
- 如果请求的时候通过
override
设置了图片大小,那么即使 View 还没有 attach,此时依然会触发 Glide 请求图片的流程- 如果请求的时候没有设置图片大小,那么 Glide 会自己计算 View 的大小。当计算出合法的大小后,才会触发 Glide 请求图片的流程
关于 Glide 的初始化过程,代码分析部分可以参考:
Glide 加载
Glide 加载网络图片的步骤:
- 数据加载:根据 load 步骤传入的 model,使用
ModelLoader
接口加载对应的图片数据到内存中。如果磁盘缓存策略允许缓存原始数据,这里会使用Encoder
接口缓存原始数据到 Data 磁盘缓存。 - 解码:使用
ResourceDecoder
对图片数据进行解码,得到一个持有解码结果的Resource
数据。同时,这里可能会有变换和转码步骤。- 变换(
Transformation
):可以获取资源并修改,通常变换操作是用来完成剪裁或对位图应用过滤器,但它也可以用于转换 GIF 动画,甚至自定义的资源类型。Glide 内置了CenterCrop
、FitCenter
、CircleCenter
- 转码:使用
ResourceTranscoder
接口将一个Resource
类型转换为另一个Resource
类型,例如将BitmapResource
转化为LazyBitmapDrawableResource
- 变换(
- 显示:在主线程回调,先将结果保存到 Active 内存缓存中,再添加到对应的
Target
显示。同时如果磁盘缓存策略允许缓存解码后的图片,这里会使用ResourceEncoder
接口缓存解码后的数据到 Resource 磁盘缓存。
数据加载
Engine
Engine
:用于管理 Active 内存缓存、Cache 内存缓存和图片加载的启动EngineJob
:用于管理DecodeJob
运行的线程、添加/移除DecodeJob
执行结果的回调、通知DecodeJob
执行结果的回调DecodeJob
:解码磁盘缓存数据或者原始数据、执行变换和转码DataFetcherGenerator
有 3 个实现者,如下:ResourceCacheGenerator
:加载磁盘中解码后的图片数据DataCacheGenerator
:加载磁盘中原始图片数据SourceGenerator
:加载网络图片数据
- Engine#load 首先会去 ActiveResource 和 MemoryCache 两个内存缓存中加载数据
- 如果内存缓存加载失败,就会创建一个
EngineJob
,在特定线程运行DecodeJob
,用于获取磁盘/网络数据。DecodeJob
会在子线程中依次使用ResourceCacheGenerator
,DataCacheGenerator
,SourceGenerator
加载图片数据
关于 Engine#load 过程,详情可以参考:Engine::load 源码分析
LoadData
DataFetcherGenerator
会根据 load 过程中设置的 model 从Registry
中找到满足条件的ModelLoader
来构建LoadData
对象。ModelLoader
->LoadData
->DataFetcher
用于加载数据,并通过DataCallback
接口回调结果。- 以
SourceGenerator
为例,正常情况下会通过HttpUrlFetcher
加载网络数据,然后通过DataCallback
接口回调给SourceGenerator
,SourceGenerator
会通过DataFetcherGenerator.FetcherReadyCallback
接口回调给DecodeJob
。- 如果需要磁盘缓存,调用
SourceGenerator#cacheData
将原始数据写入到 Data 磁盘缓存 - 如果不需要磁盘缓存,直接解码展示
- 如果需要磁盘缓存,调用
关于 LoadData 的获取过程,详情可以参考:DecodeHelper::getLoadData 源码分析
关于 SourceGenerator 加载数据的过程,详情可以参考:SourceGenerator::startNextLoad 源码分析
解码
LoadPath
根据数据加载过程得到的数据的 dataClass
、RequestOptions
中的 resourceClass
, load 过程中设置的 model 的 transcodeClass
为参数,从 ResourceDecoderRegistry
和 ResourceTranscoderRegistry
中找到符合条件的 ResourceDecoder
和 ResourceTranscoder
,构建 LoadPath
对象用于解码。
关于 LoadPath 的获取,详情可以参考:DecodeHelper::getLoadPath 源码分析
解码过程:
- 调用符合条件的
ResourceDecoder#decode
方法解码,最终会调用到Downsampler#decode
方法。Downsampler
类的职责是:根据图像的 exif 方向,使用BitmapFactory
对图像进行下采样、解码和旋转
- 通过
DecodeCallback#onResourceDecoded
接口回调给DecodeJob
,用于进行Transformation
变换操作 - 调用符合条件的
ResourceTranscoder#transcode
方法转码
关于解码过程,详情可以参考:DecodeJob::decodeFromData 源码分析
关于解码过程中的优化,详情可以参考:Glide - 内存优化
显示
DecodeJob
完成图片数据加载、解码等工作后:
- 先通过
Decode.Callback
接口回调给EngineJob
EngineJob
通过EngineJobListener#onEngineJobComplete
接口回调Engine
,Engine
收到回调后,就会将图片数据放入 Active 内存缓存中。EngineJob
通过ResourceCallback
接口回调给SingleRequest
。SingleRequest
收到回调后,就会将图片设置到Target
中显示。
- 继续在当前子线程内,调用
DeferredEncodeManager#encode
方法进行编码,将解码后的图片数据写入到 Resource 磁盘缓存
Glide 缓存
内存缓存
Active 内存缓存
ActiveResources
是 Active 内存缓存的实现类,它是用来存储当前界面正在使用的图片。原理是 HashMap + 弱引用机制。
- 读取:
Engine#load
第一步就是从ActiveResource
s 中读取数据 - 写入:
DecodeJob
加载、解码等完成后,在显示到Targe
t 之前,会写入到 Active 内存缓存中Engine#load
第二步,从 Cache 内存缓存中读取成功时,会写入到 Active 内存缓存中
- 删除:
EngineResource
持有的 acquired 引用计数为 0 的时候- 内存不足,进行 GC 的时候
Active 内存缓存中的数据被 GC 回收了,如何及时写入到 LruResourceCache 中呢?
ActiveResource 启动了一个线程专门去扫描 ReferenceQueue,只要调用 ReferenceQueue 的 remove 方法能取到元素,就说明 EngineResource 被回收了,可以移动到 LruCache 里面了
GC 的时候为啥可以将持有的图片放入 Cache 内存缓存中呢?
通过在 WeakReference 中用一个强引用对象保存资源实现。可以参考下面的 demo,TestWeakReference 保存了 EngineResource 中的 Resource。
Cache 内存缓存
LruResourceCache
是 Cache 内存缓存的实现类,它是用来存储当前没有使用到的图片。原理是 LruCache,使用 LinkedHashMap
以强引用的方式存储图片数据,缓存满时会移除较早缓存的图片。
- 读取:Active 内存缓存没有命中时,就会从 Cache 内存缓存中读取数据
- 写入:Active 内存缓存数据被删的时候,就会写入到
LruResourceCache
中 - 删除:LRU 算法规则
为什么要设置两种内存缓存?
为了保护这些正在使用的图片不会被 LruCache 算法回收掉。
磁盘缓存
不管是 Resource 磁盘缓存还是 Data 磁盘缓存,都是使用 DiskLruCache
类实现,只不过两者存储的 Key 不同而已。
DiskLruCache 是一种使用有限数量的缓存空间来缓存文件的硬盘缓存,采用了 LRU 算法在一定的空间大小下来缓存经常使用到的文件
关于 DiskLruCache 的代码分析:DiskLruCache 源码分析
Resource 磁盘缓存
- 读取:两个内存缓存都没有命中时,就会读取
- 写入:图片解码并显示到
Target
后,会将其写入 Resource 磁盘缓存,使用的 key 是ResourceCacheKey
- 删除:LRU 算法规则
Data 磁盘缓存
- 读取:两个内存缓存和 Resource 磁盘缓存都没有命中时,就会读取
- 写入:原始图片加载完成后,会将其写入 Data 磁盘缓存,使用的 key 是
DataCacheKey
- 删除:LRU 算法规则
关于 Glide 缓存,代码分析部分参考:Glide - 三级缓存