Glide load源码、缓存机制分析

我们知道Glide链式调用最重要的就是into(),而 into()最终会调用 load()

1.load源码分析

1. load流程

我们来分析一下 load()做了什么事情。

 public <R> LoadStatus load(...) {
        Util.assertMainThread();
        long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

        EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
                resourceClass, transcodeClass, options);
        ...

首先通过工厂模式创建了一个 EngineKey对象key。 看下这个类:

// EngineKey.java
class EngineKey implements Key {
  EngineKey(
      Object model,
      Key signature,
      int width,
      int height,
      Map<Class<?>, Transformation<?>> transformations,
      Class<?> resourceClass,
      Class<?> transcodeClass,
      Options options) {
    ... // setter
  }

  @Override
  public boolean equals(Object o) {
    ...
  }

  @Override
  public int hashCode() {
    ...
  }
}

它的构造函数是赋值,并重写了 equals()hashCode(),除此之外没啥别的函数了。
可以认为它的作用是描述一个Engine,重写eqauls和hashCode的作用还没看出来,但是可以猜想,是便于后续的复用。

load() 接下来走:

// load
    ...
    EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
    if (active != null) {
      cb.onResourceReady(active, DataSource.MEMORY_CACHE);
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return null;
    }
    ...

调用了 loadFromActiveResources(),传入了之前的生成的key。如果得到一个 EngineResource对象,则回调 onResourceReady(), onResourceReady是这个加载图片的出口。来看下 loadFromActiveResources()做了啥:

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

    return active;
  }

第一个if是如果 isMemoryCacheable为false则返回null,从字面意思上可以知道这个字段的意思是 是否开启内存缓存
然后调用 EngineResource<?> active = activeResources.get(key),它是通过key去ActiveResources里面取一个 EngineResource:

// ActiveResources.java
  final Map<Key, ResourceWeakReference> activeEngineResources = new HashMap<>();

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

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

从注释1、2可以看到,ActiveResources维护一个HashMap,Key为 Key类,我们刚刚的EngineKey就是Key的派生类,Value为 Resources的弱引用。
也就是说,如果之前在Map中存放了对应Key的弱引用,则取出这个弱引用中的EngineResource,否则返回null。

可以看出这里实现了一个缓存,由于它是运行时才有这个Map的,所以它是一个 内存缓存,它的存放的是弱引用的图片资源,所以在内存回收时是会被立即回收的。
回到 loadFromActiveResources()中:

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

// EngineResource.java
  void acquire() {
    if (isRecycled) {
      throw new IllegalStateException("Cannot acquire a recycled resource");
    }
    if (!Looper.getMainLooper().equals(Looper.myLooper())) {
      throw new IllegalThreadStateException("Must call acquire on the main thread");
    }
    ++acquired;
  }

如果返回的 active不为null,则调用 active.acquire(),它的作用是给计数器 acquired自增1(在主线程和非回收状态下),这里并不知道为什么要这么做。所以就回去了。这样loadFromActiveResources()就分析完了,往下走:

//Engine.java  #load
     ...
     EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
     if (cached != null) {
       cb.onResourceReady(cached, DataSource.MEMORY_CACHE);
       if (VERBOSE_IS_LOGGABLE) {
         logWithTimeAndKey("Loaded resource from cache", startTime, key);
       }
       return null;
     }
     ...

loadFromCache()中获取EngineResource,如果拿得到就调用 onResourceRead()返回。来看下这个方法:

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

这个方法和之前的 loadFromActiveResources差别不大,先看看 getEngineResourceFromCache(key)

// Engine.java
  private final MemoryCache cache;
  
  private EngineResource<?> getEngineResourceFromCache(Key key) {
    Resource<?> cached = cache.remove(key);  // 1

    final EngineResource<?> result;
    if (cached == null) {
      result = null;
    } else if (cached instanceof EngineResource) {
      result = (EngineResource<?>) cached;
    } else {
      result = new EngineResource<>(cached, true /*isMemoryCacheable*/, true /*isRecyclable*/);
    }
    return result;
  }

这个方法就是从 MemoryCache中拿出一个EngineResource并返回。那我们要去看看 MemoryCache做了啥,但是MemoryCache是一个接口类,没有实现,在向前找的时候发现它是在创建 Glide initalizeGlide() 的过程中创建的,这里直接看创建的代码:

