Glide缓存机制浅析

如果没有缓存,在大量的网络请求从远程获取图片时会造成网络流量的浪费,尤其是面对高清大图的加载更是如此,为了节省带宽,也为了减少用户等待的时间,合理的缓存方式必不可少,这也是Glide图片框架的强大之处。另外Glide的缓存机制可以说是非常高频的问题,Glide有几级缓存?Glide读取缓存的顺序和时机是什么?Glide存放缓存的顺序和时机又是什么?

1.Glide中缓存概念简述

a29aa5505376d623a985b14658142521.jpg

Glide中的缓存分为两部分,内存缓存和硬盘缓存。

1.1 内存缓存

内存缓存又分为两级,一级是LruCache缓存,一级是弱引用缓存。

内存缓存的作用:防止应用重复将图片数据读取到内存当中。

LruCache缓存:不在使用中的图片使用LruCache来进行缓存。

弱引用缓存:把正在使用中的图片使用弱引用来进行缓存

为什么设计弱引用缓存?

因为内存缓存使用LRU算法,当你使用Gilde加载并显示第一张图片时,后面又加载了很多图片,同时你的第一张图片还在用。这个时候内存缓存根据LRU算法可能会删除你正在使用的第一张照片。这样的后果就是你正在使用的照片找不到,后果就是程序崩溃。

1.2 硬盘缓存

硬盘缓存的作用:防止应用重复从网络或其他地方重复下载和读取数据;

2.缓存源码流程

memory cache和disk cache在Glide创建的时候也被创建了,并传给了Engine引擎类,Glide创建的代码在GlideBuilder.build(Context)方法。

@NonNull 
Glide build(@NonNull Context context) { 
  if (memoryCache == null) { 
    memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize()); 
  } 
  if (diskCacheFactory == null) { 
    diskCacheFactory = new InternalCacheDiskCacheFactory(context); 
  } 
  if (engine == null) { 
    engine = 
        new Engine( 
            memoryCache, 
            diskCacheFactory, 
            ...); 
  } 
  return new Glide( 
      ... 
      memoryCache, 
      ...); 
} 

1. 内存缓存--memoryCache

通过代码可以看到 memoryCache 被放入 Engine 和 Glide 实例中。在Engine中利用memoryCache进行存取操作,Glide 实例中的memoryCache是用来在内存紧张的时候,通知memoryCache释放内存。

Glide如何监听内存紧张?

