Glide系列--三级缓存

前言

Glide是一款强大的Android图片加载库,它使用了三级缓存来提供高效的图片加载和显示功能。这三级缓存分别是活动缓存/内存缓存/磁盘缓存,总的来说,Glide的三级缓存机制可以帮助Android应用在图片加载方面实现快速、高效、节省资源的目标。不仅可以提高用户体验,还可以减少网络请求,降低对服务器的负载。:

活动缓存

活动缓存其实也属于内存缓存的一种,只不过活动缓存是用于存储当前正在使用中的资源的一部分内存缓存。

Glide加载图片时,资源会首先被加载到活动缓存中,以便在当前和即将到来的请求中快速访问。活动缓存的大小通常是受限制的,它不会占用太多的内存资源,这样可以确保Glide在加载图片时不会过度消耗设备的内存。

我们首先看一下活动缓存调用的地方:

java 复制代码
public <R> LoadStatus load(
    GlideContext glideContext,
    Object model,
    ...
    ResourceCallback cb,
    Executor callbackExecutor) {
  long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

  EngineKey key =
      keyFactory.buildKey(
          model,
          signature,
          width,
          height...);

  EngineResource<?> memoryResource;
  synchronized (this) {
   // 从活动缓存或者内存缓存中加载图片资源
    memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

    if (memoryResource == null) {
      return waitForExistingOrStartNewJob(
          glideContext,
          ...
          cb,
          callbackExecutor,
          key,
          startTime);
    }
  }

  // Avoid calling back while holding the engine lock, doing so makes it easier for callers to
  // deadlock.
  cb.onResourceReady(
      memoryResource, DataSource.MEMORY_CACHE, /* isLoadedFromAlternateCacheKey= */ false);
  return null;
}

Engine.loadFromMemory是我们活动缓存以及内存缓存的起点:

kotlin 复制代码
@Nullable
private EngineResource<?> loadFromMemory(
    EngineKey key, boolean isMemoryCacheable, long startTime) {
  if (!isMemoryCacheable) {
    return null;
  }

 // 从活动缓存中加载图片资源
  EngineResource<?> active = loadFromActiveResources(key);
  if (active != null) {
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Loaded resource from active resources", startTime, key);
    }
    return active;
  }

// 从内存缓存中加载图片资源
  EngineResource<?> cached = loadFromCache(key);
  if (cached != null) {
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Loaded resource from cache", startTime, key);
    }
    return cached;
  }

  return null;
}

通过loadFromActiveResources,最终调用到ActiveResources.get方法:

ini 复制代码
final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

@Nullable
synchronized EngineResource<?> get(Key key) {
  ResourceWeakReference activeRef = activeEngineResources.get(key);
  if (activeRef == null) {
    return null;
  }

  EngineResource<?> active = activeRef.get();
  if (active == null) {
    cleanupActiveReference(activeRef);
  }
  return active;
}

可以看到,ActiveResources 中存有一个MapMap的key是Glide为图片资源生成的EngineKey,而value 是一个ResourceWeakReference 对象,而ResourceWeakReference对象就是一个EngineResource的弱引用,当通过弱引用获取到了指定的资源则返回,若得到的资源为空,证明该资源被回收了,则可以移除这一对<key, value>了:

less 复制代码
 static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {
    
    final Key key;
    
    Resource<?> resource;

    ResourceWeakReference(
        @NonNull Key key,
        @NonNull EngineResource<?> referent,
        @NonNull ReferenceQueue<? super EngineResource<?>> queue,
        boolean isActiveResourceRetentionAllowed) {
      super(referent, queue);
      this.key = Preconditions.checkNotNull(key);
      this.resource =
          referent.isMemoryCacheable() && isActiveResourceRetentionAllowed
              ? Preconditions.checkNotNull(referent.getResource())
              : null;
      isCacheable = referent.isMemoryCacheable();
    }

    void reset() {
      resource = null;
      clear();
    }
  }
}

整个活动缓存的获取思路简单清晰,就是根据指定key来获取对应的资源弱引用,再通过弱引用拿到对应的资源信息。但这里涉及的弱引用相关知识点值得我们学习

弱引用