// GlideBuilder.java
 @NonNull
  Glide build(@NonNull Context context) { 
    ....
    if (memoryCache == null) {
      memoryCache = new LruResourceCache(memorySizeCalculator.getMemoryCacheSize());
    }
    ....
  }

// LruResourceCache.java
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
   ...
}

LruResourceCache实现了MemoryCache,并继承了LruCache。
这里简单的描述一下LruCache,它是最近最少使用策略,实现原理是 accessOrder为true的 LinkedHashMap。所以在getEngineResourceFromCache的注释1中,它就是调用了 LinkedHashMap.remove(key)来看Map中是否有key对应的 EngineResources。

回到 loadFromCache中:

// Engine.java  #loadFromCache
    ...
    EngineResource<?> cached = getEngineResourceFromCache(key);
    if (cached != null) {
      cached.acquire();
      activeResources.activate(key, cached);
    }
    ....

如果remove到的资源是不为null的,则调用 acquire()方法,并且调用 activeResources.activate(key, cached),我们看看这个方法做了什么:

// ActiveResources.java
  void activate(Key key, EngineResource<?> resource) {
    ResourceWeakReference toPut =
        new ResourceWeakReference(
            key,
            resource,
            getReferenceQueue(),
            isActiveResourceRetentionAllowed);

    ResourceWeakReference removed = activeEngineResources.put(key, toPut); 
    if (removed != null) {
      removed.reset();
    }
  }

它把 key和EngineResource作为元素 put到了 ActiveResources的 HashMap中。便于以后 loadFromActiveResources去拿。
然后最后return为null。 loadFromCache()就结束了。可以看出来 LruCache也是在运行时产生的,所以它也是内存缓存

这里出现了一个问题,这里出现了两个内存缓存,一个是HashMap缓存,一个是LruResourcesCache的缓存,在前者拿资源拿不到的情况下,去拿后者,如果后者拿到了,会把该资源放到前者中缓存。乍一看是没事找事,为什么不把所有的资源都放在一个cache下存储呢?这需要往后面的代码看。

接下来继续load()函数中:

    private final Jobs jobs;
// Engine.java #load
    ...
    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);
    }
   ....

通过 Jobs去拿一个 EngineJob,如果 EngineJob不为null,则调用其 addCallback(),这个方法最终也会调用 onResourceReady(),并返回一个 LoadStatus 。先来看看 jobs的get方法:

// Jobs.java
  private final Map<Key, EngineJob<?>> jobs = new HashMap<>();
  private final Map<Key, EngineJob<?>> onlyCacheJobs = new HashMap<>();
  
  EngineJob<?> get(Key key, boolean onlyRetrieveFromCache) {
    return getJobMap(onlyRetrieveFromCache).get(key);
  }

  private Map<Key, EngineJob<?>> getJobMap(boolean onlyRetrieveFromCache) {
    return onlyRetrieveFromCache ? onlyCacheJobs : jobs;
  }

onlyRetrieveFromCache这个字段中文意思为:是否只从Cache中搜索。默认情况下是 false。它就会去 jobs这个HashMap中拿EngineJob。这里也是一个缓存,但他并不是之间缓存图片资源。

EngineJob它不是一个图片资源,那它是什么呢?这里还不是很清晰,先往load下面走,

// Engine.java  #load
   ...
   EngineJob<R> engineJob =
        engineJobFactory.build(
            key,
            isMemoryCacheable,
            useUnlimitedSourceExecutorPool,
            useAnimationPool,
            onlyRetrieveFromCache);

    DecodeJob<R> decodeJob =
        decodeJobFactory.build(
            glideContext,
            model,
            key,
            signature,
            width,
            height,
            resourceClass,
            transcodeClass,
            priority,
            diskCacheStrategy,
            transformations,
            isTransformationRequired,
            isScaleOnlyOrNoTransform,
            onlyRetrieveFromCache,
            options,
            engineJob);

    jobs.put(key, engineJob);

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