当应用内存不足的时候,会回调ComponentCallbacks2接口的void onTrimMemory(@TrimMemoryLevel int level);或者ComponentCallbacks接口的void onLowMemory();;ComGlide实现了ComponentCallbacks2接口(ComponentCallback2继承了ComponentCallback),在Glide创建完成后,通过```applicationContext.registerComponentCallbacks(glide)``将其注册进Application,因此Glide 实例可以监听内存紧张的信号。

@Override
  public void onLowMemory() {
    clearMemory();
  }

public void clearMemory() {
    // Engine asserts this anyway when removing resources, fail faster and consistently
    Util.assertMainThread();
    // memory cache needs to be cleared before bitmap pool to clear re-pooled Bitmaps too. See #687.
    memoryCache.clearMemory();
    bitmapPool.clearMemory();
    arrayPool.clearMemory();
  }
// Glide 
@Override 
public void onTrimMemory(int level) { 
  trimMemory(level); 
} 
public void trimMemory(int level) { 
  // Engine asserts this anyway when removing resources, fail faster and consistently 
  Util.assertMainThread(); 
for (RequestManager manager : managers) {
      manager.onTrimMemory(level);
    }
  // memory cache needs to be trimmed before bitmap pool to trim re-pooled Bitmaps too. See #687. 
  memoryCache.trimMemory(level); 
  bitmapPool.trimMemory(level); 
  arrayPool.trimMemory(level); 
} 

在onlowMemory 中Glide会将一些缓存的内存进行清除,方便进行内存回收,当onTrimMemory被调用的时候,如果level是系统资源紧张,Glide会将Lru缓存和BitMap重用池相关的内容进行回收。如果是其他的原因调用onTrimMemory,Glide会将缓存的内容减小到配置缓存最大内容的1/2。

memoryCache是一个使用LRU(least recently used)算法实现的内存缓存类LruResourceCache,继承至LruCache类,并实现了MemoryCache接口。LruCache定义了LRU算法实现相关的操作,而MemoryCache定义的是内存缓存相关的操作。

LruCache源码分析
    public class LruCache<K, V> {
    // 数据最终存在 LinkedHashMap 中
    private final LinkedHashMap<K, V> map;
    ...
    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        // 创建一个LinkedHashMap,accessOrder 传true
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }
    ...

LruCache 构造方法里创建一个LinkedHashMap,accessOrder 参数传true,表示按照访问顺序排序,数据存储基于LinkedHashMap。

LinkedHashMap在HashMap的数据结构基础上,添加了双向链表的数据结构,再加上对 LinkedHashMap 的数据操作上锁,就实现了LruCache的缓存策略,即最少最近访问。

当调用 put()方法时,就会在集合中添加元素,并调用
trimToSize()判断缓存是否已满,如果满了就用 LinkedHashMap 的迭代器删除队尾元素,即近期最少访问的元素。

当调用 get()方法访问缓存对象时,就会调用 LinkedHashMap 的 get()方法获得对应集合元素,同时会更新该元素到队头。

2.磁盘缓存

diskCacheFactory是创建DiskCache的Factory,DiskCache接口定义。
DiskCache.Factory的默认实现为InternalCacheDiskCacheFactory。在InternalCacheDiskCacheFactory中会默认会创建一个250M的缓存目录,其路径

/data/data/{package}/cache/image_manager_disk_cache/。

继续看其父类DiskLruCacheFactory的代码:

public class DiskLruCacheFactory implements DiskCache.Factory { 
  // 省略部分代码
  @Override 
  public DiskCache build() { 
    File cacheDir = cacheDirectoryGetter.getCacheDirectory(); 
    if (cacheDir == null) { 
      return null; 
    } 
    if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) { 
      return null; 
    } 
    return DiskLruCacheWrapper.create(cacheDir, diskCacheSize); 
  } 
} 

DiskLruCacheFactory.build()方法会返回一个DiskLruCacheWrapper类的实例,看下DiskLruCacheWrapper的实现。

public class DiskLruCacheWrapper implements DiskCache { 
  private static final String TAG = "DiskLruCacheWrapper"; 
  private static final int APP_VERSION = 1; 
  private static final int VALUE_COUNT = 1; 
  private static DiskLruCacheWrapper wrapper; 
  private final SafeKeyGenerator safeKeyGenerator; 
  private final File directory; 
  private final long maxSize; 
  private final DiskCacheWriteLocker writeLocker = new DiskCacheWriteLocker(); 
  private DiskLruCache diskLruCache; 
  @SuppressWarnings("deprecation") 
  public static DiskCache create(File directory, long maxSize) { 
    return new DiskLruCacheWrapper(directory, maxSize); 
  } 
  @Deprecated 
  @SuppressWarnings({"WeakerAccess", "DeprecatedIsStillUsed"}) 
  protected DiskLruCacheWrapper(File directory, long maxSize) { 
    this.directory = directory; 
    this.maxSize = maxSize; 
    this.safeKeyGenerator = new SafeKeyGenerator(); 
  } 
  private synchronized DiskLruCache getDiskCache() throws IOException { 
    if (diskLruCache == null) { 
      diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize); 
    } 
    return diskLruCache; 
  } 
  @Override 
  public File get(Key key) { 
    String safeKey = safeKeyGenerator.getSafeKey(key); 
    File result = null; 
    try { 
      final DiskLruCache.Value value = getDiskCache().get(safeKey); 
      if (value != null) { 
        result = value.getFile(0); 
      } 
    } catch (IOException e) { 
      ... 
    } 
    return result; 
  } 
  @Override 
  public void put(Key key, Writer writer) { 
    String safeKey = safeKeyGenerator.getSafeKey(key); 
    writeLocker.acquire(safeKey); 
    try { 
      try { 
        DiskLruCache diskCache = getDiskCache(); 
        Value current = diskCache.get(safeKey); 
        ... 
        DiskLruCache.Editor editor = diskCache.edit(safeKey); 
        ... 
        try { 
          File file = editor.getFile(0); 
          if (writer.write(file)) { 
            editor.commit(); 
          } 
        } finally { 
          editor.abortUnlessCommitted(); 
        } 
      } catch (IOException e) { 
        ... 
      } 
    } finally { 
      writeLocker.release(safeKey); 
    } 
  } 
  ... 
} 

里面包装了一个DiskLruCache,该类主要是为DiskLruCache提供了一个根据Key生成safeKey的SafeKeyGenerator以及写锁DiskCacheWriteLocker。

回到GlideBuilder.build(Context)中,diskCacheFactory会被传进Engine中,在Engine的构造方法中会被包装成为一个LazyDiskCacheProvider
在被需要的时候调用getDiskCache()方法,这样就会调用factory的build()方法返回一个DiskCache。代码如下:

# Engine.java
Engine(
      MemoryCache cache,
      DiskCache.Factory diskCacheFactory,
      GlideExecutor diskCacheExecutor,
      GlideExecutor sourceExecutor,
      GlideExecutor sourceUnlimitedExecutor,
      GlideExecutor animationExecutor,
      Jobs jobs,
      EngineKeyFactory keyFactory,
      ActiveResources activeResources,
      EngineJobFactory engineJobFactory,
      DecodeJobFactory decodeJobFactory,
      ResourceRecycler resourceRecycler,
      boolean isActiveResourceRetentionAllowed) {
    this.cache = cache;
    this.diskCacheProvider = new LazyDiskCacheProvider(diskCacheFactory);
    ...
// 省略部分代码
}
private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider { 
    // 省略部分代码
    ... 
    @Override 
    public DiskCache getDiskCache() { 
      if (diskCache == null) { 
        synchronized (this) { 
          if (diskCache == null) { 
            diskCache = factory.build(); 
          } 
          if (diskCache == null) { 
            diskCache = new DiskCacheAdapter(); 
          } 
        } 
      } 
      return diskCache; 
    } 
  } 

LazyDiskCacheProvider会在Engine后面的初始化流程中作为入参传到DecodeJobFactory的构造器。在DecodeJobFactory创建DecodeJob时也会作为入参会传进去,DecodeJob中会以全局变量保存此LazyDiskCacheProvider,在资源加载完毕并展示后,会进行缓存的存储。同时,DecodeJob也会在DecodeHelper初始化时,将此DiskCacheProvider设置进去,供ResourceCacheGenerator、DataCacheGenerator读取缓存,供SourceGenerator写入缓存。

3. ActiveResources

ActiveResources在Engine的构造器中被创建,在ActiveResources的构造器中会启动一个后台优先级级别(THREAD_PRIORITY_BACKGROUND)的线程,在该线程中会调用cleanReferenceQueue()方法一直循环清除ReferenceQueue中的将要被GC的Resource。

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

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

先来看看ActiveResources的activate方法(保存)、deactivate方法(删除)的方法。

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(); 
    } 
  } 

activate方法会将参数封装成为一个ResourceWeakReference,然后放入map中,如果对应的key之前有值,那么调用之前值的reset方法进行清除。deactivate方法先在map中移除,然后调用resource的reset方法进行清除。ResourceWeakReference继承WeakReference,内部只是保存了Resource的一些属性。

图片资源是什么时候从ActiveResource中缓存至内存的?

ResourceWeakReference继承WeakReference,内部只是保存了Resource的一些属性。

static final class ResourceWeakReference extends WeakReference<EngineResource<?>> { 
  @SuppressWarnings("WeakerAccess") @Synthetic final Key key; 
  @SuppressWarnings("WeakerAccess") @Synthetic final boolean isCacheable; 
  @Nullable @SuppressWarnings("WeakerAccess") @Synthetic Resource<?> resource; 
  @Synthetic 
  @SuppressWarnings("WeakerAccess") 
  ResourceWeakReference( 
      @NonNull Key key, 
      @NonNull EngineResource<?> referent, 
      @NonNull ReferenceQueue<? super EngineResource<?>> queue, 
      boolean isActiveResourceRetentionAllowed) { 
   // focus
    super(referent, queue); 
    this.key = Preconditions.checkNotNull(key); 
    this.resource = 
        referent.isCacheable() && isActiveResourceRetentionAllowed 
            ? Preconditions.checkNotNull(referent.getResource()) : null; 
    isCacheable = referent.isCacheable(); 
  } 
} 

其构造方法中调用了super(referent, queue),这样做可以让将要被GC的对象放入到ReferenceQueue中。而ActiveResources.cleanReferenceQueue()方法会一直尝试从queue中获取将要被GC的resource,然后调用cleanupActiveReference方法将resource从activeEngineResources中移除。cleanupActiveReference方法代码如下:

#ActiveResource.java
void cleanupActiveReference(@NonNull ResourceWeakReference ref) { 
    synchronized (listener) { 
      synchronized (this) { 
        // 移除active资源 
        activeEngineResources.remove(ref.key); 
        if (!ref.isCacheable || ref.resource == null) { 
          return; 
        } 
        // 构造新的 Resource 
        EngineResource<?> newResource = 
            new EngineResource<>(ref.resource, /*isCacheable=*/ true, /*isRecyclable=*/ false); 
        newResource.setResourceListener(ref.key, listener); 
        // 回调Engine的onResourceReleased方法 
        // 这会导致此资源从active变成memory cache状态 
        listener.onResourceReleased(ref.key, newResource); 
      } 
    } 
  } 

Engine实现了EngineResource.ResourceListener,此处的listener就是Engine,最终会回调Engine.onResourceReleased。

#Engine.java
@Override 
  public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) { 
    activeResources.deactivate(cacheKey); 
    if (resource.isCacheable()) { 
      cache.put(cacheKey, resource); 
    } else { 
      resourceRecycler.recycle(resource); 
    } 
  } 

如果资源可以被缓存,则缓存到 memory cache,否则对资源进行回收。

也就是说当前图片资源未在使用中时,会先从ActiveResource的弱引用缓存中将其移除,然后将该资源添加到内存缓存中。

缓存的存取过程

内存缓存

现在我们来分析下缓存的存取流程,加载开始入口从Engine.load()开始,先看下对这个方法的注释:

  • 会先检查(Active Resources),如果有就直接返回,Active Resources没有被引用的资源会放入Memory Cache,如果Active Resources没有,会往下走。
  • 检查Memory Cache中是否有需要的资源,如果有就返回,Memory Cache中没有就继续往下走。
  • 检查当前在运行中的job中是否有该资源的下载,有就在现有的job中直接添加callback返回,不重复下载,当然前提是计算得到的key是一致的,如果还是没有,就会构造一个新的job开始新的工作。
public synchronized <R> LoadStatus load(...) { 
  EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations, 
      resourceClass, transcodeClass, options); 
// focus1
  EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable); 
  if (active != null) { 
    cb.onResourceReady(active, DataSource.MEMORY_CACHE); 
    return null; 
  } 
// focus2
  EngineResource<?> cached = loadFromCache(key, isMemoryCacheable); 
  if (cached != null) { 
    cb.onResourceReady(cached, DataSource.MEMORY_CACHE); 
    return null; 
  } 
// focus3
  EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache); 
  if (current != null) { 
    current.addCallback(cb, callbackExecutor); 
    return new LoadStatus(cb, current); 
  } 
  EngineJob<R> engineJob = 
      engineJobFactory.build(...); 
  DecodeJob<R> decodeJob = 
      decodeJobFactory.build(...); 
  jobs.put(key, engineJob); 
  engineJob.addCallback(cb, callbackExecutor); 
// focus 4
  engineJob.start(decodeJob); 
  return new LoadStatus(cb, engineJob); 
} 

先看到 focus 1,这一步会从 ActiveResources 中加载资源,首先判断是否使用内存缓存,否的话返回null;否则到 ActiveResources 中取数据:


// Engine.java
 @Nullable
 private EngineResource<?> loadFromActiveResources(Key key, boolean isMemoryCacheable) {
   if (!isMemoryCacheable) {
     return null;
   }
   EngineResource<?> active = activeResources.get(key);
   if (active != null) {
     active.acquire();
   }
 
   return active;
 }

接着回到Engine.load()中继续看到focus 2,如果在cache中找到就是remove掉,然后返回EngineResource,其中需要EngineResource进行acquire一下,这个后面再看,然后会把资源移到ActiveResources中,也就是上面提到的缓存:

// Engine.java
  private final MemoryCache cache;
 
  private EngineResource<?> loadFromCache(Key key, boolean isMemoryCacheable) {
    if (!isMemoryCacheable) {
      return null;
    }
 
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    return cached;
  }
 
  private EngineResource<?> getEngineResourceFromCache(Key key) {
    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, true /*isMemoryCacheable*/, true /*isRecyclable*/);
    }
    return result;
  }

其中cache是MemoryCache接口的实现,如果没设置,默认在build的时候是LruResourceCache, 也就是熟悉的LRU Cache:


// GlideBuilder.java
if (memoryCache == null) {
   memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
}

关于LruCache,前面已经分析过了。

回到Engine.load方法,从缓存加载成功后的回调cb.onResourceReady(cached, DataSource.MEMORY_CACHE);可以看到:active状态的资源和memory cache状态的资源都是DataSource.MEMORY_CACHE,并且加载的资源都是 EngineResource 对象,该对象内部采用了引用计数去判断资源是否被释放,如果引用计数为0,那么会调用listener.onResourceReleased(key, this)方法通知外界此资源已经释放了。这里的listener是ResourceListener类型的接口,只有一个onResourceReleased(Key key, EngineResource resource)方法,Engine实现了该接口,此处的listener就是Engine。在Engine.onResourceReleased方法中会判断资源是否可缓存,可缓存则将此资源放入memory cache中,否则回收掉该资源,代码如下:

public synchronized void onResourceReleased(Key cacheKey, EngineResource<?> resource) { 
    // 从activeResources中移除 
    activeResources.deactivate(cacheKey); 
    if (resource.isCacheable()) { 
      // 存入 MemoryCache 
      cache.put(cacheKey, resource); 
    } else { 
      resourceRecycler.recycle(resource); 
    } 
  } 

思路再拉到Engine.load()的流程中,第一次加载的时候activeResources和memoryCache中都没有缓存的,判断当前是否有EngineJob在运行,,如果job已经在运行了,那么直接添加一个回调后返回LoadStatus,这个可以允许用户取消任务:

// Engine.java
EngineJob<?> current = jobs.get(key, onlyRetrieveFromCache);
if (current != null) {
   current.addCallback(cb);
   if (VERBOSE_IS_LOGGABLE) {
      logWithTimeAndKey("Added to existing load", startTime, key);
    }
   return new LoadStatus(cb, current);
}
 
// LoadStatus
  public static class LoadStatus {
    private final EngineJob<?> engineJob;
    private final ResourceCallback cb;
 
    LoadStatus(ResourceCallback cb, EngineJob<?> engineJob) {
      this.cb = cb;
      this.engineJob = engineJob;
    }
 
    public void cancel() {
      engineJob.removeCallback(cb);
    }
  }

接着往下看到focus 4, 到这里就需要创建后台任务去拉取磁盘文件或者发起网络请求。具体就是通过DecodeJob和EngineJob去加载资源。

DecoceJob实现了Runnable接口,然后会被EngineJob.start方法提交到对应的线程池中去执行。在DecoceJob的run方法中,会依次从ResourceCacheGeneratorDataCacheGenerator中去取缓存数据,当这两者都取不到的情况下,会交给```SourceGenerator````加载网络图片或者本地资源。resource资源和data资源都是磁盘缓存中的资源。

