本文基于Glide 4.11.0

参考文章:Glide 源码分析解读-缓存模块-基于最新版Glide 4.9.0

注意:由于版本差异问题,本文有些部分与参考文章有差异。

缓存模块是Glide中非常重要的部分,Glide图片加载的高效性,几乎有一半功劳都在这里了。

一般来说,Glide有三级缓存,就是内存缓存磁盘缓存网络缓存

先来看缓存流程图,如下:

graph TD; style A fill:#99ccff style B1 fill:#aaffaa A(发起请求) --> B{1. 通过
ActiveResources
获取资源} --> |命中|B1([加载完成]); B --> |未命中|C{2. 通过
MemoryCache
获取资源} --> |命中|C1[缓存至
ActiveResources] --> B1; C --> |未命中|D{3. 通过
DiskCache
获取资源} --> |命中|D1[缓存至
MemoryCache] --> C1; D --> |未命中|E["4. 通过数据源(网络、文件等)
加载数据"] --> E1[缓存至
DiskCache] --> D1;

内存缓存

内存缓存主要靠三个部分组成:ActiveResourcesMemoryCacheBitmapPool

ActiveResources

ActiveResources表示当前正在活动中的资源。ActiveResources通过一个Map<Key, ResourceWeakReference>来保存活动中的资源,其中的ResourceWeakReference就是资源数据,在构建这个ResourceWeakReference的时候必须传入一个ReferenceQueue用来检测资源是否被回收。

Q1:如何探知WeakReference中的值被回收了呢?

ReferenceQueue queue = ...;
WeakReference wr = new WeakReference(value, queue);

当构建WeakReference的时候,如果传入了queue参数,则在value被回收的时候,wr会被加入到queue中去,这样,通过检测queue中是否有值,就可以探知value是否被回收了。

那么,在何时去探知ReferenceQueue中的值呢?我们查看ActiveResources的关键代码:

/*构造方法中,通过monitorClearedResourcesExecutor执行了cleanReferenceQueue()方法。
*/
ActiveResources(boolean isActiveResourceRetentionAllowed) {
  this(
    isActiveResourceRetentionAllowed,
    java.util.concurrent.Executors.newSingleThreadExecutor(
      new ThreadFactory() {
        @Override
        public Thread newThread(@NonNull final Runnable r) {
          return new Thread(
            new Runnable() {
              @Override
              public void run() {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                r.run();
              }
            },
            "glide-active-resources");
        }
      }));
}

@VisibleForTesting
ActiveResources(
  boolean isActiveResourceRetentionAllowed, Executor monitorClearedResourcesExecutor) {
  this.isActiveResourceRetentionAllowed = isActiveResourceRetentionAllowed;
  this.monitorClearedResourcesExecutor = monitorClearedResourcesExecutor;

  monitorClearedResourcesExecutor.execute(
    new Runnable() {
      @Override
      public void run() {
        cleanReferenceQueue();
      }
    });
}