这里是load方法的最后一步,在上述三种缓存都命中不到资源的情况下,会创建一个 EngineJob和一个 DecodeJob,将 key和engineJob一起绑定加入到 Jobs的HashMap,这样之后上面的jobs缓存就有资源可以找,然后调用 EngineJob.addCallbackEngine.start()
这里的重点是 start方法,它是没有内存缓存后,去做的事情,所以这里算是一个新的开始。 也就是说,load可以看成两个部分,这里开始就是第二个部分。

1.2 EngineJob的start方法解析

// EngineJob.java
  private final GlideExecutor diskCacheExecutor;
  private final GlideExecutor sourceExecutor;
  private final GlideExecutor sourceUnlimitedExecutor;
  private final GlideExecutor animationExecutor;
  
  public void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }

EngineJob里面有线程池执行器 GlideExecutor,它就是一个ExecutorService,因为加载图片是一个耗时操作,所以放到子线程做,所以这里出现了 Executor,其次,在EngineJob中,exeutor执行器并不是只有一个:
它会先根据缓存策略,拿到对应executor,比如这里的 :

// EngineJob.java 
 GlideExecutor executor = decodeJob.willDecodeFromCache()
        ? diskCacheExecutor
        : getActiveSourceExecutor();

就会根据 “是否在Cache中解码” 这个条件来拿 disCacheExecutor,还是其他三个中的执行器。
在默认情况下, DiskCacheStategey是 AUTOMATIC的,这里就是true,即用的是 diskCacheExecutor。
接下来就会拿着这个线程执行器去执行 DecodeJob,就是调用它的 run()

// DecodeJob.java
  @Override
  public void run() {
    ....
    DataFetcher<?> localFetcher = currentFetcher;
    try {
      if (isCancelled) {
        notifyFailed();
        return;
      }
      runWrapped();
    } catch (Throwable t) {
     ....
    } finally {
     ...
    }
  }

notifyFailed()是通知失败的方法,然后没取消,就调用 runWrapped()

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

这里根据 runReason分成了三种情况:

  • INITIALIZE
    在第一次我们提交执行任务的状态
  • SWITCH_TO_SOURCE_SERVICE
    当我们想要从 DiskCache 的做法切换到 Source执行的做法(这里我也不知道干嘛的)
  • DECODE_DATA
    该状态标识着:当我们在子线程中拿到了一个我们从未拥有过的资源,这个时候我们需要切换回主线程,去让主线程得到这个资源。

当然了,上面的三个是执行 DecodeJob的原因,它强调的是原因,DecodeJob又维护了一套当前的状态,和上面产生了联系:

  • INITIALIZE
    和上面初始时一样
  • RESOURCE_CACHE
    根据一个 缓存资源 进行 解码(Decode)
  • DATA_CACHE
    根据一个 缓存的源数据 进行 解码(Decode)
  • SOURCE
    根据 获取到的源数据 进行 解码
  • ENCODE
    在一个 资源成功加载后, 为了将它缓存起来,进行编码
  • FINISHED
    要结束了

假设我们是第一次进入到这个方法,那我们的runReason是 INITIALIZE,接下来会调用 getNextState(Stage.INITIALIZE):

// DecodeJob.java
  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:
        return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
      case SOURCE:
      case FINISHED:
        return Stage.FINISHED;
      default:
        throw new IllegalArgumentException("Unrecognized stage: " + current);
    }
  }

// 这里是默认情况下的 DiskCacheStaratey(磁盘)的缓存模式:
// DiskCacheStrategy。java
  public static final DiskCacheStrategy AUTOMATIC = new DiskCacheStrategy() {
    @Override
    public boolean isDataCacheable(DataSource dataSource) {
      return dataSource == DataSource.REMOTE;
    }

    @Override
    public boolean isResourceCacheable(boolean isFromAlternateCacheKey, DataSource dataSource,
        EncodeStrategy encodeStrategy) {
      return ((isFromAlternateCacheKey && dataSource == DataSource.DATA_DISK_CACHE)
          || dataSource == DataSource.LOCAL)
          && encodeStrategy == EncodeStrategy.TRANSFORMED;
    }

    @Override
    public boolean decodeCachedResource() {
      return true;
    }

    @Override
    public boolean decodeCachedData() {
      return true;
    }
  };