ResourceCacheGenerator 、DataCacheGenerator和SourceGenerator

这里ResourceCacheGeneratorDataCacheGeneratorSourceGenerator均实现了DataFetcherGenerator接口。

  • ResourceCacheGenerator:从已经完成剪裁或者转换的资源缓存文件中生成DataFetchers;
  • DataCacheGenerator:从未修改的原始源数据的缓存文件中生成DataFetchers;
  • SourceGenerator:从原始数据中生成DataFetchers;

这三个类中的前两个就是Glide三级缓存中的第二级缓存,文件缓存,前者ResourceCacheGenerator存储的是经过剪裁或转换的图片数据,后者DataCacheGenerator存储的就是未经任何修改的原始数据。正好对应着磁盘缓存策略中的RESOURCEDATA

什么是DataFetcher?

就是数据加载器,他有个loadData方法用来从资源加载数据,这个方法必须运行在子线程中,如果缓存中没有数据,就会通过这个方法去加载原始数据。
举个例子,比如要通过一个网络url图片地址加载图片,那么需要通过SourceGenerator生成相应的DataFetcher通过网络获取数据;从网络上获取到数据流后,不进行任何改变会被存储到本地文件缓存中(这个本地文件默认的目录是context.getCacheDir()方法获取到的目录,文件大小250M),这个存储之前先修改runReason=SWITCH_TO_SOURCE_SERVICE,然后重启一个线程,通过SourceGenerator对象去做存储操作,而DataCacheGenerator就表示获取存储的数据,然后经过解码然后将数据显示到imageview。操作完成后会往内存中再存一份数据,那么ResourceCacheGenerator就表示获取这些已经经过解码剪裁的缓存文件。

