Glide 4.12图片框架之多级缓存源码设计分析

Posted hymKing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Glide 4.12图片框架之多级缓存源码设计分析相关的知识,希望对你有一定的参考价值。

一、Glide缓存初识

在上两篇文章中,我们从源码角度分析Glide框架加载图片的流程、以及Glide图片通过巧妙的空view的Fragment的设计实现的Glide的图片加载的三大生命周期函数onStart、onStop、onDestroy。Glide的框架的源码量确实比较大,今天我们再详细分析一下,Glide的框架的缓存模块的设计。

默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:

  1. 活动资源 (Active Resources) - 现在是否有另一个 View 正在展示这张图片?
  2. 内存缓存 (Memory cache) - 该图片是否最近被加载过并仍存在于内存中?
  3. 资源类型(Resource) - 该图片是否之前曾被解码、转换并写入过磁盘缓存?
  4. 数据来源 (Data) - 构建这个图片的资源是否之前曾被写入过文件缓存?

前两步检查图片是否在内存中,如果是则直接返回图片。后两步则检查图片是否在磁盘上,以便快速但异步地返回图片。

如果四个步骤都未能找到图片,则Glide会返回到原始资源以取回数据(原始文件,Uri, Url等)。

上述这段描述来源于官方文档对于缓存的介绍。官方文档的介绍,已经很清晰的说明了Glide框架在缓存实现的策略。缓存的类型在这里被分为了四类:活动资源、内存缓存、资源类型(磁盘缓存)、数据来源(文件缓存)。

其中活动资源、内存缓存 均属于内存缓存范畴、资源类型、数据来源 均属于磁盘缓存范畴。接下来,我们就从源码角度,来看一下这四级缓存的实现。

二、四级缓存实现的源码分析

结合之前的两篇Glide的分析的文章,我们确认缓存的分析的入口方法为Engine.load的方法,接下来我们就直接再次查看Engine类的相关源码实现:

2.1 活动资源的获取和添加

/** Engine类的功能:对启动的一个加载做响应和管理者活动资源和缓存资源 */
public class Engine imp... 
     public <R> LoadStatus load(...) 
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;
    //key的创建,一份资源(比如图片)的唯一标识
    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height,
            transformations,
            resourceClass,
            transcodeClass,
            options);

    EngineResource<?> memoryResource;
    synchronized (this) 
      //从内存中获取资源
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
      //memoryResource=null,表示活动资源、内存缓存均未获取到
      if (memoryResource == null) 
        //等待已经存在或者启动一个新的job
        return waitForExistingOrStartNewJob(
            ....);
      
    
  //从内存中获取资源
  private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) 
    //是否启动内存缓存,构建glide的时候可以配置,无的的则直接返回空
    if (!isMemoryCacheable) 
      return null;
    
    //根据key从活动资源中获取
    EngineResource<?> active = loadFromActiveResources(key);
    //活动资源不为空
    if (active != null) 
      //将活动资源返回
      return active;
    
    //活动资源为空,从cache中获取
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) 
      //cache资源不为空,将cache资源返回
      return cached;
    
    //活动资源和cache资源都为空,则最终返回null
    return null;
  

从上述源码中,主要看loadFromMemory()方法的实现,流程上,先从活动内存获取缓存,如果获取到直接向上层返回,如果获取不到从cache缓存中获取,如果获取到将cache缓存返回,获取不到则返回空。接下来分别具体看活动缓存和cache缓存的究竟:

private final ActiveResources activeResources;
@Nullable
private EngineResource<?> loadFromActiveResources(Key key) 
  EngineResource<?> active = activeResources.get(key);
  if (active != null) 
    //这个方法是在引擎的resource中定义的,做引用计数
    active.acquire();
  

  return active;

/**
 * 资源包装器
 */
class EngineResource<Z> implements Resource<Z> 
//获得引用数
synchronized void acquire() 
    if (isRecycled) 
      throw new IllegalStateException("Cannot acquire a recycled resource");
    
    //获得活动资源引用数+1
    ++acquired;
  
  
//释放引用数
void release() 
      boolean release = false;
      synchronized (this) 
        if (acquired <= 0) 
          throw new IllegalStateException("Cannot release a recycled or not yet acquired resource");
        
        if (--acquired == 0) 
          release = true;
        
      
      if (release) 
        //从活动缓存中释放、迁入cache缓存中
        listener.onResourceReleased(key, this);
      
         

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