在 INITIALIZE状态下调用了 getNextStage(),state就变成了 Stage.RESOURCE_CACHE,接下来在 runWrapped中:
调用了 currentGenerator = getNextGenerator()

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

返回了一个 ResourceCacheGenerator,它现在是做什么的还不知道。
继续往下走 ,会调用 runGenerators()

// DecodeJob.java
  private void runGenerators() {
    currentThread = Thread.currentThread();
    startFetchTime = LogTime.getLogTime();
    boolean isStarted = false;
    while (!isCancelled && currentGenerator != null
        && !(isStarted = currentGenerator.startNext())) {  // 1
      stage = getNextStage(stage);  //  2
      currentGenerator = getNextGenerator(); // 3

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

这个方法会开启一个 while循环,判断条件中第三个较为关键:调用 currentGenerator.startNext()
如果这个方法的结果为false,则 stage进入到下一个状态, currentGenerator换成下一个执行器。
在当前状态为 Stage.SOURCE时,调用了 reschedule(),然后return,也就是说 SOURCE为当前decodeJob执行的最后一个状态。
如果要一直到最后 SOURCE,那么当前的DecodeJob会走这么个顺序:
(1)DecodeJob的状态:
RESOURCE_CACHE -> DATA_CACHE -> SOURCE
(2)currentGenrator的变化:
ResourceCacheGenerator -> DataCacheGenerator -> SourceGenerator
(3) startNext的执行
ResourceCacheGenerator.startNext() = false -> DataCacheGenerator .startNext() = false

这就是在 startNext()一直为false的情况下。 我们现在就去研究一下这个方法,由于 Generator是一个发生器,在这里分成了两个,所以startNext()有必要拆出来讲解。

startNext()这个方法有点长,并且借助了一个新的类 DecodeHelper的辅助,这个类主要做了组件注册,用来扩展或替换Glide的默认加载,还有负责解码、编码的Registry类。而且我们其实还并不是很清楚 ResourceCacheGenerator、DataCacheGenerator、SourceGenerator这些所关联 DataFetcher的类是做什么的,所以有必要先简单的介绍他们。

1.3 DataFetcher、ModelLoader以及DecodeHelper的介绍

1.关于DataFetcher
它是一个接口,先来看看它的定义:

// DataFetcher.java
public interface DataFetcher<T> {
  interface DataCallback<T> {
    void onDataReady(@Nullable T data);      // 资源已经弄好了
    void onLoadFailed(@NonNull Exception e); //加载失败回调
  }
  void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback);  //加载方法
  void cleanup();  //资源释放
  void cancel();   // 取消加载
  @NonNull
  Class<T> getDataClass();
  @NonNull
  DataSource getDataSource();
}

DataFetcher是Glide的数据加载模块,定义了数据的加载时遵循的框架。
它的使用大概就是:通过调用 loadData()来加载资源,当加载成功后,调用 onDataReady()进行回调。如果失败,调用 onLoadFailed()。非常好懂。
它有很多个实现类,比如:

  • LocalUriFetcher
    使用 ContentResolver 从本地资源的Uri加载数据
  • AssetPathFetcher
    使用 AssetManager从asset path中获取数据的抽象类
  • HttpUrlFetcher
    具体加载的数据类型为 InputStream,从网络Url中获取数据

2.关于ModelLoader
它也是个接口,来看看代码:

public interface ModelLoader<Model, Data> {
  class LoadData<Data> {
    public final Key sourceKey;
    public final List<Key> alternateKeys;
    public final DataFetcher<Data> fetcher;

    public LoadData(@NonNull Key sourceKey, @NonNull DataFetcher<Data> fetcher) {
      this(sourceKey, Collections.<Key>emptyList(), fetcher);
    }

    public LoadData(@NonNull Key sourceKey, @NonNull List<Key> alternateKeys,
        @NonNull DataFetcher<Data> fetcher) {
      this.sourceKey = Preconditions.checkNotNull(sourceKey);
      this.alternateKeys = Preconditions.checkNotNull(alternateKeys);
      this.fetcher = Preconditions.checkNotNull(fetcher);
    }
  }
  @Nullable
  LoadData<Data> buildLoadData(@NonNull Model model, int width, int height,
      @NonNull Options options);
  boolean handles(@NonNull Model model);
}

它是一个工厂类的接口,用于将任意复杂的数据模型转换为具体的数据类型。
复杂的数据模型:通过给Glide设置了的各种属性,比如动画、缩放、模糊等等配置
具体的数据类型:LoadData ---- 由一系列的Key和DataFetcher组成的数据结构

它可以构建像 HttpGlideUrlLoader,内部构建一个HttpUrlFetcher,来进行网络的下载资源
也可以构建 FileLoader,内部构建一个 FileFetcher,来进行文件资源的获取。

它和DataFetcher是一起绑定的。

3. DecodeHelper
具体讲解需要费大量时间,所以可能会对本文出现岔道,所以具体请看这篇 DecodeHelper类相关方法分析
我们在 Glide.with().load(url) 的load中输入了获取图片资源的方式,这个方式可能是String、BitMap、Uri等。而 DecodeHelper的作用就是通过分析这个方式,分析出我们想要获取图片资源,需要哪些 ModelLoader、CacheKeys等。

比如当我们输入的是一个Url的字符串,它就能分析出我们所需的 cacheKey为:[GlideUrl,ObjectKey],从而获取到对应的LoadData

1.4 DataFetcherGenerator.startNext()

在1.2节中讲到,如果说一直要执行到 SOURCE状态,要先执行 ResourceCacheGenerator.startNext()DataCacheGenerator .startNext() = false ,这里看下两个方法的实现。

1.关于ResourceCacheGenerator的实现

// ResourceCacheGenerator.java
  public boolean startNext() {
    List<Key> sourceIds = helper.getCacheKeys();  // 1 
    if (sourceIds.isEmpty()) {   // 2
      return false;
    }
    List<Class<?>> resourceClasses = helper.getRegisteredResourceClasses();  // 3
    if (resourceClasses.isEmpty()) {
      if (File.class.equals(helper.getTranscodeClass())) {
        return false;
      }
    }
    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);
      currentKey =
          new ResourceCacheKey(
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());  // 4
      cacheFile = helper.getDiskCache().get(currentKey);  // 5
      if (cacheFile != null) {
        sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }
    loadData = null;
    boolean started = false;
    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;
  }