void cleanReferenceQueue() {
  while (!isShutdown) {
    try {
      ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
      cleanupActiveReference(ref);

      // This section for testing only.
      DequeuedResourceCallback current = cb;
      if (current != null) {
        current.onResourceDequeued();
      }
      // End for testing only.
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }
}

我们通过代码可以看出,cleanReferenceQueue是一个靠isShutdown变量控制的死循环方法,这个方法执行在一个优先级为THREAD_PRIORITY_BACKGROUND的线程上。

Q2:那么,既然是死循环方法,会不会过多的占用CPU资源呢?

其实不会的,因为ReferenceQueue#remove是一个阻塞式的方法,如果没有元素可以被remove,则等待至有元素可以remove的时候,等待期间释放CPU。

注意:此处与参考文章中的说法不同,这是因为版本差异。查看Glide update log hsitory,可以看出出于避免在主线程做清理的原因,将清理任务放在了后台线程,而不是放在IdleHandler中。

那么被回收了的资源去哪里了呢?

我们查看cleanupActiveReference方法,得知,通过ResourceListener#onResourceReleased回调,交给了Engine来处理,我们查看Engine的onResourceReleased方法。

@Override
public void onResourceReleased(Key cacheKey, EngineResource<?> resource) {
  activeResources.deactivate(cacheKey);
  if (resource.isMemoryCacheable()) {
    cache.put(cacheKey, resource);
  } else {
    resourceRecycler.recycle(resource, /*forceNextFrame=*/ false);
  }
}

从这里我们发现,这里出现了两种情况:

  1. 如果资源是MemoryCacheable的,则缓存在MemoryCache;
  2. 如果资源不是MemoryCacheable的,则交给ResourceRecycler调用Resource的recycle()方法来回收,如果此Resource为BitmapResource,则会将Bitmap回收到BitmapPool中去。

在开始MemoryCache和BitmapPool前,需要先了解一下MemorySizeCalculator这个类,这个类是用来计算 BitmapPool 、ArrayPool 以及 MemoryCache 大小的。

MemoryCache

MemoryCache的具体实现类是LruResourceCache,而实际的逻辑方法,都在其父类LruCache中,以put方法为例。

@Nullable
public synchronized Y put(@NonNull T key, @Nullable Y item) {
  final int itemSize = getSize(item);
  if (itemSize >= maxSize) {
    onItemEvicted(key, item);
    return null;
  }

  if (item != null) {
    currentSize += itemSize;
  }
  @Nullable Entry<Y> old = cache.put(key, item == null ? null : new Entry<>(item, itemSize));
  if (old != null) {
    currentSize -= old.size;

    if (!old.value.equals(item)) {
      onItemEvicted(key, old.value);
    }
  }
  evict();

  return old != null ? old.value : null;
}

当有一个新的item被put进去以后,会替换出一个老的值old,如果old非为空,则需要将当前容量减去old的大小,如果old并非新的item,则需要通过onItemEvicted进行回调,通知有老值被“驱逐”了。最后还要执行一次evict方法,按照LRU算法,将超出maxSize的item“驱逐”出去,以确保在maxSize范围内。

protected synchronized void trimToSize(long size) {
  Map.Entry<T, Entry<Y>> last;
  Iterator<Map.Entry<T, Entry<Y>>> cacheIterator;
  while (currentSize > size) {
    cacheIterator = cache.entrySet().iterator();
    last = cacheIterator.next();
    final Entry<Y> toRemove = last.getValue();
    currentSize -= toRemove.size;
    final T key = last.getKey();
    cacheIterator.remove();
    onItemEvicted(key, toRemove.value);
  }
}

private void evict() {
  trimToSize(maxSize);
}

“驱逐”的值去哪里了呢?我们查看MemoryCache类的源码,可以知道是通过ResourceRemovedListener回调给了Engine,在Engine中我们查看onResourceRemoved方法。

@Override
public void onResourceRemoved(@NonNull final Resource<?> resource) {
  // Avoid deadlock with RequestManagers when recycling triggers recursive clear() calls.
  // See b/145519760.
  resourceRecycler.recycle(resource, /*forceNextFrame=*/ true);
}

我们可以看到,这里同样是通过resourceRecycler进行了回收。在这里,则是交给resource自己的recycle()方法来处理,比如,BitmapResource是交给了BitmapPool进行处理。

BitmapPool

这里是专门用来存放被回收的Bitmap的,其中BitmapDrawableResourceBitmapResource都持有一个BitmapPool变量,在执行recycle()方法时候,调用BitmapPool#put()方法。我们来看一下这个BitmapPool的默认实现类LruBitmapPool的方法实现。

@Override
public synchronized void put(Bitmap bitmap) {
  if (bitmap == null) {
    throw new NullPointerException("Bitmap must not be null");
  }
  if (bitmap.isRecycled()) {
    throw new IllegalStateException("Cannot pool recycled bitmap");
  }
  if (!bitmap.isMutable()
      || strategy.getSize(bitmap) > maxSize
      || !allowedConfigs.contains(bitmap.getConfig())) {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(
        TAG,
        "Reject bitmap from pool"
        + ", bitmap: "
        + strategy.logBitmap(bitmap)
        + ", is mutable: "
        + bitmap.isMutable()
        + ", is allowed config: "
        + allowedConfigs.contains(bitmap.getConfig()));
    }
    bitmap.recycle();
    return;
  }

  final int size = strategy.getSize(bitmap);
  strategy.put(bitmap);
  tracker.add(bitmap);
	...
}

这里我们可以看出,当Bitmap在三种情况下是不会被BitmapPool缓存起来的:

  1. 这个bitmap是非mutable的,也就是说是不允许被复用的;
  2. 这一个bitmap的字节数大小已经超过了可以容纳的总大小;
  3. BitmapPool中不允许的Config类型。

在这种情况,bitmap就被直接recycle掉,而不是放入缓存等待下次使用。

如果不满足这三种情况,则会被strategy缓存起来,等待下次使用。

我们再看LruBitmapPool#get()方法。

@Override
@NonNull
public Bitmap get(int width, int height, Bitmap.Config config) {
  Bitmap result = getDirtyOrNull(width, height, config);
  if (result != null) {
    result.eraseColor(Color.TRANSPARENT);
  } else {
    result = createBitmap(width, height, config);
  }
  return result;
}

我们可以看到,当能够查询到符合条件的Bitmap的时候,会先通过eraseColor方法,将其变成透明图片,然后再交给调用者来使用;如果查询不到,则创建一个新图交给调用者来使用。

LruBitmapPool的LruPoolStrategy变量,在KITKAT以及以上,是SizeConfigStrategy,在以下是AttributeStrategy,这是因为在KITKAT版本以下,Bitmap的复用需要尺寸的严格匹配,但是KITKAT及以上没有这个问题,只要被复用的图片尺寸比目标尺寸大就可以。

ArrayPool