弱引用(Weak Reference)是在编程中常见的一种引用类型,其主要特点是在垃圾回收过程中,如果一个对象只被弱引用所引用,并且没有被其他强引用所引用,那么该对象就会被垃圾回收器回收。弱引用通常用来解决内存泄漏和循环引用等问题,主要知识点有:

强引用和弱引用的区别 使用场景 WeakReference类 ReferenceQueue
- 强引用(Strong Reference) :在Java中,我们通常使用的对象引用都是强引用。只要一个对象有强引用与之关联,垃圾回收器就不会回收该对象。
  • 弱引用(Weak Reference) :弱引用不会增加对象的引用计数,如果一个对象只被弱引用所引用,而没有被强引用所引用,那么垃圾回收器会在下一次进行垃圾回收时回收这个对象。 | - 缓存:弱引用常用于实现缓存机制。当缓存中的对象不再被其他强引用引用时,垃圾回收器可以自动清理这些对象,避免缓存占用过多内存。
  • 监听器 :在事件监听器中,使用弱引用可以避免因为监听器对象持有引用导致对象无法被垃圾回收,从而造成内存泄漏。 | 在Java中,弱引用可以通过java.lang.ref.WeakReference类来实现。这个类提供了一个构造函数,用于创建弱引用对象,并提供get()方法用于获取弱引用所引用的对象。需要注意的是,由于弱引用所引用的对象可能已经被垃圾回收器回收,因此在使用get()方法获取对象时,需要先进行null检查。 | 弱引用可以与ReferenceQueue结合使用。ReferenceQueue是一个队列,当弱引用所引用的对象被垃圾回收器回收时,该弱引用会被放入ReferenceQueue中。通过检查ReferenceQueue中的元素,可以知道哪些对象已经被垃圾回收器回收。 |

ActiveResources中,对应弱引用的第一种使用场景,即缓存的使用,另外为了保证在弱引用的EngineResource回收后,对应的Map中的<key, value>对也能清除,ActiveResources 中将弱引用和ReferenceQueue(引用队列)结合使用:

  1. 在新建ResourceWeakReference 实例时传入引用队列ReferenceQueue
java 复制代码
synchronized void activate(Key key, EngineResource<?> resource) {
  ResourceWeakReference toPut =
      new ResourceWeakReference(
          key, resource, resourceReferenceQueue, isActiveResourceRetentionAllowed);

  ResourceWeakReference removed = activeEngineResources.put(key, toPut);
  // 有旧值就清空
  if (removed != null) {
    removed.reset();
  }
}
  1. 开一个线程,轮询引用队列里是否有资源被回收:
csharp 复制代码
@Synthetic
void cleanReferenceQueue() {
  while (!isShutdown) {
    try {
      ResourceWeakReference ref = (ResourceWeakReference) resourceReferenceQueue.remove();
      cleanupActiveReference(ref);
      
      ...
  }
}

void cleanupActiveReference(@NonNull ResourceWeakReference ref) {
  synchronized (this) {
  // 删除<key, value>对
    activeEngineResources.remove(ref.key);

    if (!ref.isCacheable || ref.resource == null) {
      return;
    }
  }

  EngineResource<?> newResource =
      new EngineResource<>(
          ref.resource,
          /* isMemoryCacheable= */ true,
          /* isRecyclable= */ false,
          ref.key,
          listener);
  listener.onResourceReleased(ref.key, newResource);
}

cleanReferenceQueue 中用了一个while循环,不停的查询ReferenceQueue是否有被回收的弱引用资源,但是这不会死循环吗?我们追踪resourceReferenceQueue.remove()会发现以下代码:

java 复制代码
/**
 * Removes the next reference object in this queue, blocking until either
 * one becomes available or the given timeout period expires.
 *
 * <p> This method does not offer real-time guarantees: It schedules the
 * timeout as if by invoking the {@link Object#wait(long)} method.
 *
 * @param  timeout  If positive, block for up to <code>timeout</code>
 *                  milliseconds while waiting for a reference to be
 *                  added to this queue.  If zero, block indefinitely.
 *
 * @return  A reference object, if one was available within the specified
 *          timeout period, otherwise <code>null</code>
 *
 * @throws  IllegalArgumentException
 *          If the value of the timeout argument is negative
 *
 * @throws  InterruptedException
 *          If the timeout wait is interrupted
 */
public Reference<? extends T> remove(long timeout)
    throws IllegalArgumentException, InterruptedException
{
    if (timeout < 0) {
        throw new IllegalArgumentException("Negative timeout value");
    }
    synchronized (lock) {
        Reference<? extends T> r = reallyPollLocked();
        if (r != null) return r;
        long start = (timeout == 0) ? 0 : System.nanoTime();
        for (;;) {
            lock.wait(timeout);
            r = reallyPollLocked();
            if (r != null) return r;
            if (timeout != 0) {
                long end = System.nanoTime();
                timeout -= (end - start) / 1000_000;
                if (timeout <= 0) return null;
                start = end;
            }
        }
    }
}

可以看到,当引用队列中没有弱引用对象时,函数会阻塞在这里,而不会造成死循环的问题,可以说是非常的巧妙了。

内存缓存

内存缓存将已加载的图片数据存储在应用的内存中。由于内存读取速度非常快,因此内存缓存可以快速地提供已加载图片的显示。

Glide中的内存缓存起点可以直接从以下代码开始:

ini 复制代码
// 内存缓存区
private final MemoryCache cache;

private EngineResource<?> loadFromCache(Key key) {
  EngineResource<?> cached = getEngineResourceFromCache(key);
  if (cached != null) {
    cached.acquire();
     // 从内存缓存中获取到resource时就将其移至活动缓存中
    activeResources.activate(key, cached);
  }
  return cached;
}

private EngineResource<?> getEngineResourceFromCache(Key key) {
 // 从内存缓存中获取到Resource时就将其从内存缓存移除
  Resource<?> cached = cache.remove(key);

  final EngineResource<?> result;
  if (cached == null) {
    result = null;
  } else if (cached instanceof EngineResource) {
    // Save an object allocation if we've cached an EngineResource (the typical case).
    result = (EngineResource<?>) cached;
  } else {
    result =
        new EngineResource<>(
            cached,
            /* isMemoryCacheable= */ true,
            /* isRecyclable= */ true,
            key,
            /* listener= */ this);
  }
  return result;
}

对于获取内存缓存中的资源,有一点需要说明:

从内存缓存中取到值后,就要将其从内存缓存中移除,放到活动缓存中,因为活动缓存通常用于正在展示的图片,而获取图片资源通常意味着要展示,因此,从内存缓存中获取到的值要移至活动缓存中也顺理成章,也是多级缓存管理的一种常见思路。

内存缓存的内部实现

从上文我们可以看到,内存缓存对应的类是MemoryCacheMemoryCache 有两个实现类:

  • MemoryCacheAdapter
  • LruResourceCache

其中, MemoryCacheAdapter是一个适配器类,它并不是一个真正的内存缓存实现,而是为了兼容一些特殊场景而存在的。如果你在Glide的配置中选择不使用内存缓存,或者自己实现了自定义的内存缓存机制,但仍然想使用Glide的图片加载功能,就可以选择使用MemoryCacheAdapter。这个适配器会简单地将图片资源保存在一个内部的Map中,但不执行真正的内存缓存策略。MemoryCacheAdapter通常用于关闭内存缓存时的临时替代。

因此,真正实现内存缓存的类是LruResourceCache,从名字我们可以看出,这个类主要采用了LRU算法来进行缓存的存和取。

LRU(Least Recently Used) 算法是一种常见的缓存淘汰策略,LRU算法基于以下原则:当缓存满时,应该淘汰最久未被使用的缓存项,从而保留最近使用的缓存项,以期在未来的访问中仍然能够被频繁访问到。

内存缓存中的LRU算法实现

在真正开始了解LRU算法的实现前,我们看一下LRU存和取的过程:

暂时无法在飞书文档外展示此内容

LRU算法的原理如上图所示,主要包含两个关键点:

  1. 每次被操作(添加或者获取)的元素都会放到列表的头部;

  2. 内存满后,再添加元素时,就会删除列表的尾部元素

因此,我们再来看LruResourceCache中的实现,LruResourceCache 继承自LruCache,即真正LRU算法的实现在LruCache类中:

java 复制代码
public class LruCache<T, Y> {
 // 1. 使用LinkedHashMap作为存取的数据结构
  private final Map<T, Entry<Y>> cache = new LinkedHashMap<>(100, 0.75f, true);
  private final long initialMaxSize;
  private long maxSize;
  private long currentSize;

  /**
   * Constructor for LruCache.
   *
   * @param size The maximum size of the cache, the units must match the units used in {@link
   *     #getSize(Object)}.
   */
  public LruCache(long size) {
    this.initialMaxSize = size;
    this.maxSize = size;
  }
  /**
   * Returns the item in the cache for the given key or null if no such item exists.
   *
   * @param key The key to check.
   */
    // 2. 获取元素
  @Nullable
  public synchronized Y get(@NonNull T key) {
    Entry<Y> entry = cache.get(key);
    return entry != null ? entry.value : null;
  }

    // 3. 添加元素
  @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;
  }

  @Nullable
  public synchronized Y remove(@NonNull T key) {
    Entry<Y> entry = cache.remove(key);
    if (entry == null) {
      return null;
    }
    currentSize -= entry.size;
    return entry.value;
  }

  /** Clears all items in the cache. */
  public void clearMemory() {
    trimToSize(0);
  }

  /**
   * Removes the least recently used items from the cache until the current size is less than the
   * given size.
   *
   * @param size The size the cache should be less than.
   */
  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);
  }

  @Synthetic
  static final class Entry<Y> {
    final Y value;
    final int size;

    @Synthetic
    Entry(Y value, int size) {
      this.value = value;
      this.size = size;
    }
  }
}