有了 1.3节中的介绍,我们可以来解析上面的源码:
注释1:根据model(即我们在Glide中传入的图片资源获取方式),可以拿到 cacheKeys,当我们传入的是Stirng类型的(比如Url),那么这个 cacheKeys就有两个元素 [GlideUrl,ObjectKey]
注释2:如果拿到的cacheKeys为空,则直接返回false
注释3:获取resourceClass的信息,就是glide所支持的资源类的信息,比如Bitmap
注释4:进行循环,根据注释1中的 Key和注释3中的 资源类信息,生成一个缓存的 key对象 ---- ResourceCacheKey
注释5: 拿着这个key去 DiskCache中寻找是否有缓存对象。如果查找成功,获取 它的 ModelLoader对象,失败则继续循环。

这个while循环就是为了找到对应的 ModelLoader信息,如果找不到则返回false,如果找到了则进入下一个while循环,这个循环中就是根据上一个循环找到的ModelLoader数组,遍历每个 ModelLoader,然后执行其 fetcher的 loadData()方法进行资源的加载。然后返回true。

ResourceCacheGenerator.startNext()这个方法总结为一句话就是: 根据ResourceCacheKey从磁盘缓存中获取缓存
如果获取不到,则执行DataCacheGenerator .startNext()

2.关于DataCacheGenerator的实现

// DataCacheGenerator.java
  public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      sourceIdIndex++;
      if (sourceIdIndex >= cacheKeys.size()) {
        return false;
      }

      Key sourceId = cacheKeys.get(sourceIdIndex);
      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      cacheFile = helper.getDiskCache().get(originalKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    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;
  }

这个方法和上述的 ResourceCacheGenerator中的startNext()很像,其实很多部分都是一样的, 但是这里有唯一一处不同是,就是去DiskCache中寻找用缓存用的Key并不是 ResourceCacheKey,而是一个 DataCacheKey

