Glide 缓存

287 阅读4分钟

前言

Glide 提供有内存及硬盘缓存:

  1. 内存缓存又分为 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() 方法:

image.png

上面有一个 while 循环,最终调用了 cleanupActiveReference()

image.png

上面最终会回调 listener#onResourceReleased,而 ActiveResources 的 listener 只有在 Engine 中设置(就是 Engine 自身)。

Engine 是 Glide 中真正负责加载的地方,加载逻辑的起始点就是从 Engine#load() 方法,它的 onResourceReleased 如下:

image.png

MemoryCache

内存缓存的第二级,即常说的 Lru 缓存,它的实现类是 LruResourceCache

Engine#load 从 activeResources 中读取不到缓存后,会从 lru 中读取一次。它的入口函数是 Engine#loadFromCache

image.png

从上面也可以看出 EngineResources 内部还维持了对 Resources 的计数

LruResourceCache

该类直接继承了 LruCache,整体逻辑也是使用 LinkedHashMap 实现的 lru 算法。只不过 LruResourceCache 内部缓存的是 Resource 对象,所以每一个 item 的大小需要 Resources 自己实现

image.png

内存缓存总结

  1. 内存缓存的并不是 bitmap
  2. 为了复用 bitmap,又将内存缓存分为 active Resources 与 lru 两级
  3. active 级依赖 gc 执行淘汰(通过弱引用),淘汰时并不会淘汰真正的 resources,而是下沉到 lru 中
  4. 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() 也很简单

Xnip2022-04-15_16-24-01.png

上面最核心的就是 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;
}