为了更清晰的了解代码实现,在以上代码中,我删除了部分代码。

  1. LruCache中用了一个LinkedHashMap作为缓存存取的数据结构

    1. LinkedHashMap 继承自HashMap,并且单独维护了一个双向链表,这样就可以在操作元素时,算法复杂度仅为O(1)

    2. 构造函数中accessOrder参数为true, 这样最新访问的就会放入队尾。

  2. 获取元素

LruCache中的get方法直接调用LinkedHashMap.get:

ini 复制代码
public V get(Object key) {
    Node<K,V> e;
    //  
    if ((e = getNode(hash(key), key)) == null)
        return null;
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMapEntry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMapEntry<K,V> p =
            (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

在获取到指定元素后,由于accessOrdertrue,因此会调用afterNodeAccess方法,将获取到的元素放到链表的尾部,LinkedHashMap 中最近使用的元素会被放到队尾。

  1. 添加元素
less 复制代码
public synchronized Y put(@NonNull T key, @Nullable Y item) {
    final int itemSize = getSize(item);
    // 判断itemSize是否大于缓存的最大size
    if (itemSize >= maxSize) {
      onItemEvicted(key, item);
      return null;
    }

    if (item != null) {
      currentSize += itemSize;
    }
     // 1.调用LinkedHashMap.put
    @Nullable Entry<Y> old = cache.put(key, item == null ? null : new Entry<>(item, itemSize));
    
    if (old != null) {
    // 如果之前的key对应的value不为空,则将currentSize减1(默认itemSize为1)
      currentSize -= old.size;

      if (!old.value.equals(item)) {
        onItemEvicted(key, old.value);
      }
    }
     // 2.如果缓存满了,就删除最近未使用的元素
    evict();

    return old != null ? old.value : null;
  }
  • 调用LinkedHashMap.putLinkedHashMap并未重写put方法,因此还是调用的HashMap.put,但是LinkedHashMap 重写了newNodeafterNodeAccess 方法:
ini 复制代码
// HashMap

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 之前不存在对应Hash值的元素,调用newNode
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
                // 存在key对应的元素后调用afterNodeAccess
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

可以看到,不管是什么情况,HashMap.put方法都会调用newNode方法或者afterNodeAccess方法:

ini 复制代码
// LinkedHashMap 
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMapEntry<K,V> p =
        new LinkedHashMapEntry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}

// link at the end of list
private void linkNodeLast(LinkedHashMapEntry<K,V> p) {
    LinkedHashMapEntry<K,V> last = tail;
    tail = p;
    if (last == null)
        head = p;
    else {
        p.before = last;
        last.after = p;
    }
}

LinkedHashMap.newNode方法中会将新元素放到队尾,而afterNodeAccess方法上文已经讲过,也是将指定元素放到队尾。

  • evict方法
ini 复制代码
private void evict() {
  trimToSize(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);
  }
}