ArrayPool主要用在ThumbnailStreamOpenerByteBufferGifDecoder中,具体的实现类为LruArrayPool

在LruArrayPool中,通过groupedMap来缓存数据,而缓存数据的byte字节数是通过ArrayAdapterInterface来计算的,ArrayAdapterInterface是一个接口,实现类有两个:IntegerArrayAdapterByteArrayAdapter,分别对应缓存int[].class和byte[].class。

StreamGifDecoder和StreamBitmapDecoder都有一个ArrayPool成员。解码过程中需要用到byte[],但不是直接new byte[],而是调用ArrayPool.get()从对象池中拿,用完了归还。

DiskCache

在上一章Glide源码分析与自我实现(一)——数据加载主流程中,提到过数据加载的主流程,其中一个非常重要的类是 DecodeJob,在这个类的getNextGenerator方法中,返回的SourceGenerator会用来加载远程数据,但是这个方法不止返回这一个DataFetcherGenerator类,这是一个通过条件判断,返回不同DataFetcherGenerator类的方法。

private DataFetcherGenerator getNextGenerator() {
  switch (stage) {
    case RESOURCE_CACHE:
      return new ResourceCacheGenerator(decodeHelper, this);
    case DATA_CACHE:
      return new DataCacheGenerator(decodeHelper, this);
    case SOURCE:
      return new SourceGenerator(decodeHelper, this);
    case FINISHED:
      return null;
    default:
      throw new IllegalStateException("Unrecognized stage: " + stage);
  }
}

实际上,这是依次递进的有限状态机设计模式,当一个获取数据请求到来时候,此时是默认状态INITIALIZE,然后通过getNextStage方法判断下一个状态是什么,再按照新的状态获取DataFetcherGenerator,然后随着任务的执行,不断改变状态。

private Stage getNextStage(Stage current) {
  switch (current) {
    case INITIALIZE:
      return diskCacheStrategy.decodeCachedResource()
        ? Stage.RESOURCE_CACHE
        : getNextStage(Stage.RESOURCE_CACHE);
    case RESOURCE_CACHE:
      return diskCacheStrategy.decodeCachedData()
        ? Stage.DATA_CACHE
        : getNextStage(Stage.DATA_CACHE);
    case DATA_CACHE:
      // Skip loading from source if the user opted to only retrieve the resource from cache.
      return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
    case SOURCE:
    case FINISHED:
      return Stage.FINISHED;
    default:
      throw new IllegalArgumentException("Unrecognized stage: " + current);
  }
}

其状态变更顺序为INITIALIZE -> RESOURCE_CACHE -> DATA_CACHE -> SOURCE,代表着ResourceCacheGenerator、DataCacheGenerator和SourceGenerator,当从ResourceCahce中拿不到数据,则向DataCacheGenerator请求数据,如果还是拿不到,则通过SourceGenerator去请求数据了。

在这个过程中,SourceGenerator向DiskCache中写入数据,ResourceCacheGenerator和DataCacheGenerator从DiskCache中读取数据。

class ResourceCacheGenerator implements DataFetcherGenerator,

  @Override
  public boolean startNext() {
      ...
      currentKey = new ResourceCacheKey(sourceId, helper.getSignature(), helper.getWidth(),
          helper.getHeight(), transformation, resourceClass, helper.getOptions());
      cacheFile = helper.getDiskCache().get(currentKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }
  }
}

class DataCacheGenerator implements DataFetcherGenerator,

  @Override
  public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      ...
      Key sourceId = cacheKeys.get(sourceIdIndex);
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      cacheFile = helper.getDiskCache().get(originalKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }
}

class SourceGenerator implements DataFetcherGenerator {

  @Override
  public boolean startNext() {
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }
    ...
  }

  private void cacheData(Object dataToCache) {
    long startTime = LogTime.getLogTime();
    try {
      Encoder<Object> encoder = helper.getSourceEncoder(dataToCache);
      DataCacheWriter<Object> writer =
          new DataCacheWriter<>(encoder, dataToCache, helper.getOptions());
      originalKey = new DataCacheKey(loadData.sourceKey, helper.getSignature());
      helper.getDiskCache().put(originalKey, writer);
      ...
    } finally {
      loadData.fetcher.cleanup();
    }

    sourceCacheGenerator =
        new DataCacheGenerator(Collections.singletonList(loadData.sourceKey), helper, this);
  }
}

DiskCache的默认实现类是DiskLruCacheWrapper,其内部通过DiskLruCache来管理磁盘缓存。

总结

到现在,Glide主要部分已经分析的差不多了,实际上这个优秀的框架可挖的地方还有很多,比如通过APT来实现很好的扩展,框架中涉及多种涉及模式等。

其中涉及到的涉及模式,比如无处不在的构建者模式工厂模式,DecodeJob中的有限状态机模式,还有BitmapPool和ArrayPool中的享元模式,DiskLruCacheWrapper中的代理模式等。

参考文章

Glide缓存分析