Glide加载流程2——磁盘缓存加载细节

582 阅读6分钟

磁盘缓存策略

Glide的磁盘缓存策略定义在DiskCacheStrategy中,这是一个抽象类,定义了4个抽象方法,用来判断不同场景下的缓存策略:

//是否缓存原始数据
public abstract boolean isDataCacheable(DataSource dataSource);
//是否缓存已经转换过的数据
public abstract boolean isResourceCacheable(
      boolean isFromAlternateCacheKey, DataSource dataSource, EncodeStrategy encodeStrategy);
//是否解码缓存的已转换过的数据
public abstract boolean decodeCachedResource();
//是否解码缓存的原始数据
public abstract boolean decodeCachedData();

目前定义了5种策略:

  • ALL:缓存网络请求的原始数据,且缓存已转换的数据
  • NONE:不缓存原始数据和转换的数据
  • DATA:缓存从本地读取的原始资源数据,和网络请求的原始数据,不缓存转换的数据
  • RESOURCE:不缓存原始数据,缓存转换的数据
  • AUTOMATIC:缓存网络加载的原始数据,根据数据来源和编码策略决定是否缓存转换的数据

数据来源

DataSource定义了5种数据来源:

public enum DataSource {
  LOCAL,//本地数据,从assets,file等途径加载
  REMOTE,//网络请求
  DATA_DISK_CACHE,//磁盘缓存的原始数据
  RESOURCE_DISK_CACHE,//磁盘缓存的转换过的数据
  MEMORY_CACHE,//内存缓存
}

具体的加载逻辑

磁盘缓存策略决定了磁盘缓存的加载逻辑,主要的加载过程在DecodeJob中定义,上文已说明过,大致的流程是:

磁盘缓存策略能缓存转换过的数据:
  1. 先读取转换过的缓存,如果找到则返回
  2. 如果没有找到,那么会试图去读取原始数据的磁盘缓存,如果找到则跳到步骤6
  3. 如果原始数据的缓存也没有,那么会进行网络请求
  4. 请求完成后,会保存原始数据到文件缓存中(如果磁盘缓存策略允许缓存原始数据)
  5. 然后通过原始数据的缓存处理器加载原始数据或者直接进行第6步
  6. 将原始数据解码
  7. 将原始数据进行转换
  8. 将转换的数据进行缓存

其中的流程与具体的判断细节有关,可能会发生变化。

磁盘缓存策略只缓存原始数据:

流程与上述相似,从步骤2开始,只是第8步不会将转换过的数据缓存到本地。

不缓存转换的数据,也不缓存原始数据:

流程从第3步开始,第8步不会将转换过的数据缓存到本地。

从上面的流程看,请求的原始数据都会保存到本地,使得整个流程能运转起来,因为除了DiskCacheStrategy.NONE,其他的几种策略都是需要将网络请求的原始数据缓存的。

ResourceCacheGenerator

转换的缓存数据生成器

实现了DataFetcherGenerator接口:

interface DataFetcherGenerator {
  //结果回调
  interface FetcherReadyCallback {
    //让Glide重新调度
    void reschedule();
	//数据加载完成
    void onDataFetcherReady(
        Key sourceKey,
        @Nullable Object data,
        DataFetcher<?> fetcher,
        DataSource dataSource,
        Key attemptedKey);
	//数据加载失败
    void onDataFetcherFailed(
        Key attemptedKey, Exception e, DataFetcher<?> fetcher, DataSource dataSource);
  }
  //启动一个DataFetcher去加载数据,true表明启动,false未启动
  boolean startNext();
  //取消数据获取
  void cancel();
}

所以主要关注ResourceCacheGenerator的startNext()和cancel()方法

public boolean startNext() {
    //helper为DecodeHelper,由DecodeJob进行创建和初始化,保存了本次图片加载的基本参数:图片类型,宽,高,磁盘缓存策略,需要做的转换Transformation等
    List<Key> sourceIds = helper.getCacheKeys();
    //判断本次加载的资源类型是否注册了数据加载器
    if (sourceIds.isEmpty()) {
      return false;
    }
    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());
    }
    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);
      //生成一个ResourceCacheKey,看该类中的equals()和hashCode()方法,可以看到影响这个key的因素是宽,高,转换,解码器,sourceKey,signature和options。
      currentKey =
          new ResourceCacheKey(
              helper.getArrayPool(),
              sourceId,
              helper.getSignature(),
              helper.getWidth(),
              helper.getHeight(),
              transformation,
              resourceClass,
              helper.getOptions());
        //获取转换后的数据的缓存文件
      cacheFile = helper.getDiskCache().get(currentKey);
      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,用于加载数据
        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);
      }
    }
	//true说明有转换后的数据的缓存文件,并且已开始加载;false表明未找到缓存
    return started;
  }

startNext()方法主要是寻找转换后的数据的缓存文件,并且加载它。

  public void cancel() {
    LoadData<?> local = loadData;
    if (local != null) {
      local.fetcher.cancel();
    }
  }

cancel()主要调用了DataFetcher的cance()方法,暂停加载

