深入理解Glide加载原理--如何扩展Glide支持其它格式的图片

1,448 阅读10分钟

看了许多关于Glide的文章,大部分开发者关注的角度在于Glide的使用,去关心Glide具体的加载流程,关心Glide的缓存原理,今天我准备换一个角度。如果我们要针对Glide扩展那么该如何进行呢?

为此我这边准备分为下面几篇文章来说明下面的这些问题。需要注意的是在看下面的内容之前需要是默认读者对Glide有一定原理性的认识的。

  1. ModelLoader 工作机制
  1. 认识Glide的注册器
  1. Glide DecodeHelper(加载原理)

ModelLoader 加载原理

为什么先说ModelLoader?

或许有同学会说?ModelLoader 非常简单,就是一个数据的输入输出问题。这个虽然没错,但是如果让你来设置一个数据类型的加载,你会怎么做?让你来使用,你会怎么利用这个接口?

我们知道,Glide的load相关的api是非常丰富的,支持加载多种格式类型的数据转换成图片。

那么接下来我们来了解一下Glide是如何完成这个功能的。

ModelLoader常见的工作方式

经过代码分析ModelLoader主要有下面几种方式

  • ModelLoader直接加载
  • ModelLoader转换
  • ModelLoader链路选择

ModelLoader直接加载

ModelLoader直接加载的使用比较简单,就是直接将输入的Model数据输出为输出的Data数据。Glide中的使用案例是UriLoader,我们来看看代码

public class UriLoader<Data> implements ModelLoader<Uri, Data> {
  private static final Set<String> SCHEMES =
      Collections.unmodifiableSet(
          new HashSet<>(
              Arrays.asList(
                  ContentResolver.SCHEME_FILE,
                  ContentResolver.SCHEME_ANDROID_RESOURCE,
                  ContentResolver.SCHEME_CONTENT)));

  private final LocalUriFetcherFactory<Data> factory;

  // Public API.
  @SuppressWarnings("WeakerAccess")
  public UriLoader(LocalUriFetcherFactory<Data> factory) {
    this.factory = factory;
  }

  @Override
  public LoadData<Data> buildLoadData(
      @NonNull Uri model, int width, int height, @NonNull Options options) {
    return new LoadData<>(new ObjectKey(model), factory.build(model));
  }
  
  @Override
public boolean handles(@NonNull Uri model) {
  return SCHEMES.contains(model.getScheme());
}
  }

可以看到UriLoader直接支持加载本地数据,数据的加载通过LocalUriFetcherFactory factory构建Featcher进行加载。

ModelLoader转换

ModelLoader的转换是指当前ModelLoader自己不进行数据加载 而是通过内部包含的其它ModelLoader间接进行加载。以StringLoader来看

public class StringLoader<Data> implements ModelLoader<String, Data> {
  private final ModelLoader<Uri, Data> uriLoader;

  // Public API.
  @SuppressWarnings("WeakerAccess")
  public StringLoader(ModelLoader<Uri, Data> uriLoader) {
    this.uriLoader = uriLoader;
  }

  @Override
  public LoadData<Data> buildLoadData(
      @NonNull String model, int width, int height, @NonNull Options options) {
    Uri uri = parseUri(model);
    if (uri == null || !uriLoader.handles(uri)) {
      return null;
    }
    return uriLoader.buildLoadData(uri, width, height, options);
  }

  @Override
  public boolean handles(@NonNull String model) {
    // Avoid parsing the Uri twice and simply return null from buildLoadData if we don't handle this
    // particular Uri type.
    return true;
  }
}

可以看到StringLoader的加载是通过内部ModelLoader<Uri, Data> uriLoader来完成的,而StringLoader的工作是将输入类型由String 转成成了Uri

MultiModelLoader

MultiModelLoader的创建

StringLoader 通过内部包含的uriLoader完成数据加载,但是uriLoader的创建是通过MultiModelLoaderFactory#build来完成的。以StreamFactory为例