当前链表size大于最大容量时,删除队头的元素

磁盘缓存

当我们从活动缓存以及内存缓存中都没有获取到指定资源时,我们就需要从磁盘缓存或者网络来获取:

该部分的起点是waitForExistingOrStartNewJob方法:

scss 复制代码
private <R> LoadStatus waitForExistingOrStartNewJob(
    GlideContext glideContext,
    ...
    ResourceCallback cb,
    Executor callbackExecutor,
    EngineKey key,
    long startTime) {

  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
  if (current != null) {
    current.addCallback(cb, callbackExecutor);
    if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Added to existing load", startTime, key);
    }
    return new LoadStatus(cb, current);
  }

  EngineJob<R> engineJob =
      engineJobFactory.build(
          key,
          isMemoryCacheable,
          useUnlimitedSourceExecutorPool,
          useAnimationPool,
          onlyRetrieveFromCache);

  DecodeJob<R> decodeJob =
      decodeJobFactory.build(
          glideContext,
           ...
          engineJob);

  jobs.put(key, engineJob);

  engineJob.addCallback(cb, callbackExecutor);
  engineJob.start(decodeJob);

  if (VERBOSE_IS_LOGGABLE) {
    logWithTimeAndKey("Started new load", startTime, key);
  }
  return new LoadStatus(cb, engineJob);
}

该方法中创建了两个对象:EngineJobDecodeJob

  • EngineJob用于开启任务,在资源加载成功和失败时回调;

  • DecodeJob对象用于加载资源,操作缓存等

  • 此处EngineJob.start()内部就是调用线程池的excute()方法,传入的正是DecodeJob,因此接着进入DecodeJob.run()方法。

DecodeJob.runWrapped()

csharp 复制代码
@Override
public void run() {
  DataFetcher<?> localFetcher = currentFetcher;
  try {
        if (isCancelled) {
          notifyFailed();
          return;
        }
        runWrapped(); 
    }
 }
 
 private void runWrapped() {
  switch (runReason) {
    case INITIALIZE:
      stage = getNextStage(Stage.INITIALIZE);
      currentGenerator = getNextGenerator();
      runGenerators();
      break;
    case SWITCH_TO_SOURCE_SERVICE:
      runGenerators();
      break;
    case DECODE_DATA:
      decodeFromRetrievedData();
      break;
    default:
      throw new IllegalStateException("Unrecognized run reason: " + runReason);
  }
}

runWrapped方法中根据runReason的不同状态值执行不同的逻辑:

  • INITIALIZE :初始化状态;

  • SWITCH_TO_SOURCE_SERVICE:从磁盘服务切换到远程(网络)服务;

  • DECODE_DATA:由于格式或其他原因,仍然需要对图像数据进行解码。在这种情况下,DecodeJob会以DECODE_DATA状态运行

当状态为INITIALIZE时,我们通过getNextStage获取stage值:

arduino 复制代码
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);
  }
}

Stage也有不同的值:

  • RESOURCE_CACHE:已解码的资源缓存阶段;
  • DATA_CACHE:图片源文件缓存状态;
  • SOURCE:网络请求状态
  • FINISHED:结束状态

而不同的取值一是取决于传入的Stage值以及DiskCacheStrategyDiskCacheStrategy有几种类型:

在Glide图像加载库中,DiskCacheStrategy 枚举类定义了不同的磁盘缓存策略,用于控制图像在磁盘缓存中的存储行为。以下是 DiskCacheStrategy 可用的几种类型:

  • ALL:

此策略表示将原始图像数据和已解码的图像资源都存储在磁盘缓存中。无论图像是否被解码,都会存储。这意味着即使图像已被解码,其原始数据也会被存储在磁盘上,以便后续使用。

  • NONE:

NONE 策略表示不将任何图像数据存储在磁盘缓存中。这适用于那些你不希望在磁盘上保留图像的情况,例如临时的或敏感的图像。

  • DATA:

DATA 策略表示只将原始图像数据存储在磁盘缓存中,而不存储已解码的图像资源。这适用于只想存储原始数据以进行后续处理或转发的情况。

  • RESOURCE:

RESOURCE 策略表示只将已解码的图像资源存储在磁盘缓存中,而不存储原始图像数据。这适用于希望在磁盘上保留解码后的图像资源以供重用的情况。

  • AUTOMATIC:

AUTOMATIC 策略会根据图像的特性和请求来自动决定是否存储图像数据在磁盘缓存中。通常,它会存储原始数据和解码后的资源,以便在需要时进行快速访问。

通常选择**AUTOMATIC**即可,因此diskCacheStrategy.decodeCachedResource()true ,因此,我们再回到runWrapped函数中:

csharp 复制代码
private void runWrapped() {
  switch (runReason) {
    case INITIALIZE:
     // stage为RESOURCE_CACHE
      stage = getNextStage(Stage.INITIALIZE);
      currentGenerator = getNextGenerator();
      runGenerators();
      break;
    ...
  }
}

stageRESOURCE_CACHE后,调用getNextGenerator

csharp 复制代码
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);
  }
}

该函数根据不同的stage值,返回不同的DataFetcherGenerator对象,此处返回ResourceCacheGenerator对象,接着调用runGenerators 方法

DecodeJob.runGenerators

ini 复制代码
private void runGenerators() {
  currentThread = Thread.currentThread();
  startFetchTime = LogTime.getLogTime();
  boolean isStarted = false;
  // 
  while (!isCancelled
      && currentGenerator != null
       // 重点1
      && !(isStarted = currentGenerator.startNext())) {
    stage = getNextStage(stage);
    currentGenerator = getNextGenerator();

     // 重点2
    if (stage == Stage.SOURCE) {
      reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE);
      return;
    }
  }
  // We've run out of stages and generators, give up.
  if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
    notifyFailed();
  }

  // Otherwise a generator started a new load and we expect to be called back in
  // onDataFetcherReady.
}

runGenerators函数中有一个while循环,有两种情况可以结束循环:

  1. currentGenerator.startNext()返回true:表明currentGenerator 从磁盘加载数据成功

    1. currentGenerator 可以是ResourceCacheGenerator或者DataCacheGenerator,前者表明存在指定资源缓存,后者表明存在源数据缓存
  2. stage == Stage.SOURCE:表明从磁盘加载数据失败,需要进入下一步--从网络获取资源

stage == Stage.SOURCE 时,调用reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE)

typescript 复制代码
private void reschedule(RunReason runReason) {
  this.runReason = runReason;
  callback.reschedule(this);
}

callback就是EngineJob 对象:

scss 复制代码
public void reschedule(DecodeJob<?> job) {
  // Even if the job is cancelled here, it still needs to be scheduled so that it can clean itself
  // up.
  getActiveSourceExecutor().execute(job);
}

获取SourceExecutor,继续执行DecodeJob,也就接着执行runWrapped方法:

紧接着走SourceGenerator.runGenerators方法获取网络图片,获取到资源后写入磁盘缓存。

可以看到,整体的获取磁盘缓存或网络图片的流程稍微有些绕,一不小心就绕迷糊了,

小结

  1. 先从ResourceCacheGenerator中去寻找是否有经过转换之后的磁盘文件缓存,这个缓存要比源文件缓存小;

  2. 如果上一步没有获得数据,再从DataSourceGenerator中寻找源文件磁盘缓存,这个是加载的源文件直接写入缓存;

  3. 如果都没命中,那么就去Http请求资源,资源请求回来之后先写入磁盘缓存,再返回到Target

总结

  1. Glide三级缓存涉及到活动缓存/内存缓存/磁盘缓存;
  2. 活动缓存采用弱引用的方式存储资源,也是一种内存缓存,用于缓存正在使用的资源;
  3. 内存缓存采用LRU算法存储资源;
  4. 磁盘缓存分为指定资源缓存与指定资源对应的解码后原文件缓存
相关推荐
姑苏风18 分钟前
《Kotlin实战》-附录
android·开发语言·kotlin
数据猎手小k4 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小104 小时前
JavaWeb项目-----博客系统
android
风和先行5 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.5 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰6 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶6 小时前
Android——网络请求
android
干一行,爱一行6 小时前
android camera data -> surface 显示
android
断墨先生7 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
无极程序员8 小时前
PHP常量
android·ide·android studio