磁盘缓存

先构造两个job,一个是EngineJob,另外一个DecodeJob,其中DecodeJob会根据需要解码的资源来源分成下面几个阶段:

private enum Stage {
    /** The initial stage. */
    INITIALIZE,
    /** Decode from a cached resource. */
    RESOURCE_CACHE,
    /** Decode from cached source data. */
    DATA_CACHE,
    /** Decode from retrieved source. */
    SOURCE,
    /** Encoding transformed resources after a successful load. */
    ENCODE,
    /** No more viable stages. */
    FINISHED,
  }

在构造DecodeJob时会把状态置为INITIALIZE。

构造完两个 Job 后会调用 EngineJob.start

(DecodeJob),首先会调用getNextStage来确定下一个阶段,这里面跟DiskCacheStrategy这个传入的磁盘缓存策略有关。

磁盘策略有下面几种:

  • ALL: 缓存原始数据和转换后的数据
  • NONE: 不缓存
  • DATA: 原始数据,未经过解码或者转换
  • RESOURCE: 缓存经过解码的数据
  • AUTOMATIC(默认): 根据EncodeStrategyDataSource等条件自动选择合适的缓存方

默认的AUTOMATIC方式是允许解码缓存的RESOURCE:

public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
 // 省略部分代码
    @Override
    public boolean decodeCachedResource() {
      return true;
    }
 
    @Override
    public boolean decodeCachedData() {
      return true;
    }
  };

