Glide - 3 源码分析 Glide 缓存机制

Start

前言

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

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

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

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

加载缓存执行顺序

缓存 Key

既然是缓存功能,就必然会有用于进行缓存的 Key。那么 Glide 的缓存 Key 是怎么生成的呢?
Glide 的缓存 Key 生成规则非常繁琐,决定缓存Key的参数竟然有 8 个。不过繁琐归繁琐,至少逻辑还是比较简单的,我们先来看一下 Glide 缓存 Key 的生成逻辑。
生成缓存 Key 的代码在 Engine 类的 load() 方法当中,来看一下:

public class Engine
    implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {
...
  public <R> LoadStatus load(
      GlideContext glideContext,
      Object model,
      Key signature,
      int width,
      int height,
      Class<?> resourceClass,
      Class<R> transcodeClass,
      Priority priority,
      DiskCacheStrategy diskCacheStrategy,
      Map<Class<?>, Transformation<?>> transformations,
      boolean isTransformationRequired,
      boolean isScaleOnlyOrNoTransform,
      Options options,
      boolean isMemoryCacheable,
      boolean useUnlimitedSourceExecutorPool,
      boolean useAnimationPool,
      boolean onlyRetrieveFromCache,
      ResourceCallback cb,
      Executor callbackExecutor) {
    long startTime = VERBOSE_IS_LOGGABLE ? LogTime.getLogTime() : 0;

    EngineKey key =
        keyFactory.buildKey(
            model,
            signature,
            width,
            height,
            transformations,
            resourceClass,
            transcodeClass,
            options);
    EngineResource<?> memoryResource;
    synchronized (this) {
      // ⚠️ 1:下面要用到
      memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);
    ...
  }
...
}

这里 model 就是我们要加载的图片的唯一标识,比如说如果是一张网络上的图片的话,那么这个id就是这张图片的url地址。

接下来这行,将这个 model 连同着signature、width、height等等 8 个参数一起传入到 EngineKeyFactory 的 buildKey() 方法当中,从而构建出了一个 EngineKey 对象,这个 EngineKey 也就是 Glide 中的缓存 Key 了。

可见,决定缓存 Key 的条件非常多,即使你用 override() 方法改变了一下图片的 width 或者 height,也会生成一个完全不同的缓存 Key。

EngineKey 类的源码大家有兴趣可以自己去看一下,其实主要就是重写了 equals()hashCode() 方法,保证只有传入 EngineKey 的所有参数都相同的情况下才认为是同一个 EngineKey 对象。

EngineKey

内存缓存

有了缓存 Key,接下来就可以开始进行缓存了,那么我们先从内存缓存看起。

首先你要知道,默认情况下,Glide 自动就是开启内存缓存的。

也就是说,当我们使用 Glide 加载了一张图片之后,这张图片就会被缓存到内存当中,只要在它还没从内存中被清除之前,下次使用 Glide 再加载这张图片都会直接从内存当中读取,而不用重新从网络或硬盘上读取了。

如果你有什么特殊的原因需要禁用内存缓存功能,Glide 对此提供了接口:

Glide.with(this)
     .load(url)
     .skipMemoryCache(true)
     .into(imageView);

可以看到,只需要调用 skipMemoryCache() 方法并传入 true,就表示禁用掉Glide的内存缓存功能。

没错,关于Glide内存缓存的用法就只有这么多,可以说是相当简单。接下来就让我们就通过阅读源码来分析一下 Glide 的内存缓存功能是如何实现的。

其实说到内存缓存的实现,非常容易就让人想到 LruCache 算法(Least Recently Used),也叫近期最少使用算法。

首先回忆一下,在上一篇文章的第三步 into() 方法中,我们当时分析到了在 开始用引擎load: 方法的时候,说到过,先去缓存去找,如果没有就去走 * waitForExistingOrStartNewJob* 方法。当时我们没有展开说缓存那块,那么我们现在来看下它的源码,也就是上面 ⚠️1 的那 loadFromMemory 方法,我们看一下代码:

@Nullable
  private EngineResource<?> loadFromMemory(
      EngineKey key, boolean isMemoryCacheable, long startTime) {
    if (!isMemoryCacheable) {
      return null;
    }
    // 优先加载内存中的活动缓存 - ActiveResources
    EngineResource<?> active = loadFromActiveResources(key);
    if (active != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from active resources", startTime, key);
      }
      return active;
    }

    // 如果活动缓存中没有,就加载 LRU 内存缓存中的资源数据。
    EngineResource<?> cached = loadFromCache(key);
    if (cached != null) {
      if (VERBOSE_IS_LOGGABLE) {
        logWithTimeAndKey("Loaded resource from cache", startTime, key);
      }
      return cached;
    }

    return null;
  }

可以看到,首先就判断了 isMemoryCacheable 是不是 false,如果是 false 的话就直接返回 null。这是什么意思呢?

其实很简单,我们刚刚不是学了一个 skipMemoryCache() 方法吗?如果在这个方法中传入 true,那么这里的 isMemoryCacheable 就会是 false,表示内存缓存已被禁用 Glide 的图片加载过程中会调用两个方法来获取内存缓存,loadFromActiveResources()loadFromCache()

我们来看一下 loadFromActiveResources() 的源码:

  @Nullable
  private EngineResource<?> loadFromActiveResources(Key key) {
    EngineResource<?> active = activeResources.get(key);
    if (active != null) {
      active.acquire();
    }

    return active;
  }

这里调用到了 ActiveResources 类里面的 * get* 方法,代码如下:

final class ActiveResources {
  @VisibleForTesting 
  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;
  }

  ···

 @VisibleForTesting
  static final class ResourceWeakReference extends WeakReference<EngineResource<?>> {

  ···
}

可以看到 activeResources 就是一个弱引用的 HashMap,用来缓存正在使用中的图片。我们可以看到,loadFromActiveResources() 方法就是从 activeResources 这个 HashMap 当中取值的。使用 activeResources 来缓存正在使用中的图片,可以保护这些图片不会被 LruCache 算法回收掉。

接下来再看一下 loadFromCache(key) 方法,在这个方法中,会使用缓存 Key 来从 cache 当中取值,而这里的 cache 对象就是在构建 Glide 对象时创建的 LruResourceCache,那么说明这里其实使用的就是 LruCache 算法了。代码如下:

public class Engine
    implements EngineJobListener,
        MemoryCache.ResourceRemovedListener,
        EngineResource.ResourceListener {

  ···
  
  private final MemoryCache cache;

  ···

  private EngineResource<?> loadFromCache(Key key) {
    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, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
    }
    return result;
  }

先看 cache,这个是 MemoryCache ,MemoryCache 代码如下:

/** An interface for adding and removing resources from an in memory cache. */
public interface MemoryCache {

···

可以看到是一个接口,在 GlideBuilder 类中实现如下:

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

而 LruResourceCache 从名字上看是实现LRU算法的,进去看一下:

/** An LRU in memory cache for {@link com.bumptech.glide.load.engine.Resource}s. */
public class LruResourceCache extends LruCache<Key, Resource<?>> implements MemoryCache {
···

果然是,那内部就是 LinkedHashMap 实现的,我们接着看 getEngineResourceFromCache 方法里面的 remove 方法,LinkedHashMap 的 remove 方法是,存在这个key对应的值,那就删除并且返回,没有的话直接返回空。

为什么 Glide 会弄 2 个内存缓存(一个 Map + 弱引用,一个 LRU 内存缓存?

郭神的回道是这样的:ActiveResources 就是一个弱引用的 HashMap ,用来缓存正在使用中的图片,使用 ActiveResources 来缓存正在使用中的图片,可以保护这些图片不会被 LruCache 算法回收掉。

DevYK的理解是这样的:比如我们 Lru 内存缓存 size 设置装 99 张图片,在滑动 RecycleView 的时候,如果刚刚滑动到 100 张,那么就会回收掉我们已经加载出来的第一张,这个时候如果返回滑动到第一张,会重新判断是否有内存缓存,如果没有就会重新开一个 Request 请求,很明显这里如果清理掉了第一张图片并不是我们要的效果。所以在从内存缓存中拿到资源数据的时候就主动添加到活动资源中,并且清理掉内存缓存中的资源。这么做很显然好处是 保护不想被回收掉的图片不被 LruCache 算法回收掉,充分利用了资源。

好的,从内存缓存中读取数据的逻辑大概就是这些了。概括一下来说,就是如果能从内存缓存当中读取到要加载的图片,那么就直接进行回调,如果读取不到的话,才会开启线程执行后面的图片加载逻辑。

硬盘缓存

调用如下代码开启磁盘缓存:

Glide.with(this)
     .load(url)
     .diskCacheStrategy(DiskCacheStrategy. RESOURCE)//使用磁盘资源缓存功能
     .into(imageView);

这个 diskCacheStrategy() 方法它可以接收五种参数:

  • DiskCacheStrategy.NONE: 表示不缓存任何内容。
  • DiskCacheStrategy.RESOURCE: 表示只缓存转换过后的图片。
  • DiskCacheStrategy.DATA: 表示只缓存原始图片。
  • DiskCacheStrategy.ALL : 表示既缓存原始图片,也缓存转换过后的图片。
  • DiskCacheStrategy.AUTOMATIC:表示根据数据获取器和编码策略自动选择策略(默认)

默认的策略叫做 AUTOMATIC,它会尝试对本地和远程图片使用最佳的策略。当你加载远程数据(比如,从URL下载)时,AUTOMATIC 策略仅会存储未被你的加载过程修改过(比如,变换,裁剪–译者注)的原始数据,因为下载远程数据相比调整磁盘上已经存在的数据要昂贵得多。对于本地数据,AUTOMATIC 策略则会仅存储变换过的缩略图,因为即使你需要再次生成另一个尺寸或类型的图片,取回原始数据也很容易。

关于 Glide 硬盘缓存的用法也就只有这么多,那么接下来我们通过阅读源码来分析一下,Glide的硬盘缓存功能是如何实现的。

DiskCacheStrategy.RESOURCE 资源类型
获取资源数据

在上一篇文章中,创建 DecodeJob 并且调用 start 方法开始工作:,当时没有说了不考虑缓存,现在重新看一下缓存这块,代码如下:

class EngineJob<R> implements DecodeJob.Callback<R>, Poolable {
···
  public synchronized void start(DecodeJob<R> decodeJob) {
    this.decodeJob = decodeJob;
    GlideExecutor executor =
        decodeJob.willDecodeFromCache() ? diskCacheExecutor : getActiveSourceExecutor();
    executor.execute(decodeJob);
  }
···
}

可以看到,这里通过 willDecodeFromCache() 方法来判断是否从磁盘缓存中读取数据,

  /**
   * Returns true if this job will attempt to decode a resource from the disk cache, and false if it
   * will always decode from source.
   */
  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);
    }
  }

从代码可以看到最终返回 true 还是 false 是根据我们选择的 DiskCacheStrategy 模式来决定,上面说到了一共有五种模式,这里看一下具体源码,我们在选择模式的时候,各个值对应的结果是什么,这样,在 getNextStage 方法中逻辑结果了。

public abstract class DiskCacheStrategy {
  public static final DiskCacheStrategy ALL =
      new DiskCacheStrategy() {
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return dataSource == DataSource.REMOTE;
        }
        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return dataSource != DataSource.RESOURCE_DISK_CACHE
              && dataSource != DataSource.MEMORY_CACHE;
        }
        @Override
        public boolean decodeCachedResource() {
          return true;
        }
        @Override
        public boolean decodeCachedData() {
          return true;
        }
      };

  public static final DiskCacheStrategy NONE =
      new DiskCacheStrategy() {
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return false;
        }
        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return false;
        }
        @Override
        public boolean decodeCachedResource() {
          return false;
        }
        @Override
        public boolean decodeCachedData() {
          return false;
        }
      };

  public static final DiskCacheStrategy DATA =
      new DiskCacheStrategy() {
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return dataSource != DataSource.DATA_DISK_CACHE && dataSource != DataSource.MEMORY_CACHE;
        }
        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return false;
        }
        @Override
        public boolean decodeCachedResource() {
          return false;
        }
        @Override
        public boolean decodeCachedData() {
          return true;
        }
      };

  public static final DiskCacheStrategy RESOURCE =
      new DiskCacheStrategy() {
        @Override
        public boolean isDataCacheable(DataSource dataSource) {
          return false;
        }
        @Override
        public boolean isResourceCacheable(
            boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy) {
          return dataSource != DataSource.RESOURCE_DISK_CACHE
              && dataSource != DataSource.MEMORY_CACHE;
        }
        @Override
        public boolean decodeCachedResource() {
          return true;
        }
        @Override
        public boolean decodeCachedData() {
          return false;
        }
      };

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

  public abstract boolean isDataCacheable(DataSource dataSource);

  public abstract boolean isResourceCacheable(
      boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy);

  public abstract boolean decodeCachedResource();

  public abstract boolean decodeCachedData();
}