ResourceCacheGenerator也实现了DataFetcher.DataCallback接口,用于处理缓存加载的回调。

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

  @Override
  public void onLoadFailed(@NonNull Exception e) {
    cb.onDataFetcherFailed(currentKey, e, loadData.fetcher, DataSource.RESOURCE_DISK_CACHE);
  }

缓存的加载结果,会调用FetcherReadyCallback的回调方法,而cb是DecodeJob实现并传入的,因此结果都交给DecodeJob处理。

DecodeJob具体的处理逻辑最后再分析。

DataCacheGenerator

原始数据的缓存加载器

DataCacheGenerator实现了相同的接口,DataFetcherGenerator和DataFetcher.DataCallback,直接看具体的实现方法:

public boolean startNext() {
    while (modelLoaders == null || !hasNextModelLoader()) {
      sourceIdIndex++;
        //cacheKeys由DecodeHelper提供,包含了该资源类型的加载方法
      if (sourceIdIndex >= cacheKeys.size()) {
        return false;
      }

      Key sourceId = cacheKeys.get(sourceIdIndex);
        //生成一个DataCacheKey,由equals和hashCode方法可知,影响因素是sourceKey和signature
      Key originalKey = new DataCacheKey(sourceId, helper.getSignature());
      //获取原始数据缓存文件
        cacheFile = helper.getDiskCache().get(originalKey);
      if (cacheFile != null) {
        this.sourceKey = sourceId;
          //当前的资源类型是File,所以需要文件加载器
        modelLoaders = helper.getModelLoaders(cacheFile);
        modelLoaderIndex = 0;
      }
    }

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
      ModelLoader<File, ?> modelLoader = modelLoaders.get(modelLoaderIndex++);
      //该类型的数据加载器,用来加载缓存文件,由于类型是File,因此用的是FileLoader
        loadData =
          modelLoader.buildLoadData(
              cacheFile, helper.getWidth(), helper.getHeight(), helper.getOptions());
      if (loadData != null && helper.hasLoadPath(loadData.fetcher.getDataClass())) {
        started = true;
          //因为类型是File,用的是FileFetcher,加载文件,返回数据
        loadData.fetcher.loadData(helper.getPriority(), this);
      }
    }
    return started;
  }

startNext()主要是获取缓存文件,并进行加载。

cancel()调用了DataFetcher.cancel()方法,用来取消加载。

实现的onDataReady和onLoadFailed回调与ResourceCacheGenerator一样,都是交给DecodeJob处理。

SourceGenerator

资源加载器。

实现的接口与上述两个XXGenerator一样。

 public boolean startNext() {
     //如果磁盘缓存策略允许缓存原始数据,那么加载数据后,会先缓存到文件中,再读取并处理
    if (dataToCache != null) {
      Object data = dataToCache;
      dataToCache = null;
      cacheData(data);
    }
	//只有cacheData()后,sourceCacheGenerator才非空,且这个sourceCacheGenerator的类型是DataCacheGenerator,只是跟DecodeJob中创建的不一样,此处只是利用了DataCacheGenerator重新加载一遍原始数据,并把结果回调给DecodeJob(因为sourceCacheGenerator中传入的回调是SourceGenerator,最后都会传给DecodeJob)
    if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
      return true;
    }
    sourceCacheGenerator = null;

    loadData = null;
    boolean started = false;
    while (!started && hasNextModelLoader()) {
        //按顺序获取该资源类型的LoadDada,并进行加载数据
      loadData = helper.getLoadData().get(loadDataListIndex++);
      if (loadData != null
          && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
              || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
        started = true;
        startNextLoad(loadData);
      }
    }
    return started;
  }

startNextLoad()方法用于执行具体的加载逻辑,调用LoadData的DataFetcher去加载数据:

 private void startNextLoad(final LoadData<?> toStart) {
    loadData.fetcher.loadData(
        helper.getPriority(),
        new DataCallback<Object>() {
          @Override
          public void onDataReady(@Nullable Object data) {
            //判断是否同一个请求	
            if (isCurrentRequest(toStart)) {
              onDataReadyInternal(toStart, data);
            }
          }

          @Override
          public void onLoadFailed(@NonNull Exception e) {
            if (isCurrentRequest(toStart)) {
              onLoadFailedInternal(toStart, e);
            }
          }
        });
  }

onDataReadyInternal()处理加载结果:

  void onDataReadyInternal(LoadData<?> loadData, Object data) {
    DiskCacheStrategy diskCacheStrategy = helper.getDiskCacheStrategy();
    if (data != null && diskCacheStrategy.isDataCacheable(loadData.fetcher.getDataSource())) {
      //如果磁盘策略允许缓存原始数据,那么保存到dataToCache变量中
        dataToCache = data;
        //DecodeJob实现了reschedule方法,最终还是会调用SourceGenerator的startNext()方法,进行文件缓存,并读取的操作
      cb.reschedule();
    } else {
        //如果不缓存原始数据,则直接把结果回调给DecodeJob
      cb.onDataFetcherReady(
          loadData.sourceKey,
          data,
          loadData.fetcher,
          loadData.fetcher.getDataSource(),
          originalKey);
    }
  }

DecodeJob中处理逻辑下篇再分析。