所以在 getNextStage 会先返回

Stage.RESOURCE_CACHE,然后在start中会返回diskCacheExecutor,然后开始执行DecodeJob:

// EngineJob.java
public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
 //到这里之前一直都在主线程,下面这句代码就会切换到子线程
    executor.execute(decodeJob);
}
 
// DecodeJob.java
  boolean willDecodeFromCache() {
    Stage firstStage = getNextStage(Stage.INITIALIZE);
    return firstStage == Stage.RESOURCE_CACHE || firstStage == Stage.DATA_CACHE;
  }
 
  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);
    }
  }

DecodeJob会回调run()开始执行, run()中调用runWrapped执行工作,这里runReason还是RunReason.INITIALIZE ,根据前面的分析指导这里会获得一个ResourceCacheGenerator,然后调用runGenerators:


// DecodeJob.java 
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);
    }
  }
 
  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);
    }
  }

在 runGenerators 中,会调用 startNext,目前

currentGenerator是ResourceCacheGenerator, 那么就是调用它的startNext方法:

@Override 
  public boolean startNext() { 
    // list里面只有一个GlideUrl对象 
    List<Key> sourceIds = helper.getCacheKeys(); 
    if (sourceIds.isEmpty()) { 
      return false; 
    } 
    // 获得了三个可以到达的registeredResourceClasses 
    // GifDrawable、Bitmap、BitmapDrawable 
    List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses(); 
    if (resourceClasses.isEmpty()) { 
      if (File.class.equals(helper.getTranscodeClass())) { 
        return false; 
      } 
      throw new IllegalStateException( 
         "Failed to find any load path from " + helper.getModelClass() + " to " 
             + helper.getTranscodeClass()); 
    } 
    // 遍历sourceIds中的每一个key、resourceClasses中每一个class,以及其他的一些值组成key 
    // 尝试在磁盘缓存中以key找到缓存文件 
    while (modelLoaders == null || !hasNextModelLoader()) { 
      resourceClassIndex++; 
      if (resourceClassIndex >= resourceClasses.size()) { 
        sourceIdIndex++; 
        if (sourceIdIndex >= sourceIds.size()) { 
          return false; 
        } 
        resourceClassIndex = 0; 
      } 
      Key sourceId = sourceIds.get(sourceIdIndex); 
      Class<?> resourceClass = resourceClasses.get(resourceClassIndex); 
      Transformation<?> transformation = helper.getTransformation(resourceClass); 
      // PMD.AvoidInstantiatingObjectsInLoops Each iteration is comparatively expensive anyway, 
      // we only run until the first one succeeds, the loop runs for only a limited 
      // number of iterations on the order of 10-20 in the worst case. 
      // 构造key 
      currentKey = 
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops 
              helper.getArrayPool(), 
              sourceId, 
              helper.getSignature(), 
              helper.getWidth(), 
              helper.getHeight(), 
              transformation, 
              resourceClass, 
              helper.getOptions()); 
      // 查找缓存文件 
      cacheFile = helper.getDiskCache().get(currentKey); 
      // 如果找到了缓存文件,循环条件则会为false,退出循环 
      if (cacheFile != null) { 
        sourceKey = sourceId; 
        // 1. 找出注入时以File.class为modelClass的注入代码 
        // 2. 调用所有注入的factory.build方法得到ModelLoader 
        // 3 .过滤掉不可能处理model的ModelLoader 
        // 此时的modelLoaders值为: 
        // [ByteBufferFileLoader, FileLoader, FileLoader, UnitModelLoader] 
        modelLoaders = helper.getModelLoaders(cacheFile); 
        modelLoaderIndex = 0; 
      } 
    } 
    // 如果找到了缓存文件,hasNextModelLoader()方法则会为true,可以执行循环 
    // 没有找到缓存文件,则不会进入循环,会直接返回false 
    loadData = null; 
    boolean started = false; 
    while (!started && hasNextModelLoader()) { 
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++); 
      // 在循环中会依次判断某个ModelLoader能不能加载此文件 
      loadData = modelLoader.buildLoadData(cacheFile, 
          helper.getWidth(), helper.getHeight(), helper.getOptions()); 
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { 
        started = true; 
        // 如果某个ModelLoader可以,那么就调用其fetcher进行加载数据        
// 加载成功或失败会通知自身 
        loadData.fetcher.loadData(helper.getPriority(), this); 
      } 
    } 
    return started; 
  } 