活动资源是谁来维护的,我们可以看到activeResources.get(key),ActivityResources是活动资源的维护对象,ActivityResources是啥你,追踪一下源码:

final class ActiveResources 
  private final boolean isActiveResourceRetentionAllowed;
  private final Executor monitorClearedResourcesExecutor;
  //value 为弱引用对象Map集合
  @VisibleForTesting final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();
  //引用队列
  private final ReferenceQueue<EngineResource<?>> resourceReferenceQueue = new ReferenceQueue<>();
  .....
  //活动资源集合激活:本质就是向map集合中添加弱引用资源对象
  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();
    
  
  //激活状态转为空激活状态、移除
  synchronized void deactivate(Key key) 
    ResourceWeakReference removed = activeEngineResources.remove(key);
    if (removed != null) 
      removed.reset();
    
  
  //get方法,从map中获取弱应用 活动资源对象
  @Nullable  
  synchronized EngineResource<?> get(Key key) 
    //核心就这句,通过key从map集合中获取
    ResourceWeakReference activeRef = activeEngineResources.get(key);
    if (activeRef == null) 
      return null;
    

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

这块代码逻辑比较清晰,活动资源从ActivityResource中维护的activeEngineResources(一个根据资源key,存储着弱引用资源的Map集合)来进行维护。get(key)是获取,同时我们也查看一下激活的逻辑,通过查看activate方法调用来自于engine类中:

@SuppressWarnings("unchecked")
@Override
public synchronized void onEngineJobComplete(
    EngineJob<?> engineJob, Key key, EngineResource<?> resource) 
  if (resource != null && resource.isMemoryCacheable()) 
    //激活活动缓存
    activeResources.activate(key, resource);
  
  jobs.removeIfCurrent(key, engineJob);

private EngineResource<?> loadFromCache(Key key) 
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) 
      cached.acquire();
      //激活活动缓存
      activeResources.activate(key, cached);
    
    return cached;
  

小结:

活动资源通过引用计数法确定正在在被使用的资源。这样的资源被维护在内存中的一个Map集合中。活动资源的添加,很显然有两种手段,一种从远程获取到图片资源被使用后,会添加到活动资源,对应上述源码中的第一种情况,另一种是,从cache缓存中获得资源后,会添加活动资源,对应上述源码中的第二种情况。

而活动资源的移除,则是当当前资源不再有任何使用时,会从活动资源集合中移除,并添加到cache缓存中。

2.2 cache资源的获取和添加

2.1节介绍的活动缓存源码分析中,当活动缓存获取结果为空是,会继续调用loadFromCache()加载内存缓存。

public class Engine
    implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener 
  //加载cache缓存
  private EngineResource<?> loadFromCache(Key key) 
    //真实调用者
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) 
      cached.acquire();
      activeResources.activate(key, cached);
    
    return cached;
   
   //cache 是个momerycache
   private final MemoryCache cache;
   private EngineResource<?> getEngineResourceFromCache(Key key) 
     //从memeryCache中获取的
    Resource<?> cached = cache.remove(key);
    /下面的代码是包装成engineResource
    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;
  


这里我们重点关注memoryCache的实现LruResourceCache:

/** Lru 内存资源 */
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache 
  private ResourceRemovedListener listener;
  public LruResourceCache(long size) 
    super(size);
  

public class LruCache<T, Y> 
  private final Map<T, Entry<Y>> cache = new LinkedHashMap<>(100, 0.75f, true);
  public synchronized Y remove(@NonNull T key) 
    Entry<Y> entry = cache.remove(key);
    if (entry == null) 
      return null;
    
    currentSize -= entry.size;
    return entry.value;
  

Lru的特点,这里不做详细介绍了,可自行查资料,Lru主要说明的是集合的维护策略,英文:Least Recently Used,最近最少使用的资源会从集合中被淘汰。很多缓存实现都使用了Lru的算法。

关于Lrucache缓存的添加,2.1节中活动缓存的移除中,已经说明,当活动缓存不在使用,被释放时,会被添加的Lrucache中。

2.3对活动资源和LruCache缓存设计的思考和总结

近期研究的一些框架,无论客户端架构设计还是服务端架构设计,都会面临一个问题:性能。

而貌似解决性能问题的手段,又大有相似,无论是网络请求队列的设计、网络缓存的设计、还是图片加载队列的设计、图片缓存的设计,甚至于大型服务端架构、都不离开缓存设计,离不开优秀的缓存架构策略。而这些缓存的设计策略上,往往又都涉及多级缓存架构。