public static class StreamFactory implements ModelLoaderFactory<String, InputStream> {

  @NonNull
  @Override
  public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
    return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));
  }

  @Override
  public void teardown() {
    // Do nothing.
  }
}

通过multiFactory创建对应的ModelLoader,

@NonNull
public synchronized <Model, Data> ModelLoader<Model, Data> build(
    @NonNull Class<Model> modelClass, @NonNull Class<Data> dataClass) {
  try {
    List<ModelLoader<Model, Data>> loaders = new ArrayList<>();
    boolean ignoredAnyEntries = false;
    //遍历已经注册的ModelLoader
    for (Entry<?, ?> entry : entries) {
      if (alreadyUsedEntries.contains(entry)) {
        ignoredAnyEntries = true;
        continue;
      }
      //获取能够处理当前输入输出的ModelLoader
      if (entry.handles(modelClass, dataClass)) {
        alreadyUsedEntries.add(entry);
        loaders.add(this.<Model, Data>build(entry));
        alreadyUsedEntries.remove(entry);
      }
    }
    if (loaders.size() > 1) {//如果你能够处理的ModelLoader数量大于1 通过Factory重新创建
      return factory.build(loaders, throwableListPool);
    } else if (loaders.size() == 1) {
      return loaders.get(0);
    } else {
      if (ignoredAnyEntries) {
        return emptyModelLoader();
      } else {
        throw new NoModelLoaderAvailableException(modelClass, dataClass);
      }
    }
  } catch (Throwable t) {
    alreadyUsedEntries.clear();
    throw t;
  }
}

MultiModelLoaderFactory#build的构建逻辑如下:

  1. 遍历当前已经注册的ModelLoader 获取能够处理当前输入输出的ModelLoader
  1. 如果当前能够处理的ModelLoader只有一个则直接使用,如果数量大于1 则通过MultiModelLoaderFactory#Factory 创建MultiModelLoader

MultiModelLoader的工作

我们知道ModelLoader的工作非常简单,

  1. Handles 判断能否处理当前输入类型
  1. buildLoadData 构建加载数据

class MultiModelLoader<Model, Data> implements ModelLoader<Model, Data> {

  private final List<ModelLoader<Model, Data>> modelLoaders;
  private final Pool<List<Throwable>> exceptionListPool;

  MultiModelLoader(
      @NonNull List<ModelLoader<Model, Data>> modelLoaders,
      @NonNull Pool<List<Throwable>> exceptionListPool) {
    this.modelLoaders = modelLoaders;
    this.exceptionListPool = exceptionListPool;
  }

  @Override
  public LoadData<Data> buildLoadData(
      @NonNull Model model, int width, int height, @NonNull Options options) {
    Key sourceKey = null;
    int size = modelLoaders.size();
    List<DataFetcher<Data>> fetchers = new ArrayList<>(size);
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0; i < size; i++) {
      ModelLoader<Model, Data> modelLoader = modelLoaders.get(i);
      if (modelLoader.handles(model)) {//过滤出所有能够处理的ModelLoader
        LoadData<Data> loadData = modelLoader.buildLoadData(model, width, height, options);
        if (loadData != null) {
          sourceKey = loadData.sourceKey;
          //将LoadeData 的fetcher加入到一个数组
          fetchers.add(loadData.fetcher);
        }
      }
    }
    //返回一个包含所有fetcher的MultiFetcher
    return !fetchers.isEmpty() && sourceKey != null
        ? new LoadData<>(sourceKey, new MultiFetcher<>(fetchers, exceptionListPool))
        : null;
  }

  @Override
  public boolean handles(@NonNull Model model) {
    for (ModelLoader<Model, Data> modelLoader : modelLoaders) {
    //只要内部有一个能够处理对应的数据  当前MultModelLoader能够处理
      if (modelLoader.handles(model)) {
        return true;
      }
    }
    return false;
  }
}