找缓存时key的类型为ResourceCacheKey,生成ResourceCacheKey之后会根据key去磁盘缓存中查找cacheFile = helper.getDiskCache().get(currentKey);

helper.getDiskCache()返回DiskCache接口,它的实现类是DiskLruCacheWrapper,看下DiskLruCacheWrapper.get方法。

# DiskLruCacheWrapper.java
@Override 
  public File get(Key key) { 
    String safeKey = safeKeyGenerator.getSafeKey(key); 
    ... 
    File result = null; 
    try { 
      final DiskLruCache.Value value = getDiskCache().get(safeKey); 
      if (value != null) { 
        result = value.getFile(0); 
      } 
    } catch (IOException e) { 
      ... 
    } 
    return result; 
  } 

这里调用SafeKeyGenerator生成了一个String类型的SafeKey,实际上就是对原始key中每个字段都使用SHA-256加密,然后将得到的字节数组转换为16进制的字符串。生成SafeKey后,接着根据SafeKey去DiskCache里面找对应的缓存文件,然后返回文件。

回到ResourceCacheGenerator.startNext方法中,如果找到了缓存会调用loadData.fetcher.loadData(helper.getPriority(), this);这里的 fetcher 是 ByteBufferFetcherByteBufferFetcher的loadData方法中最终会执行callback.onDataReady(result)这里callback是ResourceCacheGenerator

public void onDataReady(Object data) { 
    cb.onDataFetcherReady(sourceKey, data, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE, 
        currentKey); 
  } 

ResourceCacheGeneratoronDataReady方法又会回调DecodeJobonDataFetcherReady方法进行后续的解码操作。

如果ResourceCacheGenerator没有找到缓存,就会交给DataCacheGenerator继续查找缓存。该类大体流程和ResourceCacheGenerator一样,有点不同的是,DataCacheGenerator的构造器有两个构造器,其中的DataCacheGenerator(List, DecodeHelper, FetcherReadyCallback)构造器是给SourceGenerator准备的。因为如果没有磁盘缓存,那么从源头加载后,肯定需要进行磁盘缓存操作的。所以,SourceGenerator会将加载后的资源保存到磁盘中,然后转交给DataCacheGenerator从磁盘中取出交给ImageView展示。

看下DataCacheGenerator.startNext:

public boolean startNext() { 
    while (modelLoaders == null || !hasNextModelLoader()) { 
      sourceIdIndex++; 
      if (sourceIdIndex >= cacheKeys.size()) { 
        return false; 
      } 
      Key sourceId = cacheKeys.get(sourceIdIndex); 
      ... 
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature()); 
      cacheFile = helper.getDiskCache().get(originalKey); 
      ... 
    while (!started && hasNextModelLoader()) { 
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++); 
      loadData = 
          modelLoader.buildLoadData(cacheFile, helper.getWidth(), helper.getHeight(), 
              helper.getOptions()); 
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) { 
        started = true; 
        loadData.fetcher.loadData(helper.getPriority(), this); 
      } 
    } 
    return started; 
  } 

这里的originalKey是DataCacheKey类型的,DataCacheKey构造方法如下:

DataCacheKey(Key sourceKey, Key signature)

这里的sourceKey和signature与ResourceCacheKey中的两个变量一致,从这里就可以看出:DataCache缓存的是原始的数据,ResourceCache缓存的是是被解码、转换后的数据。

如果DataCacheGenerator没有取到缓存,那么会交给SourceGenerator从源头加载。

总结一下:
  • 首先在ResourceCacheGenerator没有取到缓存,那么它的startNext就会返回false,在runGenerators中就会进入循环体内:
  • 接着会重复上面执行getNextStage,由于现在Stage已经是RESOURCE_CACHE,所以接下来会返回DataCacheGenerator,执行逻辑和上面的ResourceCacheGenerator是一样的,如果还是没有找到需要的,进入循环体内。
  • 此时getNextStage会根据用于是否设置只从磁盘中获取资源,如果是就会通知失败,回调onLoadFailed;如果不是就设置当前Stage为Stage.SOURCE,接着往下走。
  • 状态就会进入循环内部的if条件逻辑里面,调用reschedule。
  • 在reschedule把runReason设置成SWITCH_TO_SOURCE_SERVICE,然后通过callback回调。
#DecodeJob.runGenerators()
 if (stage == Stage.SOURCE) {
        reschedule();
        return;
      }

 public void reschedule() {
    runReason = RunReason.SWITCH_TO_SOURCE_SERVICE;
    callback.reschedule(this);
  }
  • DecodeJob中的callback是EngineJob传递过来的,所以现在返回到EngineJob。
    -在EngineJob中通过
    getActiveSourceExecutor切换到网络线程池中,注意在此之前执行文件缓存的线程池一直都是diskCacheExecutor,执行DecodeJob,下面就准备开始发起网络请求。
#EngineJob.java
@Override
  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);
  }

看下SourceGenerator的startNext方法。

@Override 
  public boolean startNext() { 
    // 首次运行dataToCache为null 
    if (dataToCache != null) { 
      Object data = dataToCache; 
      dataToCache = null; 
      cacheData(data); 
    } 
    // 首次运行sourceCacheGenerator为null 
    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) { 
      return true; 
    } 
    sourceCacheGenerator = null; 
    loadData = null; 
    boolean started = false; 
    while (!started && hasNextModelLoader()) { 
      loadData = helper.getLoadData().get(loadDataListIndex++); 
      if (loadData != null 
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource()) 
          || helper.hasLoadPath(loadData.fetcher.getDataClass()))) { 
        started = true; 
        loadData.fetcher.loadData(helper.getPriority(), this); 
      } 
    } 
    return started; 
  } 

这里的DataFetcher其实是HttpUrlFetcher,由它来发起网络请求。


// HttpUrlFetcher.java
@Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      callback.onDataReady(result);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }

加载成功后,依然会回调SourceGenerator的onDataReady方法。

@Override 
  public void onDataReady(Object data) { 
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy(); 
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) { 
      dataToCache = data; 
      // cb 为 DecodeJob 
      cb.reschedule(); 
    } else { 
      // cb 为 DecodeJob 
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher, 
          loadData.fetcher.getDataSource(), originalKey); 
    } 
  } 

先判断获取到的数据是否需要进行磁盘缓存,如果需要磁盘缓存,则经过DecodeJob、EngineJob的调度,重新调用SourceGenerator.startNext方法,此时dataToCache已经被赋值,则会调用cacheData(data);进行磁盘缓存的写入,并转交给DataCacheGenerator完成后续的处理;否则就通知DecodeJob已经加载成功。

DataCacheGenerator前面已经分析过了,就是用来加载本地原始数据的。

先看下SourceGenerator的startNext方法中调用的SourceGenerator.cacheData(data)。

# SourceGenerator.java
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()); 
//根据data,key,signature 创建缓存key。这个key是DataCacheKey类型的,存储是原始数据
      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); 
  } 

cacheData方法先构建了一个DataCacheKey将data写入了磁盘,然后new了一个DataCacheGenerator赋值给sourceCacheGenerator。回到startNext继续向下执行,此时sourceCacheGenerator不为空,就调用其startNext()方法从磁盘中加载刚写入磁盘的数据,并返回true让DecodeJob停止尝试获取数据。此时,从磁盘缓存中读取数据的逻辑已经完成,接下来是写磁盘缓存。