Glide框架中,活动资源是正在被使用的资源,目的是为了减少LruCache的缓存压力,也能避免同一份资源可能被加载两次的可能性。比如当前应用使用了大量的图片资源展示,如果Lrucache有可能会移除一份正在使用的图片资源,当这个资源需要将被被另一个视图所用之时,则可能需要重新从磁盘缓存中进行加载,这样的资源消耗是双份。

当然,上面的论断,是为了分析活动缓存设计的必要性思考出来的理由,也许是设计者的精益求精,大多数场景下,我觉得没有活动缓存的设计,并无影响。

Glide的设计上,目标对象先使用活动资源、资源后入lrucache ,另一种方案,资源直接入lrucache、驱动目标对象再使用,这是两种不同的设计、后者数据源统一,唯一的数据源驱动Ui视图更新。而入lrucache的动作和编解码无关,代码执行效率是极高的,所以,我认为即使采用后者的设计方案,也不会带来过大的性能问题。

(对这块设计的思考,有不同理解的朋友,欢迎在评论区讨论)

2.4磁盘缓存的读取和写入

在这篇文章 《最新源码Glide4.12框架之加载图片流程源码分析》 中我们分析了详细的图片加载流程,其中不涉及缓存模块的逻辑,再引用一下这篇文章末尾的流程图。

2.4.1磁盘缓存的读取

从前文的分析以及上述流程图中,我们知道真正执行加载任务的起点是EngineJob使用线程池执行DecodeJob。接下来,我们就从此处入手,继续分析磁盘缓存的相关内容。

class DecodeJob<R>
    implements DataFetcherGenerator.FetcherReadyCallback,
        Runnable,
        Comparable<DecodeJob<?>>,
        Poolable 
   //在DecodeJob中最终调用的方法是此方法
   private void runWrapped() 
    switch (runReason) 
      case INITIALIZE:
        //初始状态
        stage = getNextStage(Stage.INITIALIZE);
        //磁盘缓存策略如果配置了,则返回的是Stage.RESOURCE_CACHE
        currentGenerator = getNextGenerator();
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    
  
   private Stage getNextStage(Stage current) 
    switch (current) 
      case INITIALIZE:
        //磁盘缓存策略如果配置了,则返回的是Stage.RESOURCE_CACHE
        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);
    
  
   private DataFetcherGenerator getNextGenerator() 
    switch (stage) 
      //如果是磁盘缓存,此时构建的是DataFectherGenerator是ResourceCacheGenerator
      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);
    
  

通过上述源码的调用分析,如果磁盘缓存策略配置为可用,则最终是用的是ResourceCacheGenerator生成器。之后调用runGenerator():

private void runGenerators() 
  currentThread = Thread.currentThread();
  startFetchTime = LogTime.getLogTime();
  boolean isStarted = false;
  while (!isCancelled
      && currentGenerator != null/**此处调用当前generator的startNext方法*/
      && !(isStarted = currentGenerator.startNext())) 
    stage = getNextStage(stage);
    currentGenerator = getNextGenerator();

    if (stage == Stage.SOURCE) 
      reschedule();
      return;
    
  
  if ((stage == Stage.FINISHED || isCancelled) && !isStarted) 
    notifyFailed();
  


DataFetcherGenerator的实现类,在glide框架的设计中有三个,分别为DataCacheGenerator、ResourceCacheGenerator、SourceGenerator。这里面是是ResourceGenerator。查看ResourceGenerator的startNext方法:

/**
 * Generates @link com.bumptech.glide.load.data.DataFetcher DataFetchers from cache files
 * containing downsampled/transformed resource data.
 */
class ResourceCacheGenerator implements DataFetcherGenerator, DataFetcher.DataCallback<Object> 
   public boolean startNext() 
    //缓存key的集合列表
    List<Key> sourceIds = helper.getCacheKeys();
    ...
    //当前资源构建一个ResourceCacheKey
  

以上是关于Glide 4.12图片框架之多级缓存源码设计分析的主要内容,如果未能解决你的问题,请参考以下文章

Glide 4.12图片框架之多级缓存源码设计分析

Glide 4.12图片框架之多级缓存源码设计分析

Glide 4.12图片框架之多级缓存源码设计分析

Glide 4.12 框架源码中的生命周期设计

Glide 4.12 框架源码中的生命周期设计

Glide 4.12 框架源码中的生命周期设计