从代码逻辑来看MultiModelLoader最重要的事情就是把内部能够处理当前Model的ModelLoader#LoadeData中的Fetcher收集起来。构建一个MultiFetcher

MultiFetcherz的工作就是遍历内部所有的Fetcher直到有数据加载成功或者遍历完所有的Fetcher。

MultiModelLoader多级串联

当我们定义好一个ModelLoader对象,他可以自己完成数据加载,或者通过ModelLoader转换,使用已将注册好ModelLoader,当 MultiModelLoaderFactory#build构建的对象是MultiModelLoader ModelLoader加载数量在最坏的情况下呈现 乘法级别增长。

比如:加载内容是一个String 输出为InputStrem 。能够处理输入String输出InputStream的ModelLoader有一个,通过内部转化能够为输入为Uri输出为InputStream有3个 这个三个ModelLoader内部又通过MultiModelLoaderFactory#build构建的ModelLoader 又分别有三个。那么这次可以进行处理的ModelLoader 就有3X3 = 9 种可能。

如果自定义ModelLoader在条件允许的情况下尽可能让handles 方法的返回更精确,这样可以减少ModelLoader 的尝试加载

这种模块串联的思维,在加载数据的流程中也得到了应用。即只关心输入输出,而不关心整个过程的变化。

Glide的注册器

DecodeJob的工作过程

因为我们关注的是如何扩展,因此不准备对Glide如何调度到DecodeJob的流程进行说明。而是直接分析数据的加载过程。数据加载抽象了DataFetcherGenerator接口,Glide中有三个实现类。

image.png SourceGenerator:负责加载原始数据,并对加载出的原始数据进行缓存

DataCacheGenerator:负责加载缓存的原始数据

ResourceCacheGenerator:负责加载缓存的指定大小的数据

在整个数据的加载过程中,由DecodeJob调度三个SourceGenrator完成图片的加载。

Glide DecodeJob 的工作过程我们简单分析了DecodeJob的调度流程。这里就不再额外说明。

SourceGenrator数据加载的过程

SourceGenrator的工作过程主要是通过SourceGenrator#startNext()配合DecodeHelper来完成

@Override
public boolean startNext() {
  if (dataToCache != null) {//如果缓存数据不为空,那么直接进行缓存
    Object data = dataToCache;
    dataToCache = null;
    try {
      boolean isDataInCache = cacheData(data);
      if (!isDataInCache) {
        return true;
      }
    } catch (IOException e) {
    }
  }
  //cacheData 如果缓存成功sourceCacheGenerator 会被赋值
  if (sourceCacheGenerator != null && sourceCacheGenerator.startNext()) {
    return true;
  }
  sourceCacheGenerator = null;

  loadData = null;
  boolean started = false;
  //判断是否有ModelLoader 可以进行尝试加载
  while (!started && hasNextModelLoader()) {
    loadData = helper.getLoadData().get(loadDataListIndex++);
    //这里添加进行缓存判断的原因是 当数据可以进行缓存的时候,图片还可以从文件中进行加载
    //hasLoadPath 判断的是是否存在一条加载链路能够加载出对应的图片数据
    if (loadData != null
        && (helper.getDiskCacheStrategy().isDataCacheable(loadData.fetcher.getDataSource())
            || helper.hasLoadPath(loadData.fetcher.getDataClass()))) {
      started = true;
      startNextLoad(loadData);
    }
  }
  return started;
}

//通过LoadeData fetcher进行数据加载
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);
          }
        }
      });
}

整个代码的逻辑是

  1. 是否存在缓存数据,存在则进行缓存,缓存成功之后 将数据加载转移到DataCacheGenerator
  1. 判断是否还有没有尝试加载的LoadeData,如果有则判断其内部是否存在加载链路加载出对应的数据。
  1. 调用LoadData#fetcher进行数据加载
  1. 如果3加载失败,那么不断重复123的流程直到数据加载成功,或者LoadeData全部被尝试完。