他们的区别是:

  • ResourceCacheKey
    指向的是 包含缩减采样/转换资源数据的缓存文件
  • DataCacheKey
    指向的是 包含原始未修改源数据的缓存文件

这也就能看出DataFetcherGenerator的作用是:

  • ResourceCacheGenerator
    DataFetcherGenerator实现类,从包含缩减采样/转换资源数据的缓存文件生成DataFetchers
  • DataCacheGenerator
    DataFetcherGenerator实现类,从包含原始未修改源数据的缓存文件生成DataFetchers

在1.2节中,这两个方法执行完之后,stage来到了 SWITCH_TO_SOURCE_SERVICE状态,那么他会runReason为调用 reschedule(),拿着非diskCacheExecutor的线程执行器再去执行一遍任务。
也就是重新执行了 DecodeJob.runWrapped(),而这个方法最终为调用 SourceGenerator.startNext()

3.关于SourceGenerator的实现

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

    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);  // 2
      }
    }
    return started;
  }

  // 将数据缓存到Disk中
  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);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished encoding source to cache"
            + ", key: " + originalKey
            + ", data: " + dataToCache
            + ", encoder: " + encoder
            + ", duration: " + LogTime.getElapsedMillis(startTime));
      }
    } finally {
      loadData.fetcher.cleanup();
    }

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

注释1:通过 cacheData()将数据缓存到 DataDiskCache中,dataToCache是在数据加载好,这个对象才有的,所以在一开始进来,这个方法是不会执行的。
注释2:通过 DataFetcher.loadData()请求数据。
具体的请求这里就不细讲了。只知道在请求成功,会调用 SourceGenerator.onDataReady()表示数据已经获取成功:

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

注释1:如果数据可以被缓存,则调用 reschedule(),这个方法会在 SourceGenerator.startNext()中执行 cacheData()将数据和新的DataCacheKey一起保存在了磁盘中。最后返回true。

最终还是调用了 onDataFetcherReady()使得代码回到了 DecodeJob 中。
而DecodeJob就会对数据解码,和重新编码后缓存,调用到 EngineJob.onResourceReady()
最后 EnginJob会向主线程发送消息,最终告知 ViewTarget()执行成功并显示对应图片资源。

1.5 DecodeJob.run时序图

从上面的源码,我这边总结了一下 run的时序图,便于理解方法的调用:
在这里插入图片描述
到这里 load方法算是分析的七七八八了。

上述分析中缓存讲的并不是很清楚,所以这里需要再补充一些缓存的知识。

2. Glide缓存总结

我们知道 Glide的缓存机制,都在 load()流程中。
一路看下来,我们知道了它总共有两种缓存,一种是 内存缓存,一种是 磁盘缓存:

2.1 内存缓存

内存缓存出现在 load的前半段:

// Engine.java
... 
 EngineResource<?> active = loadFromActiveResources(key, isMemoryCacheable);
 ...
 EngineResource<?> cached = loadFromCache(key, isMemoryCacheable);
 ...

他们的作用:

  • 第一层内存缓存: loadFromActiveResources
    ActiveResources所维护的 HashMap中获取弱引用缓存资源
  • 第二层内存缓存:loadFromCache
    LruCache中获取缓存资源,LruCache就是一个拓展的 LinkedHashMap
    如果可以获取成功, 把获取到的数据放到 ActiveResources 中。

这里的问题是,同样是内存缓存,为什么要分成两层?为什么第二层在获取成功后把数据移动到第一层?
我们需要注意的是在我们获取成功的时候,代码调用了: active.acquire() 资源有一个计数器,当被引用时,自增1。
这个地方就解释了为什么内存缓存有两层:
将内存缓存的资源分成 正在使用的 和 没有使用的,正在使用的用弱引用缓存,这样能优化运行时内存性能,在需要时随时回收,当前不用的图片则通过LruCache来缓存。这样也能节省空间

这里就解决了两层内存缓存的疑惑。
这里还有另外一个问题,那就是 资源是什么时候被放到 内存Cache里面去的呢?

在 1.5节中,我们看到了回调,在 Engine.onEngineJobComplete()中:

// Engine.java
  public void onEngineJobComplete(EngineJob<?> engineJob, Key key, EngineResource<?> resource) {
    Util.assertMainThread();
    if (resource != null) {
      resource.setResourceListener(key, this);

      if (resource.isCacheable()) {
        activeResources.activate(key, resource); // 1
      }
    }

    jobs.removeIfCurrent(key, engineJob);
  }

注释1:这个方法就是将资源放到了 ActiveResources中。

那只有这个地方,LruCache中的缓存在哪里存的呢?这就要看Resource资源了,我们在EngineResource中,看到它除了acquire()进行引用自增,它同样还有一个方法,如果减少一个引用,他就会自减:

// EngineResource.java
  void release() {
    ...
    if (--acquired == 0) {
      listener.onResourceReleased(key, this);
    }
  }

如果发现引用为0,说明没有地方引用这个图片资源了,就会调用 Engine.onResourceReleased()

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

注释1:如果资源可以缓存,则缓存在 LruCache中
注释2:如果不能缓存,则直接释放资源。

这样一来,就解答了开始的疑问。这里做一个总结:

  1. 当资源被成功加载后,在 Engine的onEngineJobComplete()回调中,放到内存的 ActiveResources所维护的 弱引用HashMap中。
  2. 当一个资源所持有的引用为0,则调用 Engine.onResourceReleased,如果这个资源可以被缓存,则它加入到 内存的 LruCache中。如果不能被缓存则直接释放。

2.2 磁盘缓存

磁盘缓存是在 DecodeJob的run方法中,通过 ResourceCacheGenerator.startNext()DataCacheGenerator.startNext()做的:

  1. ResourceCacheGenerator.startNext()
    读取硬盘中可以匹配ResourceCacheKey的文件,它是包含缩减采样/转换资源数据的缓存文件
  2. DataCacheGenerator.startNext()
    读取硬盘中可以匹配DataCacheKey的文件,它是包含原始未修改源数据的缓存文件
    在资源加载成功后,在 SourceGenerator.cacheData()中写入到磁盘中。

它会通过下面的代码来更新:

// ResourceCacheKey.java
  @Override
  public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
    byte[] dimensions = arrayPool.getExact(8, byte[].class);
    ByteBuffer.wrap(dimensions).putInt(width).putInt(height).array();
    signature.updateDiskCacheKey(messageDigest);
    sourceKey.updateDiskCacheKey(messageDigest);
    messageDigest.update(dimensions);
    if (transformation != null) {
      transformation.updateDiskCacheKey(messageDigest);
    }
    options.updateDiskCacheKey(messageDigest);
    messageDigest.update(getResourceClassBytes());
    arrayPool.put(dimensions);
  }

至于调用时机,请看这个步骤:

// 在我们获取磁盘缓存时调用的方法
helper.getDiskCache().get(currentKey);

而这个 DiskCache()DiskLruCacheWrapper类型,
在 DecodeJob解析完数据后,会调用 notifyEncodeAndRelease(),示意对数据进行自己的编码然后缓存,这个方法如下:

 private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
    ...
    stage = Stage.ENCODE;
    try {
      if (deferredEncodeManager.hasResourceToEncode()) {
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    }...
  }

这里调用了 DeferredEncodeManager.encode():

    void encode(DiskCacheProvider diskCacheProvider, Options options) {
      ...
      try {
        diskCacheProvider.getDiskCache().put(key,
            new DataCacheWriter<>(encoder, toEncode, options));
      } ...
    }

这里则把数据给put到了 DiskLruCacheWrapper中。这就完成了磁盘缓存。

这里有一个问题,为什么磁盘缓存也需要两层,这个非常容易理解:

  • 通过DataCacheGenerator缓存
    它是在数据被加载完后put进去的,它是原始的未改变的图片资源,是最原生的。
  • 通过ResourceCacheGenerator缓存
    它是在数据被加载完后,经过重新编码,再缓存到磁盘中的。经过重新编码,图片资源会减小采样或者更换资源形式,这样可以用更少的空间来替代原生的。在获取的时候也先从该缓存中获取,是轻一级的磁盘缓存。

因为磁盘读取是耗时操作,所以磁盘缓存是在 子线程(GlideExecutor)中做的。和快速的内存缓存分开了~

发布了263 篇原创文章 · 获赞 110 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/rikkatheworld/article/details/105650504