前言
Glide 提供有内存及硬盘缓存:
内存缓存又分为 active resources 与常见的 lru 缓存。之所以这么分,是为了 bitmap 的复用。一般 bitmap 复用是通过 BitmapFactory.Options.inBitmap 指向要复用的 bitmap。但使用 inBitmap 有一个前提:被复用的 bitmap 不能正在被使用。因此,使用 active resource 引用正在被使用的 bitmap,而 lru 缓存当前未被使用的。
ActiveResources
内存缓存的第一级,使用
弱引用记录正在被使用的 resources
它的使用入口在 Engine#loadFromActiveResources()。Engine 类主要负责加载图片,它的 load() 方法是整个图片加载的入口。
虽然上面说是缓存的 bitmap,但实际上 glide 缓存的是 Resources 对象(glide 自己内部类,不是系统的 Resources),而 ActiveResources 缓存的是 Resources 的子类 EngineResource
它的使用主要是下面两个方法
synchronized void activate(Key key, EngineResource<?> resource) {
// 建立 EngineResource 的弱引用
ResourceWeakReference toPut =
new ResourceWeakReference(
key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);
// activeEngineResources 就是一 Map 对象
// 将 key 与弱引用建立映射
ResourceWeakReference removed = activeEngineResources.put(key, toPut);
if (removed != null) {
removed.reset();
}
}
synchronized void deactivate(Key key) {
ResourceWeakReference removed = activeEngineResources.remove(key);
if (removed != null) {
removed.reset();
}
}
ResourceWeakReference 内部会强引用 EngineResource 包装的真正 Resources。所以上面两个方法最后都调用了 reset(),就是为了清空对 Resources 的强引用。而 ResourceWeakReference 本身又是对 EngineResource 的弱引用,所以它并不会对 EngineResource 回收产生影响。
当 EngineResource 被回收时,说明没有 View 指向它。但它封装的 resources 还可以回收利用,所以不能随着 EngineResource 一起被回收。这就是 ResourceWeakReference 即是弱引用,又使用强引用的原因。
从代码上看,ActiveResources 在构造函数中启动一子线程执行 cleanReferenceQueue() 方法:
上面有一个 while 循环,最终调用了 cleanupActiveReference()
上面最终会回调 listener#onResourceReleased,而 ActiveResources 的 listener 只有在 Engine 中设置(就是 Engine 自身)。
Engine 是 Glide 中真正负责加载的地方,加载逻辑的起始点就是从 Engine#load() 方法,它的 onResourceReleased 如下:
MemoryCache
内存缓存的第二级,即常说的 Lru 缓存,它的实现类是 LruResourceCache
Engine#load 从 activeResources 中读取不到缓存后,会从 lru 中读取一次。它的入口函数是 Engine#loadFromCache
从上面也可以看出 EngineResources 内部还维持了对 Resources 的计数
LruResourceCache
该类直接继承了 LruCache,整体逻辑也是使用 LinkedHashMap 实现的 lru 算法。只不过 LruResourceCache 内部缓存的是 Resource 对象,所以每一个 item 的大小需要 Resources 自己实现
内存缓存总结
- 内存缓存的并不是 bitmap
- 为了复用 bitmap,又将内存缓存分为 active Resources 与 lru 两级
active 级依赖 gc 执行淘汰(通过弱引用),淘汰时并不会淘汰真正的 resources,而是下沉到 lru 中lru 级依赖 lru 算法进行淘汰。从 lru 中读取缓存时,会自动上升至 active 中
BitmapPool
上面说过,LruResourceCache 内部是使用的 lru,当 LruResourceCache 满了会回收到 BitmapPool 中
主要流程:LruResourceCache#put() 最终到 Engine#onResourceRemoved(),然后到 Resource#recycle(),而该方法只有 BitmapResource, BitmapDrawableResource 与 GifDrawable 进行了处理。前两者都用于回收 bitmap,后者用于处理 gif,暂不考虑。
对于 bitmap 的回收,都使用 BitmapPool 进行。BitmapPool 最主要的实现是 LruBitmapPool,而后者根据不同的版本选择不同的策略,高版本使用的是 SizeConfigStrategy,它要求 config 相同,旧图片的内存不小于新图片需要的内存大小即可复用
当 bitmapPool 从 strategy 中拿到不复存的 bitmap 时,会就调用 Bitmap#createBitmap 根据需要创建一个新的 bitmap 返回。
glide 会主动监听内存变化,当内存过低时,会调用 BitmapPool#trimMemory() 该方法会对缓存的 bitmap 根据 lru 进行删除。
SizeConfigStrategy
核心方法就是 put() 与 get() 两个。put 比较简单
public void put(Bitmap bitmap) {
int size = Util.getBitmapByteSize(bitmap);
Key key = keyPool.get(size, bitmap.getConfig()); // 对 key 也进行了缓存
groupedMap.put(key, bitmap);
// 通过 config 指向了一个 Map
// 这个 Map 的 key 为 bitmap 占有的内存,value 为 bitmap 个数
// 那么 bitmap 复用的时候,就非常简单:先通过 config 拿到 config 适合的 map
// 再通过 map 拿到合适大小的个数
NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig());
Integer current = sizes.get(key.size);
// 记录每一个大小有多少个 bitmap 可复用
sizes.put(key.size, current == null ? 1 : current + 1);
}
get() 也很简单
上面最核心的就是 findBeseKey()
private Key findBestKey(int size, Bitmap.Config config) {
// 先根据需要的 size 与 config 生成一个 key
Key result = keyPool.get(size, config);
// getInConfigs() 可以理解为就返回 config。虽然看代码不同的 config 好像还可以替换
// 不太懂这块
for (Bitmap.Config possibleConfig : getInConfigs(config)) {
// 拿到 config 对应的 map
NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
// 返回 map 中的某一个 key,要求 key>=size。也就是说查找的是 size 可以复用的 bitmap
Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);
// 理论上已经可以直接复用,但 glide 要求旧 bitmap 的尺寸不能超过 size 太大
if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
if (possibleSize != size
|| (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
keyPool.offer(result); // 回收刚生成的 key,因为此时有复用的 key 了
// 可以复用,直接返回复用对应的 key
result = keyPool.get(possibleSize, possibleConfig);
}
break;
}
}
return result;
}