结论

Glide加载网络数据后, 在返回之前,会先缓存一份原始数据 。

磁盘缓存与内存缓存的关联

磁盘缓存结束后,sourceCacheGenerator被赋值,然后返回,回到startNext方法,接着往下走,会执行sourceCacheGenerator的startNext方法,
此方法最终调用loadData.fetcher.loadData(helper.getPriority(), this);这句话。由于data是流的类型,所以看ByteBufferFetcher

package com.bumptech.glide.load.model.ByteBufferFileLoader.ByteBufferFetcher
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super ByteBuffer> callback) {
  ByteBuffer result;
  try {
  //读取文件获取结果 是ByteBuffer类型
    result = ByteBufferUtil.fromFile(file);
  } catch (IOException e) {
    callback.onLoadFailed(e);
    return;
  }
  //回调 
  callback.onDataReady(result);
}

最终会调用到下面的方法

@Override
  public void onDataFetcherReady(
      Key sourceKey, Object data, DataFetcher<?> fetcher, DataSource dataSource, Key attemptedKey) {
    this.currentSourceKey = sourceKey;
    this.currentData = data;
    this.currentFetcher = fetcher;
    this.currentDataSource = dataSource;
    this.currentAttemptingKey = attemptedKey;
    //如果不是同一个线程,最终会修改runReason为RunReason.DECODE_DATA;并从起一个线程执行decodeFromRetrievedData方法
    if (Thread.currentThread() != currentThread) {
      runReason = RunReason.DECODE_DATA;
      callback.reschedule(this);
    } else {
      try {
      //如果是同一个线程 直接执行decodeFromRetrievedData方法
        decodeFromRetrievedData();
      } finally {
      }
    }
  }

decodeFromRetrievedData中主要调用了notifyEncodeAndRelease方法,在后一个方法中会执行到notifyComplete方法,notifyComplete最终会调用到notifyCallbacksOfResult方法,该方法调用下面的回调方法:

@Override
public synchronized void onEngineJobComplete(
    EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
  // A null resource indicates that the load failed, usually due to an exception.
  if (resource != null && resource.isMemoryCacheable()) {
  //将资源缓存到activeResource中,这里就跟前面的”先从内存的二级缓存加载数据“的逻辑对应上了
    activeResources.activate(key, resource);
  }

  jobs.removeIfCurrent(key, engineJob);
}

在这里会将资源放入activeResources中,资源变为active状态。后面会使用Executors.mainThreadExecutor()调用SingleRequest.onResourceReady回调进行资源的显示。在触发回调前后各有一个地方会对engineResource进行acquire()和release()操作,这两个操作分别发生在notifyCallbacksOfResult()方法的incrementPendingCallbacks、decrementPendingCallbacks()调用中。

CallResourceReady的run方法中也会调用engineResource.acquire(),上面的代码调用结束后,engineResource的引用计数为1。engineResource的引用计数会在RequestManager.onDestory方法中最终调用SingleRequest.clear()方法,SingleRequest.clear()内部调用releaseResource()、Engine.release 进行释放,这样引用计数就变为0。引用计数就变为0后会通知Engine将此资源从active状态变成memory cache状态。如果我们再次加载资源时可以从memory cache中加载,那么资源又会从memory cache状态变成active状态。也就是说,在资源第一次显示后,我们关闭页面,资源会由active变成memory cache;然后我们再次进入页面,加载时会命中memory cache,从而又变成active状态。

小结一下

Glide的三级缓存:第一级缓存内存缓存,内存缓存又分为两种存储形式,弱引用缓存和LRU策略的缓存方式;第二级缓存是本地文件缓存,缓存的文件大小默认是250M,本地缓存会分为存储未经修改的源数据和经过剪裁后的数据;第三级缓存就是网络/数据源缓存。
关于缓存及展示数据:数据加载出来之后,会先在缓存到本地文件中,然后再去本地缓存文件中读取数据进行编码,将编码后的数据再进行一次缓存,接着再内存中进行缓存,最后将数据编码后的图片文件发往主线程设置给target。

回答以下几个问题:

Glide网络请求回来后数据直接返回给用户还是先存再返回

不是直接返回给用户,会在SourceGenerator中构造一个DataCacheGenerator来取数据。简单来说就是先缓存到磁盘,再从磁盘里取数据返回给ImageView添加图片

Glide本地文件IO和网络请求是一个线程吗?

明显不是,本地IO通过diskCacheExecutor,而网络IO通过ActiveSourceExecutor

总结

读取内存缓存时,先从弱引用机制的内存缓存读取,再从LruCache算法机制的内存缓存读取

写入内存缓存时,先写入 弱引用机制 的内存缓存,等到图片不再被使用时,再写入到 LruCache算法机制的内存缓存;

读取磁盘缓存时,先读取转换后图片的缓存,再读取原始图片的缓存。

参考:
Android源码进阶之Glide缓存机制原理详解
Glide缓存流程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容