DecodeHelper

DecodeHelper中有两个比较关键的数据

//调用load api 传入的数据类型,比如加载一个Sting 那么modelo就是String
private Object model;
//指定出解码的数据类型 默认是Object  意味着对解码出的数据不做限制,任何解码-转码都可以进行尝试
private Class<?> resourceClass;
//转换出的资源类型,默认是Drawable 调用as进行指定
private Class<Transcode> transcodeClass;

DecodeHelper#getLoadData

SourceGenrator中的LoadData是通过DecodeHelper#getLoadData来获取的。

List<LoadData<?>> getLoadData() {
  if (!isLoadDataSet) {
    isLoadDataSet = true;
    loadData.clear();
    //这里的ModelLoader的获取主要是 1.通过注册器获取能够处理整个Model并调用ModelLoader#handles判断是否真的能够处理对应的Model
    List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = modelLoaders.size(); i < size; i++) {
      ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
      //通过ModelLoader构建LoadData
      LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);
      if (current != null) {
        loadData.add(current);
      }
    }
  }
  return loadData;
}
  1. .通过注册器获取能够处理整个Model并调用ModelLoader#handles判断是否真的能够处理对应的Model
  1. 通过ModelLoader构建LoadData

DecodeHelper#hasLoadPath

hasLoadPath的逻辑非常简单主要是判断loadPath是否为空,而LoadPath通过DecodeHelper#getLoadPath 来获取

<Data> LoadPath<Data, ?, Transcode> getLoadPath(Class<Data> dataClass) {
  return glideContext.getRegistry().getLoadPath(dataClass, resourceClass, transcodeClass);
}

LoadPath中比较关键的是decodePaths 他代表的数据从ModelLoader中加载的数据进行解码,转码的线路。

public class LoadPath<Data, ResourceType, Transcode> {
  //数据类型
  private final Class<Data> dataClass;
private final Pool<List<Throwable>> listPool;
//解码路径 注意这个是一个数组,一条loadPath 可能包含多条DecodePath
private final List<? extends DecodePath<Data, ResourceType, Transcode>> decodePaths;
}

DecodePath的获取

DecodePath从Registry#getDecodePaths中获取

private <Data, TResource, Transcode> List<DecodePath<Data, TResource, Transcode>> getDecodePaths(
    @NonNull Class<Data> dataClass,
    @NonNull Class<TResource> resourceClass,
    @NonNull Class<Transcode> transcodeClass) {
  List<DecodePath<Data, TResource, Transcode>> decodePaths = new ArrayList<>();
  //获取能够解码data 的解码器解码出来的resourceClass
  List<Class<TResource>> registeredResourceClasses =
      decoderRegistry.getResourceClasses(dataClass, resourceClass);
//遍历解码出的registeredResourceClasses
  for (Class<TResource> registeredResourceClass : registeredResourceClasses) {
  //获取能够转码registeredResourceClass 到transcodeClass的转码出的类型
    List<Class<Transcode>> registeredTranscodeClasses =
        transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);
     //遍历转码出的class类型数组,组装DecodePath
    for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {

      List<ResourceDecoder<Data, TResource>> decoders =
          decoderRegistry.getDecoders(dataClass, registeredResourceClass);
      ResourceTranscoder<TResource, Transcode> transcoder =
          transcoderRegistry.get(registeredResourceClass, registeredTranscodeClass);
      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
      DecodePath<Data, TResource, Transcode> path =
          new DecodePath<>(
              dataClass,
              registeredResourceClass,
              registeredTranscodeClass,
              decoders,
              transcoder,
              throwableListPool);
      decodePaths.add(path);
    }
  }
  return decodePaths;
}