看到这个类,应该就很清楚了。那么我们默认是 decodeJob.willDecodeFromCache() 默认是 true 来分析磁盘缓存里面的逻辑,看过上一期就可以知道 decodeJob 其实就是 runnable,执行 execute 方法其实是走到 DecodeJob 的 run() 方法中:

@Override
  public void run() {

     ···
      // 1. 执行runWrapped
      runWrapped();

     ···

  }

上一期分析过这个方法了,我们直接看重点,* runWrapped();* 的源码:

  private void runWrapped() {
    switch (runReason) {
      case INITIALIZE:
        // 2.找到执行的状态
        stage = getNextStage(Stage.INITIALIZE);
        // 3.找到具体执行器
        currentGenerator = getNextGenerator();
        // 4.开始执行
        runGenerators();
        break;
      case SWITCH_TO_SOURCE_SERVICE:
        runGenerators();
        break;
      case DECODE_DATA:
        decodeFromRetrievedData();
        break;
      default:
        throw new IllegalStateException("Unrecognized run reason: " + runReason);
    }
  }

上面分析 willDecodeFromCache() 方法的时候,有⚠️的地方我们知道 Stage.INITIALIZE,所以我们走到 case INITIALIZE:,看一下getNextGenerator 方法的代码:

  private DataFetcherGenerator getNextGenerator() {
    switch (stage) {
      case RESOURCE_CACHE: // 3.1解码后的资源执行器
        return new ResourceCacheGenerator(decodeHelper, this);
      case DATA_CACHE: // 原始数据执行器
        return new DataCacheGenerator(decodeHelper, this);
      case SOURCE: // 新的请求,http 执行器
        return new SourceGenerator(decodeHelper, this);
      case FINISHED:
        return null;
      default:
        throw new IllegalStateException("Unrecognized stage: " + stage);
    }
  }

通过上面代码可知,主要是执行 currentGenerator.startNext() 就句代码,currentGerator 是一个接口,通过注释 3.1 我们知道这里它的实现类是 ResourceCacheGenerator ,那么我们具体看下 ResourceCacheGenerator 的 startNext 函数;

