Glide 内存缓存:ActiveResources 与 LruResourceCache
在 Glide 的图片加载流程中,内存缓存并不是只有一个简单的 LruCache。Glide 会把内存中的资源分成两类:
正在使用的资源:ActiveResources
最近用过但当前没人使用的资源:LruResourceCache
所以 Glide 查询缓存的大致顺序是:
ActiveResources
↓
LruResourceCache / MemoryCache
↓
Resource Disk Cache
↓
Data Disk Cache
↓
原始数据源
也就是说,Glide 会优先判断这张图片是不是已经在某个 ImageView 上显示;如果没有,再去内存 LRU 缓存里找。
一、ActiveResources 是什么?
ActiveResources 可以理解为:
当前正在被页面使用的图片资源集合。
比如:
Glide.with(context)
.load(url)
.into(imageView)
当图片加载完成,并且正在 ImageView 上显示时,这个资源就属于 active resource。
它的核心特点是:
简单来说:
ActiveResources = 正在显示中的图片
它更像是一个“正在使用资源登记表”,而不是传统意义上的缓存。
二、LruResourceCache 是什么?
LruResourceCache 是 Glide 内存缓存中的 LRU 缓存实现。
它可以理解为:
最近使用过,但当前没有被页面使用的图片资源缓存。
比如一个页面显示过某张图片,后来这个页面退出了,或者 ImageView 被复用了,这张图片不再被任何地方显示。
如果这张图片允许被内存缓存,Glide 就会把它放入 LruResourceCache,方便下次快速复用。
它的特点是:
简单来说:
LruResourceCache = 最近用过、当前空闲、可以复用的图片
三、为什么有了 LruResourceCache,还需要 ActiveResources?
核心原因是:
LruResourceCache管的是“空闲资源”,ActiveResources管的是“正在使用的资源”。
它们解决的问题不一样。
四、问题一:正在显示的图片不能被 LRU 淘汰
假设 Glide 只有 LruResourceCache,那么正在显示的图片也放在 LRU 缓存里。
这时可能会出现这种情况:
ImageView 正在显示 Bitmap
↓
Bitmap 同时也在 LruResourceCache 中
↓
内存紧张,LRU 开始淘汰资源
↓
正在显示的 Bitmap 被回收
↓
ImageView 继续使用这个 Bitmap
↓
可能出现显示异常,甚至 recycled bitmap 相关错误
这显然是不安全的。
所以 Glide 把正在使用的资源从 LRU 缓存中独立出来,放到 ActiveResources 中管理。
这样就可以保证:
正在显示的图片不会被 LRU 策略误删
五、问题二:LRU 缓存只应该管理“没人用的资源”
LRU 的核心思想是:
缓存空间有限,最近最少使用的资源可以被淘汰
但是 active resource 不是“可以淘汰的缓存”,而是“业务正在使用的资源”。
这两者的语义完全不同。
可以用一个生活中的例子理解:
ActiveResources = 正在桌上吃的菜
LruResourceCache = 冰箱里的剩菜
冰箱里的剩菜可以清理,但是别人正在吃的菜不能直接拿走。
同理:
LruResourceCache 可以淘汰
ActiveResources 不能随便淘汰
六、问题三:同一张图片正在显示时,可以直接复用
假设两个 ImageView 同时加载同一张图片:
Glide.with(context)
.load(url)
.into(imageView1)
Glide.with(context)
.load(url)
.into(imageView2)
如果 imageView1 已经显示了这张图片,那么 imageView2 再加载同一个 URL 时,Glide 会优先从 ActiveResources 中查找。
如果命中,Glide 就可以直接复用这份正在使用的资源,而不需要:
重新解码
重新从内存 LRU 中取
重新从磁盘读取
重新请求网络
这可以减少重复加载,提高性能。
七、ActiveResources 和 LruResourceCache 的流转过程
一张图片在内存中的生命周期大致如下:
图片加载完成
↓
放入 ActiveResources
↓
ImageView 正在显示
↓
页面销毁 / View 被复用 / Request 被 clear
↓
资源 release
↓
如果允许内存缓存
↓
放入 LruResourceCache
↓
下次再次加载相同图片
↓
从 LruResourceCache 中取出
↓
重新放回 ActiveResources
可以画成这样:
加载完成
↓
ActiveResources
↓ release
LruResourceCache
↓ 再次被使用
ActiveResources
也就是说:
ActiveResources 和 LruResourceCache 不是互相替代关系
而是资源在不同状态下的两个管理位置
八、Glide 内部的大致查询逻辑
Glide 加载图片时,内存查询逻辑可以简化为:
先查 ActiveResources
如果命中,直接返回正在使用的资源
否则查 LruResourceCache
如果命中,把资源从 LRU 缓存中取出
然后重新放入 ActiveResources
否则继续查磁盘缓存或原始数据源
伪代码可以这样理解:
fun loadFromMemory(key: Key): Resource? {
// 1. 先查正在使用的资源
val active = activeResources[key]
if (active != null) {
active.acquire()
return active
}
// 2. 再查 LRU 内存缓存
val cached = memoryCache.remove(key)
if (cached != null) {
cached.acquire()
activeResources.activate(key, cached)
return cached
}
// 3. 内存没有命中,继续走磁盘或网络
return null
}
注意这里有一个关键点:
从 LruResourceCache 命中的资源,会重新变成 active resource
因为它又被页面使用了。
九、release 之后资源去哪?
当 ImageView 不再使用这张图片时,比如:
Glide.with(context).clear(imageView)
或者页面生命周期结束,Glide 会释放对应的资源。
释放之后大致有两种情况:
如果资源允许内存缓存
→ 放入 LruResourceCache
如果资源不允许内存缓存
→ 回收资源
可以理解为:
正在用的时候:ActiveResources
不用但还能复用:LruResourceCache
不能复用:回收
十、ActiveResources 和 LruResourceCache 的区别
十一、为什么不直接都放进 LruResourceCache?
因为这样会混淆两种资源状态:
正在使用的资源
空闲等待复用的资源
如果全部放到 LRU 中,会带来两个问题:
第一,正在显示的图片可能被 LRU 淘汰,导致显示异常。
第二,LRU 缓存中既有正在使用的资源,又有空闲资源,缓存淘汰逻辑会变得不清晰。
所以 Glide 选择把它们拆开:
ActiveResources:负责正在使用
LruResourceCache:负责空闲复用
这样职责更清楚,也更安全。
十二、总结
ActiveResources 和 LruResourceCache 都属于 Glide 的内存缓存体系,但它们不是同一个东西。
一句话总结:
ActiveResources 管正在使用的图片;
LruResourceCache 管暂时不用但可能马上复用的图片。
再简单一点:
ActiveResources = 使用中
LruResourceCache = 待复用
DiskCache = 持久复用
Source = 重新加载
所以 Glide 之所以需要 ActiveResources,并不是因为 LruResourceCache 不够用,而是因为:
正在使用的资源不能按照 LRU 规则随便淘汰
这就是 Glide 内存缓存设计中 ActiveResources 存在的核心意义。
Glide 内存缓存:ActiveResources 与 LruResourceCache
https://lautung.com/archives/V2sMv9dP