DecodePath获取逻辑如下

  1. 获取能够解码data数据的解码器中解码出的registeredResourceClasses
  1. 遍历解码出的registeredResourceClasses ,获取能够转码registeredTranscodeClass 的转码器。
  1. 组装DecodePath

DecodeJob#onDataFetcherReady

当LoadData通过DataFetcher#LoadData数据加载完成之后。会调用onDataReadyInternal,如果当前数据不可以缓存,会调度到DecodeJob#onDataFetcherReady,如果可以进行缓存,那么会给dataToCache进行赋值。并重新调度SourceGenerator进行缓存,缓存之后将加载数据转交给DataCacheGenerator进行处理。DataCacheGenerator从缓存文件中加载数据之后会调用DataCacheGenerator#onDataReady 。最终会调用DecodeJob#onDataFetcherReady。

onDataFetcherReady会完成两件事情

  1. 通过decodeFromData 完成数据解码转码
  1. 通过notifyEncodeAndRelease完成资源数据的缓存和通知EngineJob数据加载完成

LoadPath的工作过程

decodeFromData解码,编码的过程是通过LoadPath#load来完成的。而load调用了loadWithExceptionList

private Resource<Transcode> loadWithExceptionList(
    DataRewinder<Data> rewinder,
    @NonNull Options options,
    int width,
    int height,
    DecodePath.DecodeCallback<ResourceType> decodeCallback,
    List<Throwable> exceptions)
    throws GlideException {
  Resource<Transcode> result = null;
  //noinspection ForLoopReplaceableByForEach to improve perf
  for (int i = 0, size = decodePaths.size(); i < size; i++) {
    DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
    try {
      result = path.decode(rewinder, width, height, options, decodeCallback);
    } catch (GlideException e) {
      exceptions.add(e);
    }
    if (result != null) {
      break;
    }
  }

  if (result == null) {
    throw new GlideException(failureMessage, new ArrayList<>(exceptions));
  }

  return result;
}

代码逻辑非常简单:就是遍历decodePaths 尝试加载出想要的资源。每一个decodePath的尝试通过DecodePath#decode

public Resource<Transcode> decode(
    DataRewinder<DataType> rewinder,
    int width,
    int height,
    @NonNull Options options,
    DecodeCallback<ResourceType> callback)
    throws GlideException {
    
  Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
  Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
  return transcoder.transcode(transformed, options);
}
  1. 遍历所有解码器进行解码,直到有一个解码器解码成功
  1. 回调DecodeJob#DecodeCallback#onResourceDecoded 进行动画转变,并初始化资源缓存相关逻辑
  1. 将经历动画变化的资源转码成对应的资源数据

小结:

因此Glide 的数据加载过程可以简单的分成下面的结果阶段。

image.png

  • 一次完整的数据加载流程中,由ModelLoader->LoadPath构成。
  • 一次完整的数据加载流程中,可以由多个ModelLoader->LoadPath 组成
  • 一个LoadPath包含多个DecodePath
  • DecodePath包含多个解码路径以及一个转码器,
  • Glide真正关心的是通过RequestManager#load决定的输入model与通过

RequestManager#as决定的输出transcodeClass。对于中间的转换过程并不关心,他会尝试所有可能的加载链路。直到加载成功。最坏的情况下能够处理当前输入Model的ModelLoader有 N 个,

一个LoadPath 有 M 个 DecodePath,一个DecodePath有 S 个decoders 那么这次加载的可能次数为

Y = N * M * S (粗糙的计算方式)

扩展时其它注意事项:

  1. 明确输入,输出类型。了解现有的编码,解码能否转换成为对应的数据类型
  1. 增加文件缓存编码器和资源缓存编码器。
  1. 善用BaseRequestOptions#decode, decode方法决定了解码出来的数据类型resourceClass。

通过resourceClass 能显示解码器的输出和转码器的输入从而减少可能存在的解码转码链路。减少Glide加载的尝试次数。