class ResourceCacheGenerator implements DataFetcherGenerator,
    DataFetcher.DataCallback<Object> {
      
    ...
      
      @Override
  public boolean startNext() {

    ...
      
    while (modelLoaders == null || !hasNextModelLoader()) {
      resourceClassIndex++;
  
      ...
      //1. 拿到资源缓存 key
      currentKey =
          new ResourceCacheKey(// NOPMD AvoidInstantiatingObjectsInLoops
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
      //2. 通过 key 获取到资源缓存
      cacheFile = helper.getDiskCache().get(currentKey);
      if (cacheFile != null) {
        sourceKey = sourceId;
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

        loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      //3. 获取一个数据加载器
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      //3.1 为资源缓存文件,构建一个加载器,这是构建出来的是 ByteBufferFileLoader 的内部类 ByteBufferFetcher
      loadData = modelLoader.buildLoadData(cacheFile,
          helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
        //3.2 利用 ByteBufferFetcher 加载,最后把结果会通过回调给 DecodeJob 的 onDataFetcherReady 函数
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
      
    ...
      
    }

通过上面注释可以得到几点信息

  1. 首先根据 资源 ID 等一些信息拿到资源缓存 Key
  2. 通过 key 拿到缓存文件
  3. 构建一个 ByteBufferFetcher 加载缓存文件
  4. 加载完成之后回调到 DecodeJob 中。
存储资源数据

先来看下面一段代码:

class DecodeJob<R> implements DataFetcherGenerator.FetcherReadyCallback,
    Runnable,
    Comparable<DecodeJob<?>>,
    Poolable {
    
    ...
      
      
     private void notifyEncodeAndRelease(Resource<R> resource, DataSource dataSource) {
   
      ....

    stage = Stage.ENCODE;
    try {
      //1. 是否可以将转换后的图片缓存
      if (deferredEncodeManager.hasResourceToEncode()) {
        //1.1 缓存入口
        deferredEncodeManager.encode(diskCacheProvider, options);
      }
    } finally {
        ...
    }
    onEncodeComplete();
  }     
    }
        
    void encode(DiskCacheProvider diskCacheProvider, Options options) {
      GlideTrace.beginSection("DecodeJob.encode");
      try {
        //1.2 将 Bitmap 缓存到资源磁盘
        diskCacheProvider.getDiskCache().put(key,
            new DataCacheWriter<>(encoder, toEncode, options));
      } finally {
        toEncode.unlock();
        GlideTrace.endSection();
      }
    }

通过上面我们知道 http 请求到图片输入流之后经过一系列处理,转换得到目标 Bitmap 资源,最后通过回调到 DecodeJob 进行缓存起来。

清理资源缓存
  1. 用户主动通过系统来清理
  2. 卸载软件
  3. 调用 DisCache.clear();
DiskCacheStrategy.DATA 原始数据类型
获取原始数据

参考上小节 DiskCacheStrategy.RESOURCE 获取资源,不同的是把 ResourceCacheGenerator 换成 DataCacheGenerator 加载了。

存储原始数据

这里既然存的是原始数据那么我们直接从 http 请求之后的响应数据开始查看,通过上一篇我们知道是在 HttpUrlFetcher 中请求网络,直接定位到目的地:

public class HttpUrlFetcher implements DataFetcher<InputStream> {
  
    @Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      //1. 通过 loadDataWithRedirects 来进行http 请求,返回 InputStream
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      //2. 将请求之后的数据返回出去
      callback.onDataReady(result);
    } catch (IOException e) {
          ...
    } finally {
            ...
    }
  }
}

根据注释可以得知,这里主要用于网络请求,请求响应数据回调给 MultiModelLoader 中。我们看下 它具体实现:

class MultiModelLoader<Model, Data> implements ModelLoader<Model, Data> {  
  ...
@Override
    public void onDataReady(@Nullable Data data) {
    //如果数据不为空,那么就回调给 SourceGenerator
      if (data != null) {
        callback.onDataReady(data);
      } else {
        startNextOrFail();
      }
    }
 .... 
}

这里的 callback 指的是 SourceGenerator ,继续跟

class SourceGenerator implements DataFetcherGenerator,
    DataFetcher.DataCallback<Object>,
    DataFetcherGenerator.FetcherReadyCallback {
    ....
      
    
      
      @Override
  public void onDataReady(Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      //1. 收到网络下载好的图片原始数据,赋值给成员变量 dataToCache
      dataToCache = data;
      //2. 交给 EngineJob 
      cb.reschedule();
    } else {
      cb.onDataFetcherReady(loadData.sourceKey, data, loadData.fetcher,
          loadData.fetcher.getDataSource(), originalKey);
    }
  }      
   ....       
    }

通过上面注释可以知道 cb.reschedule(); 最后回调到 EngineJob 类,会执行 reschedule(DecodeJob<?> job) 函数的 getActiveSourceExecutor().execute(job); 用线程池执行任务,最后又回到了 DecodeJob 的 run 函数 拿到执行器DataCacheGenerator ,最终会在 SourceGenerator 的 startNext() 函数,之前流程代码我就不贴了,上面讲了很多次了,相信大家应该记得了,我们直接看 startNext() 函数吧:

class SourceGenerator implements DataFetcherGenerator,
    DataFetcher.DataCallback<Object>,
    DataFetcherGenerator.FetcherReadyCallback {

  /**这个临时的变量就是 http 请求回来的图片原始数据
  */
  private Object dataToCache;


@Override
  public boolean startNext() {
   ....
     
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      //放入缓存
      cacheData(data);
    }
    
    ...
    }
    return started;
  }


  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());
      //存储原始数据
      //通过 StreamEncoder encode 写入文件
      helper.getDiskCache().put(originalKey, writer);
    
    } finally {
      loadData.fetcher.cleanup();
    }

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

通过上面代码得知,这里将原始数据写入文件中了。

清理资源缓存
  1. 用户主动通过系统来清理
  2. 卸载软件
  3. 调用 DisCache.clear();
磁盘缓存小节
  • 资源缓存是在把图片转换完之后才缓存;
  • 原始数据是网络请求成功之后就写入缓存;

参考文献:
DevYK
郭霖

申明:开始结束的图片来源网上,侵删

End
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容