Glide手记之——DecodeHelper分析

1,046 阅读26分钟

以下源码分析基于Glide 4.12.0版本分析

官方文档中文版:muyangmin.github.io/glide-docs-…

本篇文章是分析Glide中比较细节的源码,如果没有大概看过Glide流程的可能会对本篇文章的内容难以理解,可以先去阅读其他作者对Glide主流程源码的分析,这部分本人也有写,目前还有很大的修改空间,暂时不会发布。

DecodeHelper是用于辅助三四级缓存在获取数据时的导向。

我们都知道Glide可以加载很多类型的资源,比如网络链接、本地资源res,本地文件等等,以及可以生成DrawableBitmapGifDrawable,还提供各种模糊、圆角等等的转换,还有强大的缓存功能。

那么多的功能可以堆到一块调用,如何让逻辑更清晰简单,复用性强呢。按照我们平时的写法每个参数搞个if else判断下,决定用什么方法,但更高级的做法是将所有关系列出来,通过某个参数去获取对应的功能就行了。

定义关系的是Registry注册表干的事情,DecodeHelper则是将参数传给注册表,拿到对应的功能。调用者无脑使用就行,不用关心里面有多复杂。

所以DecodeHelper大多数的逻辑都是调用Registry注册表的方法,等同于分析注册表中的关系构建。

涉及到的注册表有:

Transformation不属于注册表,是BaseRequestOptions自己构建的关系,不过DecodeHelper中用到,所以就列到一块去。

构建关系是比较复杂的流程,涉及很多参数以及逻辑,建议在浏览本篇文章时同时打开Registry注册表关系图跟文中给出的一些查询结果作对应。

在关系构建中会涉及到三个参数分别是modelresourceClasstranscodeClass。简单介绍这三个参数的含义

  • model 资源路径

    可以是Glide支持的任意类型:UriUrlResFile等等

  • resourceClass 解码类型

    api类型
    asDrawable()Object
    asGif()GifDrawable
    asBitmap()Bitmap
    downloadOnly()File

    解码类型参数设定有点奇怪,取决于默认配置的转码类型。asDrawable()是默认选项

  • transcodeClass 转码类型

    api类型
    asDrawable()Drawable
    asGif()GifDrawable
    asBitmap()Bitmap
    downloadOnly()File

这篇文章分析的内容是关于参数的关系构建,不同的参数构建出的关系是不一样的,为了分析时不迷糊,所以固定参数如下:

参数类型作用
modelString资源路径
resourceClassObject解码类型
transcodeClassBitmap转码类型

getModelLoaders()

获取模型加载方案,用来解决Glide加载不同资源时,使用不同的方案。比如加载网络链接用什么方案,加载本地文件用什么方案,加载res用什么方案。

不过ModelLoader并不是真正使用的,只能算是加载方案的Factory吧。

由于这个方法不是通用方法,我们重点关注的是glideContext.getRegistry().getModelLoaders(),所以暂时忽略掉这个File参数,改为我们固定好的String参数

class DecodeHelper {
  List<ModelLoader<File, ?>> getModelLoaders(File file)
      throws Registry.NoModelLoaderAvailableException {
    return glideContext.getRegistry().getModelLoaders(file);
  }
}

那么假设现在调用这个方法传入了一个链接字符串,让我们分析什么怎么构建关系的。这个方法最终会调用到ModelLoaderRegistry.getModelLoaders()所以直接进去看源码。

class ModelLoaderRegistry {
  public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
    
    //获取该`model(String)`支持的所有ModelLoader
    List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
    if (modelLoaders.isEmpty()) {
      throw new NoModelLoaderAvailableException(model);
    }

    int size = modelLoaders.size();
    boolean isEmpty = true;
    List<ModelLoader<A, ?>> filteredLoaders = Collections.emptyList();
    //赶紧试试这些ModelLoader能不能处理model(String)
    for (int i = 0; i < size; i++) {
      ModelLoader<A, ?> loader = modelLoaders.get(i);
      //之前是注册表的元素判断能不能处理`modelClass(String)`这样的类型
      //现在是拿到具体对象了,再判断能不能处理具体的内容`model(https://xxx.xxx)`
      if (loader.handles(model)) {
        if (isEmpty) {
          filteredLoaders = new ArrayList<>(size - i);
          isEmpty = false;
        }
        //过滤掉不能处理的ModelLoader,留下能处理的ModelLoader
        filteredLoaders.add(loader);
      }
    }
    //如果没有一个方案,就抛异常。
    if (filteredLoaders.isEmpty()) {
      throw new NoModelLoaderAvailableException(model, modelLoaders);
    }
    return filteredLoaders;

  }
}

这里说明下获取ModelLoader用的是model的类型,这里是String,而拿到ModelLoader后判断能不能处理model是判断能不能处理model的值,这里的值是图片链接

调用getModelLoadersForClass()获取可处理当前model(String)类型的关系表。如果一个都不支持这种类型就抛异常。

class ModelLoaderRegistry {
  private synchronized <A> List<ModelLoader<A, ?>> getModelLoadersForClass(@NonNull Class<A> modelClass) {
    //查询当前`modelClass(String)`类型的关系表有没有缓存过,没有缓存就去构建缓存
    List<ModelLoader<A, ?>> loaders = cache.get(modelClass);
    if (loaders == null) {
      //拿到`modelClass(String)`对应的ModelLoader,然后缓存起来
      loaders = Collections.unmodifiableList(multiModelLoaderFactory.build(modelClass));
      cache.put(modelClass, loaders);
    }
    return loaders;

  }
}

查询关系表,由于关系表是固定的,所以查询完后需要存储起来,避免下次继续查询,这里的关系很复杂,每次查询都会花费时间,既然如此就用空间换时间好了。

如果没有缓存过,自然需要去构建缓存。

class MultiModelLoaderFactory {
  synchronized <Model> List<ModelLoader<Model, ?>> build(@NonNull Class<Model> modelClass) {

    try {
      //根据不同的`modelClass(String)`获取不同的关系表,所以每次都新new一个集合
      List<ModelLoader<Model, ?>> loaders = new ArrayList<>();
      for (Entry<?, ?> entry : entries) {
        if (alreadyUsedEntries.contains(entry)) {
          continue;
        }
        //判断当前注册的方案能否处理`modelClass(String)`类型的资源
        if (entry.handles(modelClass)) {
          alreadyUsedEntries.add(entry);
          //构建出每一种组合的Loader
          loaders.add(this.<Model, Object>build(entry));
          alreadyUsedEntries.remove(entry);
        }
      }
      //返回所有能处理modelClass的方案
      return loaders;
    } catch (Throwable t) {
      alreadyUsedEntries.clear();
      throw t;
    }

  }
}

这个entries其实是Glide初始化的时候,往注册表里添加的各种关系,这里用到的是ModelLoaderRegistry注册表的的关系,由于关系比较多,具体可以参照ModelLoaderRegistry关系图

这里我们传入的modelClass类型为String,所以我们得到的关系有四组,分别是

modelClassdataClassFactory
StringInputStreamDataUrlLoader.StreamFactory
StringInputStreamStringLoader.StreamFactory
StringParcelFileDescriptorStringLoader.FileDescriptorFactory
StringAssetFileDescriptorStringLoader.AssetFileDescriptorFactory

这四组都是可以处理String类型的资源。得到关系后还没完,关系只是用来查询,最终是需要生成对象的,这个对象就是ModelLoader,通过ModelLoaderRegistry关系图可以看出,有非常多的的Loader,但我们只需要生成上面查询出来的这四个Loader,只是这个构造过程也不是很简单,可能这才是最复杂的一环。

class MultiModelLoaderFactory {
  private <Model, Data> ModelLoader<Model, Data> build(@NonNull Entry<?, ?> entry) {
    return (ModelLoader<Model, Data>) Preconditions.checkNotNull(entry.factory.build(this));
  }
}

可以看到这个build其实是调用了某个方案中的factory去构造的,由于factory还是挺多的,这里提前透露一下我们用链接作为请求的资源,关系表最终会通过StringLoader.StreamFactory生成一个StringLoader,所以我们分析的话从它入手。

class StringLoad {
  public static class StreamFactory implements ModelLoaderFactory<String, InputStream> {
    public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
      //注意这里,参数改成了Uri,以及多了一个InputStream的参数
      return new StringLoader<>(multiFactory.build(Uri.class, InputStream.class));
    }
  }
}

????大写的问号。我们刚从MultiModelLoaderFactory.build()调用某个factory.build()过来,现在又调用MultiModelLoaderFactory.build(),死循环了?几个意思啊。

其实是这样,我们一开始是通过modelClass(String)类型查的关系,虽然查出来StringLoader.StreamFactory可以处理,但是StreamFractory的意思是可以通过modelClass(String)类型转换成流数据,但是单凭一个字符串根本无法转换任何有用的东西,但我知道字符串它是个资源吧,不论是什么资源总是可以转成Uri类型,至于字符串的内容先不管,我先看看有没有能处理Uri类型的关系,所以此时modelClass已经变成了Uri.class类型了。

仔细看,上面的multiFactory.build()入参已经不是我们原本的modelClass(String)类型,而是Uri.class类型,并且我们传入modelClass(String)是并不知道它可以转换出什么东西,现在知道了它是可以转换出流数据的,所以第二个入参就是InputStream.class,两个参数的准确性比一个参数的准确性更高,更能有目的的查询我们最终想要的ModelLoader

废话就到这,其他的Factory不一定会那么复杂,具体看细节。那么现在用新类型Uri.class重新查询关系表,会得出什么组合呢。继续分析multiFactory.build()方法

public class MultiModelLoaderFactory {
  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;
      for (Entry<?, ?> entry : entries) {
        if (alreadyUsedEntries.contains(entry)) {
          ignoredAnyEntries = true;
          continue;
        }
        //判断当前的方案能否处理`modelClass(Uri)`和dataClass(InputStream)类型的资源
        if (entry.handles(modelClass, dataClass)) {
          alreadyUsedEntries.add(entry);
          //构建出每一种组合的Loader
          loaders.add(this.<Model, Data>build(entry));
          alreadyUsedEntries.remove(entry);
        }
      }
      //如果高精度查询得出来的还是多个Loader,那就将这些Loader合并成一个MultiModelLoader
      if (loaders.size() > 1) {
        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;
    }

  }
}

modelClass(String)查出了好几个ModelLoader,但这几个Modeloader发现不能直接处理String,然后转成了Uri类型继续查询,但modelClass(String)类型的又查出有很多ModelLoader可以处理它,如果又发现不能直接处理Uri的,又会继续往下查询,相当于递归查询。

好巧不巧,能处理modelClass(Uri)的其中一个ModelLoader刚好就不支持直接处理Uri,这意味着我们又要转类型继续查。那么到底是哪个类值得特意提起的呢,一起来看看。

class UrlUriLoader {
  public static class StreamFactory implements ModelLoaderFactory<Uri, InputStream> {
    public ModelLoader<Uri, InputStream> build(MultiModelLoaderFactory multiFactory) {
      //这里注意,参数转成了GlideUrl类型
      return new UrlUriLoader<>(multiFactory.build(GlideUrl.class, InputStream.class));
    }
  }
}

它就是UrlUriLoader,因为我们的Uri太虚了,不一定是个实质的东西,有可能是本地的,有可能是系统的,有可能是其他应用的,不好说,还有一种可能就是不是本设备的,不是本设备就是远程的呗。那好,给你转成modelClass(GlideUrl)类型再去查询下。GlideUrlGlide自己对链接封装的这么一个类型,用于查询远程资源的。

后续的查询逻辑就不跟踪下去了,直接看ModelLoaderRegistry关系图可以知道,modelClass(GlideUrl)类型可以查询到HttpGlideUrlLoader

到这里开始要往上返回结果了,我们查询了三层modelClass(String)modelClass(Uri)modelClass(GlideUrl),需要将这三层查询到所有支持的结果都往上返回。我们把重要的ModelLoader列出来,便于梳理。

modelClassdataClassFactory
StringInputStreamDataUrlLoader.StreamFactory
StringInputStreamStringLoader.StreamFactory(重点)
StringParcelFileDescriptorStringLoader.FileDescriptorFactory
StringAssetFileDescriptorStringLoader.AssetFileDescriptorFactory

modelClass(String)查到的结果有4个,重点是StringLoader.StreamFactory,由于不能直接处理modelClass(String)查到的结果有类型的资源,所以又转成modelClass(Uri)类型继续查

modelClassdataClassFactory
UriInputStreamDataUrlLoader.StreamFactory
UriInputStreamAssetUriLoader.StreamFactory
UriInputStreamMediaStoreImageThumbLoader.StreamFactory
UriInputStreamMediaStoreVideoThumbLoader.StreamFactory
UriInputStreamUriLoader.StreamFactory
UriInputStreamUrlUriLoader.StreamFactory(重点)

modelClass(Uri)查到的结果有6个,重点是UrlUriLoader.StreamFactory,这是唯一不能直接处理modelClass(Uri)ModelLoader,所以继续转成modelClass(GlideUrl)类型继续查

modelClassdataClassFactory
GlideUrlInputStreamHttpGlideUrlLoader.StreamFactory

modelClass(GlideUrl)查到的结果只有1个,所以直接就构造出HttpGlideUrlLoader返回了

由于MultiModelLoaderFactory中,一个参数的build()是不需要聚合ModelLoader的,两个参数是需要聚合的,所以modelClass(String)查到4个,就是4个。

modelClass(Uri)是用两个参数的build(),查到多个就需要聚合到一起,怎么说呢,便于管理吧,便于理解,总之就将它们聚合到MultiModelLoader中,其实就是一个集合存了多个ModelLoader

现在构建modelClass(String)所有的关系,并且还生成了几个ModelLoader,那么回到getModelLoadersForClass()中,将我们生成好的ModelLoadermodelClass(String)关联缓存起来,下次在用modelClass(String)请求,直接就能找到对应的ModelLoader了。

个人建议从这里往上看效果会更好,但文章不能跳转,所以下面要继续重复列出代码回到某个点
class ModelLoaderRegistry {
  private synchronized <A> List<ModelLoader<A, ?>> getModelLoadersForClass(@NonNull Class<A> modelClass) {
    //查询当前modelClass(String)类型的关系表有没有缓存过,没有缓存就去构建缓存
    List<ModelLoader<A, ?>> loaders = cache.get(modelClass);
    if (loaders == null) {
      //拿到modelClass(String)对应的ModelLoader,然后缓存起来
      loaders = Collections.unmodifiableList(multiModelLoaderFactory.build(modelClass));
      cache.put(modelClass, loaders);
    }
    return loaders;

  }
}

费劲千辛万苦终于回来了,不容易,现在把它存好,下次继续用。现在拿到所有支持的ModelLoader,返回给调用方getModelLoaders()

class ModelLoaderRegistry {
  public <A> List<ModelLoader<A, ?>> getModelLoaders(@NonNull A model) {
    
    //获取该`model(String)`支持的所有ModelLoader
    List<ModelLoader<A, ?>> modelLoaders = getModelLoadersForClass(getClass(model));
    if (modelLoaders.isEmpty()) {
      throw new NoModelLoaderAvailableException(model);
    }

    int size = modelLoaders.size();
    boolean isEmpty = true;
    List<ModelLoader<A, ?>> filteredLoaders = Collections.emptyList();
    //赶紧试试这些ModelLoader能不能处理model(String)
    for (int i = 0; i < size; i++) {
      ModelLoader<A, ?> loader = modelLoaders.get(i);
      //之前是注册表的元素判断能不能处理`modelClass(String)`这样的类型
      //现在是拿到具体对象了,再判断能不能处理具体的内容`model(https://xxx.xxx)`
      if (loader.handles(model)) {
        if (isEmpty) {
          filteredLoaders = new ArrayList<>(size - i);
          isEmpty = false;
        }
        //过滤掉不能处理的ModelLoader,留下能处理的ModelLoader
        filteredLoaders.add(loader);
      }
    }
    //如果没有一个方案,就抛异常。
    if (filteredLoaders.isEmpty()) {
      throw new NoModelLoaderAvailableException(model, modelLoaders);
    }
    return filteredLoaders;

  }
}

由上面分析得知,我们会拿到好三个StringLoader和一个DataUrlLoader,那么既然能构造StringLoader出来必然是因为资源是字符串,所以调用handles()时直接就返回true,而DataUrlLoader判断字符串内容符不符合data:image协议,我们传的是一个网络链接,肯定就不符合了,所以pass一个,最终保留下来的就只有三个StringLoader

model类型ModelLoaderFactory
StringStringLoaderStringLoader.StreamFactory生成(重点)
StringStringLoaderStringLoader.FileDescriptorFactory生成
StringStringLoaderStringLoader.AssetFileDescriptorFactory生成

其他三个虽然都叫StringLoader,但是处理的内容是不一样的,这就不细说了,有兴趣点进每个去看看。分析到这里,getModelLoaders()的源码都分析完了。

总结,通过model的类型去注册表中查询关系,构建所有支持model类型的ModelLoader并缓存起来,最后返回这些ModelLoader之前去过滤一下是否支持model的内容,将剩下的返回。

getModelLoaders()只是负责将所有ModelLoader找出来,但是还没发挥作用,真正使用的地方是在getLoadData()


getRegisteredResourceClasses()

获取真实的可用的解码类型,用来解决当不知道解码类型的时候,可以通过数据和转码类型来确定有什么解码类型可以使用。有点像数学中的,已知速度和距离,求时间

再次强调我们的参数类型

参数类型作用
modelString资源路径
resourceClassObject解码类型
transcodeClassBitmap转码类型
class DecodeHelper {
  List<Class<?>> getRegisteredResourceClasses() {
    return glideContext
        .getRegistry()
        .getRegisteredResourceClasses(model.getClass(), resourceClass, transcodeClass);
  }
}
class Registry {
  @NonNull
  public <Model, TResource, Transcode> List<Class<?>> getRegisteredResourceClasses(
      @NonNull Class<Model> modelClass,
      @NonNull Class<TResource> resourceClass,
      @NonNull Class<Transcode> transcodeClass) {

    //通过参数获取缓存
    List<Class<?>> result =
        modelToResourceClassCache.get(modelClass, resourceClass, transcodeClass);

    if (result == null) {
      result = new ArrayList<>();
      //找出所有处理`modelClass(String)`后得到的数据的类型,比如File、InputStream
      List<Class<?>> dataClasses = modelLoaderRegistry.getDataClasses(modelClass);

      for (Class<?> dataClass : dataClasses) {

        //根据数据类型找出所有真实的解码类型
        List<? extends Class<?>> registeredResourceClasses =
            decoderRegistry.getResourceClasses(dataClass, resourceClass);

        for (Class<?> registeredResourceClass : registeredResourceClasses) {

          //根据真实解码类型和转码类型得到能被转码器处理的转码类型,如果可以支持则返回传入的转码类型
          //作用就是为了确定我用这套解码方案解除的类型,能不能转成你想要的类型,不能的话这套方案就不能用
          List<Class<Transcode>> registeredTranscodeClasses =
              transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);

          if (!registeredTranscodeClasses.isEmpty() && !result.contains(registeredResourceClass)) {
            result.add(registeredResourceClass);
          }
        }
      }
      modelToResourceClassCache.put(
          modelClass, resourceClass, transcodeClass, Collections.unmodifiableList(result));
    }

    return result;

  }
}

modelToResourceClassCacheget()put()就是一个Map中的方法,用来缓存查询的数据的,不是很重要,最后才分析下。

那直接就进入getDataClasses()中分析里面的内容,到底获取了什么东西

class ModelLoaderRegistry {
  @NonNull
  public synchronized List<Class<?>> getDataClasses(@NonNull Class<?> modelClass) {
    return multiModelLoaderFactory.getDataClasses(modelClass);
  }

  synchronized List<Class<?>> getDataClasses(@NonNull Class<?> modelClass) {
    List<Class<?>> result = new ArrayList<>();
    for (Entry<?, ?> entry : entries) {
      //看看什么方案可以处理`modelClass(string)`,处理完之后得到的是什么数据,这个数据类型就是我们想要的
      if (!result.contains(entry.dataClass) && entry.handles(modelClass)) {
        result.add(entry.dataClass);
      }
    }
    return result;
  }
}

这个entries我们在getModelLoaders()中分析过了,忘记了的话可以到ModelLoaderRegistry关系图中再看看。

现在需要根据modelClass(String)找出所有处理modelClass(String)后得到的类型,这里筛选的结果为

dataClass
InputStream
ParcelFileDescriptor
AssetFileDescriptor

拿到这几个类型有什么用呢,接着往下分析。遍历拿到的dataClass并且将resourceClass(Object)传入到decoderRegistry.getResourceClasses()中查询要解码的类型

class ResourceDecoderRegistry {
  public synchronized <T, R> List<Class<R>> getResourceClasses(
      @NonNull Class<T> dataClass, @NonNull Class<R> resourceClass) {

    List<Class<R>> result = new ArrayList<>();
    for (String bucket : bucketPriorityList) {
      List<Entry<?, ?>> entries = decoders.get(bucket);
      if (entries == null) {
        continue;
      }
      for (Entry<?, ?> entry : entries) {
        //看看有哪个资源解码器可以处理`dataClass(InputStream,ParcelFileDescriptor,AssetFileDescriptor)`和`resoruceClass(Object)`
        //既然它能解码这个数据,说明它解码的类型就是我们要的,不过能解码这个数据的应该有很多,那解码类型也有很多
        if (entry.handles(dataClass, resourceClass)
            && !result.contains((Class<R>) entry.resourceClass)) {
          result.add((Class<R>) entry.resourceClass);
        }
      }
    }
    return result;

  }
}

根据ResourceDecoderRegistry关系图可以分析出来,传入 dataClass(InputStream、ParcelFileDescriptor、AssetFileDescriptor)resourceClss(Object)可以得出的解码类型为

dataClassresourceClass真实解码类型
InputStreamObject[Bitamp,BitampDrawable,GifDrawable]
ParcelFileDescriptorObject[Bitmap,BitmapDrawable]
AssetFileDescriptorObjectBitmap

这个图是什么意思呢,大概意思是,我用InputStream可以解码出三种类型,分别是BitmapBitmapDrawableGifDrawable;同样,用ParcelFileDescriptor可以解出两种类型,用AssetFileDescriptor可以解出一种类型。

拿到解码类型之后,还需要继续用真实的解码类型查找出转码类型,这里会有点困惑,transcodeClass就是转码类型的参数,为什么还要去找转码类型。是这样的,如果我用这套方案解码出来后不能转成你想要的类型,那么这套解码方案就不能用,那如何知道能不能转码呢,问问转码器不就知道了嘛。

那我们用这三种类型resourceClass(Bitmap、BitmapDrawable、GifDrawable)transcodeClass(Drawable)的组合传入transcoderRegistry.getTranscodeClasses()中查找转码类型,如果可以支持则返回传入的转码类型。

class TranscoderRegistry {
  public synchronized <Z, R> List<Class<R>> getTranscodeClasses(
      @NonNull Class<Z> resourceClass, @NonNull Class<R> transcodeClass) {

    List<Class<R>> transcodeClasses = new ArrayList<>();
    //如果解码类型是转码类型的子类,直接返回转码类型
    //比如解码类型是GifDrawable,转码类型是Drawable,这样也是成立的,就会返回Drawable
    if (transcodeClass.isAssignableFrom(resourceClass)) {
      transcodeClasses.add(transcodeClass);
      return transcodeClasses;
    }

    //查询有哪个转码器可以处理真实的解码类型转成转码类型的
    for (Entry<?, ?> entry : transcoders) {
      if (entry.handles(resourceClass, transcodeClass)) {
        transcodeClasses.add(transcodeClass);
      }
    }
    return transcodeClasses;

  }
}

虽然这个方法是用来查询转码类型的,但是在getRegisteredResourceClasses()这个方法中,我们只用来验证我们查询到的解码类型是可以用来转成使用者想要的类型的,是可用的。

通过层层校验,最终确定了我们可以使用的解码类型有GitDrawableBitmapBitmapDrawable这三种。

modelClassresourceClasstranscodeClass真正可用的解码类型resourceClass
StringObjectDrawable[GifDrawable,Bitmap,BitmapDrawable]

那么拿到这组关系,就可以存起来,以便下次可以继续使用,不用重复查询。

class ModelToResourceClassCache {
  public void put(
      @NonNull Class<?> modelClass,
      @NonNull Class<?> resourceClass,
      @NonNull Class<?> transcodeClass,
      @NonNull List<Class<?>> resourceClasses) {
    synchronized (registeredResourceClassCache) {
      //生成了一个KEY包装这组关系
      registeredResourceClassCache.put(
          new MultiClassKey(modelClass, resourceClass, transcodeClass), resourceClasses);
    }
  }
}

对应取的方法就是这样的。

class ModelToResourceClassCache {
  public List<Class<?>> get(
      @NonNull Class<?> modelClass,
      @NonNull Class<?> resourceClass,
      @NonNull Class<?> transcodeClass) {

    MultiClassKey key = resourceClassKeyRef.getAndSet(null);
    if (key == null) {
      key = new MultiClassKey(modelClass, resourceClass, transcodeClass);
    } else {
      key.set(modelClass, resourceClass, transcodeClass);
    }
    final List<Class<?>> result;
    synchronized (registeredResourceClassCache) {
      result = registeredResourceClassCache.get(key);
    }
    resourceClassKeyRef.set(key);
    return result;

  }
}

总结getRegisteredResourceClasses()这个方法是用来查询真实的解码类型的。为什么要查解码类型呢,因为我们默认不指定的话,是不知道一个数据可以解码成什么,所以默认就是resourceClass(Object),但是可以通过预先在注册表设定支持的类型,通过modelClassresourceClasstranscodeClass三个参数就可以确定下来可以使用的解码类型有哪些。


getSourceEncoder()

根据源数据类型查找编码器,调用这个方法的时候一般是加载远程数据后需要编码时,也就是四级缓存做的事情。

拿到远程数据一般来说都是流或者字节数组,以我们固定的参数来说,我们拿到远程的数据就是一个流:ContentLengthInputStream,它是FileInputStream的子类。

class DecodeHelper {
  <X> Encoder<X> getSourceEncoder(X data) throws Registry.NoSourceEncoderAvailableException {
    return glideContext.getRegistry().getSourceEncoder(data);
  }
}
class Registry {
  public <X> Encoder<X> getSourceEncoder(@NonNull X data) throws NoSourceEncoderAvailableException {
    Encoder<X> encoder = encoderRegistry.getEncoder((Class<X>) data.getClass());
    if (encoder != null) {
      return encoder;
    }
    //找不到合适的编码器抛异常
    throw new NoSourceEncoderAvailableException(data.getClass());
  }
}
class EncoderRegistry {
  public synchronized <T> Encoder<T> getEncoder(@NonNull Class<T> dataClass) {
    for (Entry<?> entry : encoders) {
      if (entry.handles(dataClass)) {
        return (Encoder<T>) entry.encoder;
      }
    }
    return null;
  }
}

而通过EncoderRegistry关系图看出,能编码源数据的编码器就两个,要么将流编码到文件,要么将字节编码到文件。

总结,根据源数据类型查找编码器,将源数据缓存到本地


getResultEncoder()

根据解码后的类型查找资源编码器,当我们的资源已经是Bitmap这样的位图对象的时候,不能再像ByteBuffer那样直接编码了。那么什么地方会用到资源编码器呢,那当然是在我们需要缓存转换过的资源的时候,

以我们固定参数为例,解码后得到的类型是BitampBitampDrawableGifDrawable这三种,这里以Bitmap作为参数来查询资源编码器。

class DecodeHelper {
  <Z> ResourceEncoder<Z> getResultEncoder(Resource<Z> resource) {
    return glideContext.getRegistry().getResultEncoder(resource);
  }
}
class Reigstry {
  public <X> ResourceEncoder<X> getResultEncoder(@NonNull Resource<X> resource)
      throws NoResultEncoderAvailableException {
    ResourceEncoder<X> resourceEncoder = resourceEncoderRegistry.get(resource.getResourceClass());
    if (resourceEncoder != null) {
      return resourceEncoder;
    }
    throw new NoResultEncoderAvailableException(resource.getResourceClass());
  }
}
class ResourceEncoderRegistry {
  public synchronized <Z> ResourceEncoder<Z> get(@NonNull Class<Z> resourceClass) {
    //遍历编码器,找出能编码这个类型的编码器
    for (int i = 0, size = encoders.size(); i < size; i++) {
      Entry<?> entry = encoders.get(i);
      if (entry.handles(resourceClass)) {
        return (ResourceEncoder<Z>) entry.encoder;
      }
    }
    return null;
  }
}

这里使用到的参数是资源类型,资源就是我们常用来显示位图信息的,既然已经是位图对象了所以编码过程自然不会像原始数据编码那样直接,为了区别编码的内容,将编码器分为了源数据编码器和资源编码器(我瞎理解的)。

ResourceEncoderRegistry关系图可以看到,其实编码器就几个,所以逻辑比较简单。

总结,通过资源类型查找编码器,将转换过的资源缓存到本地。


isResourceEncoderAvailable()

判断解码后的类型是否可以编码,如果我们要缓存转换过的资源的时候,至少得知道有没有适合的资源编码器将它编码吧,从列表查一下就知道了。

以我们固定参数为例,解码后得到的类型是BitampBitampDrawableGifDrawable这三种,这里以Bitmap作为参数来查询资源编码器。

class DecodeHelper {
  boolean isResourceEncoderAvailable(Resource<?> resource) {
    return glideContext.getRegistry().isResourceEncoderAvailable(resource);
  }
}
class Registry {
  public boolean isResourceEncoderAvailable(@NonNull Resource<?> resource) {
    return resourceEncoderRegistry.get(resource.getResourceClass()) != null;
  }
}

总结,前面分析getResultEncoder()的源码就有调用过resourceEncoderRegistry.get(),只不过getResultEncoder()真的是用来获取编码器,而isResourceEncoderAvailable()仅用于判断有没有资源编码器可用。


getTransformation()

根据解码类型获取对应的转换器,用于在解码后,需要对资源进行各种样式的转换时调用。

这里的真实解码类型以我们固定参数为例,会有三种真实解码类型

真实解码类型
Bitmap
BitmapDrawable
GifDrawable
class DecodeHelper {
  <Z> Transformation<Z> getTransformation(Class<Z> resourceClass) {

    Transformation<Z> result = (Transformation<Z>) transformations.get(resourceClass);
    if (result == null) {
      for (Entry<Class<?>, Transformation<?>> entry : transformations.entrySet()) {
        if (entry.getKey().isAssignableFrom(resourceClass)) {
          result = (Transformation<Z>) entry.getValue();
          break;
        }
      }
    }

    if (result == null) {
      if (transformations.isEmpty() && isTransformationRequired) {
        throw new IllegalArgumentException("简化代码");
      } else {
        return UnitTransformation.get();
      }
    }
    return result;

  }
}
支持的解码类型转换器
GifDrawableGifDrawableTransformation
BitmapDrawableDrawableTransformation
DrawableDrawableTransformation
BitmapFitCenter(不是固定的)

虽然这里列出了默认的转换器,但是不同的请求条件不一定都会有,或者一个都没有,有没有转换类型取决于几个条件:

  • into()的是不是View,因为如果是View的话,可以从View中获取getScaleType()一个类型,这些类型可以对应一个转换类型,那么自然就有一个了。

  • 有没有主动或被动设置过转换的功能,被动就像上一个条件,如果into()的是View则会被动增加一个转换类型,主动嘛就是我们手动调用的centerCropRoundedCorners这样的转换。

  • 无论主动被动调用,只要调用过,都会默认添加内置的三种转换。

解释一下这三种转换器的存在意义:

  • FitCenter,是调整位图的缩放样式的,继承于BitmapTransformation,看名字就知道是直接操作位图的,所以如果我们拿到的解码类型是Bitmap,那就直接给转换。类似的,只要是BitmapTransformation的子类都表示可以直接操作位图。

  • DrawableTransformation 平时使用的Drawable并不是直接的位图,是将位图包装了一层,既然它不是位图,那么BitmapTransformation类型的转换器就不能直接给它转换,又不能直接转换,转换完了还得给别人返回Drawable类型,BitmapTransformation可不支持干这事,你给我Bitmap,完事我给你个转换过的Bitmap,那咋整。所以这时候就搞了个DrawableTransformation转换器,Drawable你们都来我这转换,完事了我还能给你返回原本的类型。那它是怎么做到的呢?既然Drawable里是包装了一个Bitmap,那把它取出来不就可以拿去转换了吗,转换完再用Bitmap重新生成一个Drawable这不就解决了吗。所以DrawableTransformation就是干这事的。
class DrawableTransformation {
  public Resource<Drawable> transform(@NonNull Context context, @NonNull Resource<Drawable> resource,
                                      int outWidth, int outHeight) {

    BitmapPool bitmapPool = Glide.get(context).getBitmapPool();
    Drawable drawable = resource.get();
    //将Drawable的Bitmap拿出来,也有可能是重新画,具体可以自己看看逻辑
    Resource<Bitmap> bitmapResourceToTransform =
        DrawableToBitmapConverter.convert(bitmapPool, drawable, outWidth, outHeight);
    if (bitmapResourceToTransform == null) {
      if (isRequired) {
        throw new IllegalArgumentException("Unable to convert " + drawable + " to a Bitmap");
      } else {
        return resource;
      }
    }
    //将拿出来的Bitmap做转换
    Resource<Bitmap> transformedBitmapResource =
        wrapped.transform(context, bitmapResourceToTransform, outWidth, outHeight);

    if (transformedBitmapResource.equals(bitmapResourceToTransform)) {
      transformedBitmapResource.recycle();
      return resource;
    } else {
      //转换完了生成新的Drawable
      return newDrawableResource(context, transformedBitmapResource);
    }
  }
}
  • GifDrawableTransformation 看名字就知道是用来处理Gif的,Gif其实也是有一个个Bitmap组成,但是它的转换逻辑很复杂。和BitmapDrawable一样,GifDrawable本身也不是一个位图,只是内部包含了Bitmap,那么也要一套特殊的逻辑将Bitmap拿出来转换。而且GifDrawable并不是一次就将所有的位图都加载出来,而是一帧一帧的加载,那么当前帧转换完了,下一帧呢,如何知道要怎么转换。所以GifDrawableTransformation除了要把Bitmap拿出来转换,还要给GifDrawable记录转换器,下一帧加载的时候用上次记录的转换器继续转换。不过不需要像DrawableTransformation那样需要生成一个新的GifDrawableGifDrawable的切换下一帧逻辑比较复杂,三言两语不好说,这里就不介绍了。
class GifDrawableTransformation {
  public Resource<GifDrawable> transform(@NonNull Context context, @NonNull Resource<GifDrawable> resource,
                                         int outWidth,int outHeight) {
    GifDrawable drawable = resource.get();
    BitmapPool bitmapPool = Glide.get(context).getBitmapPool();
    //取出当前加载的帧,firstFrame的含义不一定是第一帧
    Bitmap firstFrame = drawable.getFirstFrame();
    Resource<Bitmap> bitmapResource = new BitmapResource(firstFrame, bitmapPool);
    //转换Bitmap
    Resource<Bitmap> transformed = wrapped.transform(context, bitmapResource, outWidth, outHeight);
    if (!bitmapResource.equals(transformed)) {
      bitmapResource.recycle();
    }
    Bitmap transformedFrame = transformed.get();
    //将转换后的Bitmap重新设置给第一帧,并且记录转换器
    drawable.setFrameTransformation(wrapped, transformedFrame);
    //返回原本的GifDrawable
    return resource;
  }
}

还有一种转换器MultiTransformation 这个前面没有列出来,这里稍微提一下,就是一个转换器集合,如果我们添加了多个转换器,避免搞得太复杂,将它们都添加到一个里面就好了。

了解了三种转换器的作用,可以稍微总结一下为什么需要这三种作为默认转换器:不同的转换器为不同的资源处理不同的逻辑,并且还需要返回相同的资源类型,因为资源类型有三种,按类型处理的也需要有三种。不废话了,已经超纲很多了。

总结,根据真实的解码类型获取可以支持的转换器。


getLoadPath()

根据数据类型获取加载路径,调用这个方法是在拿到远程数据或读取本地文件数据时,需要通过一个执行路径去处理这个数据。

以我们固定参数为例,不管获取远程数据或读取本地文件数据都会拿到ByteBuffer类型的数据,所以以下三个参数的类型为

参数类型
dataClassByteBuffer
resourceClassObject
transcodeClassDrawable
class DecodeHelper {
  <Data> LoadPath<Data, ?, Transcode> getLoadPath(Class<Data> dataClass) {
    return glideContext.getRegistry().getLoadPath(dataClass, resourceClass, transcodeClass);
  }
}
class Registry {
  public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> getLoadPath(
      @NonNull Class<Data> dataClass,
      @NonNull Class<TResource> resourceClass,
      @NonNull Class<Transcode> transcodeClass) {

    //根据参数组合获取LoadPath缓存
    LoadPath<Data, TResource, Transcode> result =
        loadPathCache.get(dataClass, resourceClass, transcodeClass);
    if (loadPathCache.isEmptyLoadPath(result)) {
      return null;
    } else if (result == null) {
      //根据参数构建DecodePath
      List<DecodePath<Data, TResource, Transcode>> decodePaths =
          getDecodePaths(dataClass, resourceClass, transcodeClass);
    
      if (decodePaths.isEmpty()) {
        result = null;
      } else {
        //根据参数构建LoadPath
        result = new LoadPath<>(dataClass, resourceClass, transcodeClass,
                                decodePaths, throwableListPool);
      }
      //根据参数缓存LoadPath
      loadPathCache.put(dataClass, resourceClass, transcodeClass, result);
    }
    return result;
  }
}

DecodePath才是我们关心的那个,LoadPath则是又包装一层而已,由于关系都是确定的,所以构造后缓存起来。

getDecodePaths()又是一个很复杂的方法,和getRegisteredResourceClasses()有点像但又不完全一样,而且参数不同了结果也会有变化

class Registry {
  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<>();

    //获取真实解码类型
    List<Class<TResource>> registeredResourceClasses =
        decoderRegistry.getResourceClasses(dataClass, resourceClass);

    for (Class<TResource> registeredResourceClass : registeredResourceClasses) {

      //根据真实解码类型和转码类型得到能被转码器处理的转码类型,如果可以支持则返回传入的转码类型
      List<Class<Transcode>> registeredTranscodeClasses =
          transcoderRegistry.getTranscodeClasses(registeredResourceClass, transcodeClass);

      for (Class<Transcode> registeredTranscodeClass : registeredTranscodeClasses) {

        //根据源数据dataClass(ByteBuffer)和真实解码类型获取资源解码器
        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;
  }
}

不管参数怎么变,通过ResourceDecoderRegistry关系图都可以一一找出对应关系,所以dataClass(ByteBuffer)resrouceClass(Object)找出来真实的解码类型为三种

dataClassresourceClass真实解码类型
ByteBufferObject[Bitamp,BitampDrawable,GifDrawable]

有了真实的解码类型,可以调用transcoderRegistry.getTranscodeClasses()查找下转码类型,也不算查找吧,只能说是验证一下有没有转码器能处理真实的解码类型和转码类型,能处理那么这个解码类型和转码类型就是可用的,直接将这个转码类型返回就行,一般来说每次验证只会返回一个转码类型,但为什么返回值是一个集合就不得而知。转码类型的关系图可以参考TranscoderRegistry关系图

真实解码类型transcodeClass可支持的转码类型
BitmapDrawableDrawable
BitampDrawableDrawableDrawalbe
GifDrawableDrawableDrawable

这里是校验真实解码类型和transcodeClass(Drawable)类型是否能被支持,以我们这里为例,既然都支持Drawable这么顶级的类型了,那至于是转BitmapDrawable还是GifDrawable都没区别,返回个Drawable告诉你,只要是这个类型尽管转。

接下来要查询资源解码器了,通过dataClass(ByteBuffer)和真实的解码类型就可以确定使用哪个资源解码器,但是由于解码器众多,只要参数一样就会在不同分类中找到同一个解码器,所以避免不了会出现重复的解码器,但无所谓,只要一个解码器能处理,其他的都不会处理了。解码器类型的关系图可以参考ResourceDecoderRegistry关系图

class ResourceDecoderRegistry {
  public synchronized <T, R> List<ResourceDecoder<T, R>> getDecoders(
      @NonNull Class<T> dataClass, @NonNull Class<R> resourceClass) {

    List<ResourceDecoder<T, R>> result = new ArrayList<>();
    for (String bucket : bucketPriorityList) {
      List<Entry<?, ?>> entries = decoders.get(bucket);
      if (entries == null) {
        continue;
      }
      //尝试所有的解码器,找到处理源数据和真实的解码类型的解码器
      for (Entry<?, ?> entry : entries) {
        if (entry.handles(dataClass, resourceClass)) {
          result.add((ResourceDecoder<T, R>) entry.decoder);
        }
      }
    }
    return result;

  }
}
dataClass真实解码类型解码器
ByteBufferBitamp[ByteBufferBitmapDecoder,VideoDecoder]
ByteBufferBitampDrawable[BitmapDrawableDecoder,BitmapDrawableDecoder]
ByteBufferGifDrawableByteBufferGifDecoder

可以看到ByteBufferBitmapDecoderVideoDecoder都能从ByteBuffer解码成Bitmap,只要支持就添加进来,后面到底用哪个还会继续判断。

接着继续查询资源转码器,通过真实的解码类型和可支持的转码类型就可以确定使用哪个资源转码器。转码器类型的关系图可以参考TranscoderRegistry关系图

class TranscoderRegistry {
  public synchronized <Z, R> ResourceTranscoder<Z, R> get(
      @NonNull Class<Z> resourceClass, @NonNull Class<R> transcodedClass) {
    if (transcodedClass.isAssignableFrom(resourceClass)) {
      return (ResourceTranscoder<Z, R>) UnitTranscoder.get();
    }
    //遍历所有的转码器,能处理真实的解码类型和转码类型的就直接返回
    for (Entry<?, ?> entry : transcoders) {
      if (entry.handles(resourceClass, transcodedClass)) {
        return (ResourceTranscoder<Z, R>) entry.transcoder;
      }
    }

    throw new IllegalArgumentException(
        "No transcoder registered to transcode from " + resourceClass + " to " + transcodedClass);
  }
}

查询转码器比较简单,因为能转码的类型就那几样,并且都是一对一的,查到直接就返回,查不到就跑异常,毕竟连UnitTranscode这个不用转码的转码器都不支持确实需要抛异常。

比如BitmapDrawableDrawable的父类,这种转码是毫无意义的,所以UnitTranscode是解决这种无意义的转码,直接返回,你调用者就当Drawable使用就行了。

真实解码类型可支持的转码类型转码器
BitmapDrawableBitmapDrawableTranscoder
BitampDrawableDrawableUnitTranscoder
GifDrawableDrawableUnitTranscoder

那么将以上查询出的真实的解码类型支持的转码类型解码器转码器的数据组合成DecodePath会有以下的可能性

dataClass真实解码类型可支持的转码类型转码器解码器
ByteBufferBitmapDrawableBitmapDrawableTranscoder[ByteBufferBitmapDecoder,VideoDecoder]
ByteBufferBitmapDrawableDrawableUnitTranscoder[BitmapDrawableDecoder,BitmapDrawableDecoder]
ByteBufferGifDrawableDrawableUnitTranscoderByteBufferGifDecoder

以上就是我们拿到源数据之后的每一种解决方案,总有一款会适合我们的需求,不行就抛异常。

拿到这些解决方案之后返回到getLoadPath(),将它和参数包装成LoadPath,然后缓存起来,避免重复查询。缓存的方案在getRegisteredResourceClasses()中也见过了,差不多是一个意思,就不分析了。

class LoadPathCache {
  public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> get(
      Class<Data> dataClass, Class<TResource> resourceClass, Class<Transcode> transcodeClass) {
    MultiClassKey key = getKey(dataClass, resourceClass, transcodeClass);
    LoadPath<?, ?, ?> result;
    synchronized (cache) {
      result = cache.get(key);
    }
    keyRef.set(key);

    return (LoadPath<Data, TResource, Transcode>) result;
  }
}
class LoadPathCache {
  private MultiClassKey getKey(
      Class<?> dataClass, Class<?> resourceClass, Class<?> transcodeClass) {
    MultiClassKey key = keyRef.getAndSet(null);
    if (key == null) {
      key = new MultiClassKey();
    }
    key.set(dataClass, resourceClass, transcodeClass);
    return key;
  }
}

总结getLoadPath()是根据参数去寻找合适的加载路径,路径有可能有多条,需要一一尝试,最终有一条可以执行,如果没有则不支持,在适当的时机抛出异常。

getLoadPath()的参数不一定是ByteBuffer,从本地文件读取有可能是流之类的,不管如何,根据参数一定能从注册表中查询到关系。


getLoadData()

前面分析过getModelLoaders()了,主要是根据model来查询出所有的ModelLoaderModelLoader算是一个加载方案的工厂吧,没法直接使用的,需要工厂生成加载方案的对象,LoadData也不是最终的对象,也是用来包装的,不知道包装那么多层有何意义。

你是不是想问为啥不直接在获取ModelLoader的时候直接就构造那些需要使用的对象了。我想到两个原因:

  • 先要解耦,注册表关心的是查询关系,给我一个查询的参数,我给你查出来,至于后面怎么用是你的事,如果一次就给我很多的参数,就破坏了设计。

  • 其次这些加载方案是需要根据不同的参数执行的,自然是调用的时候去根据参数构造更好,因为人家说了是查询的,非要让别人在查询的时候生成额外的对象,也不合适。

以上就是个人猜测,可能不准确,八九不离十吧。

那么根据之前分析的getModelLoaders(),可以拿到三个ModelLoader,分别是:

ModelLoader解析类型子集
StringLoaderStream[DataUrlLoader,AssetUriLoader,MediaStoreImageThumbLoader,MediaStoreVideoThumbLoader,QMediaStoreUriLoader,UriLoader,UrlUriLoader,HttpGlideUrlLoader]
StringLoaderParcelFileDescriptor[AssetUriLoader,QMediaStoreUriLoader,UriLoader]
StringLoaderAssetFileDescriptor[UriLoader]

虽然拿到的都叫StringLoader,但是处理的事情是不一样的,里面包含的子集也是不一样

List<LoadData<?>> getLoadData() {

  if (!isLoadDataSet) {
    isLoadDataSet = true;
    loadData.clear();
    List<ModelLoader<Object, ?>> modelLoaders = glideContext.getRegistry().getModelLoaders(model);
    for (int i = 0, size = modelLoaders.size(); i < size; i++) {
      ModelLoader<Object, ?> modelLoader = modelLoaders.get(i);
      //去构造所有的加载方案需要的对象
      LoadData<?> current = modelLoader.buildLoadData(model, width, height, options);
      if (current != null) {
        loadData.add(current);
      }
    }
  }
  return loadData;
  
}

StringLoader为例分析下buildLoadData(),没有比StringLoaer更复杂的ModelLoader了,既是个体也包含聚合体,分析完StringLoader,其他都是洒洒水。

class StringLoader {
  @Override
  public LoadData<Data> buildLoadData(@NonNull String model, int width,
                                      int height, @NonNull Options options) {

    //将字符串转Uri,一般情况下字符串的内容都是某种资源的定位
    Uri uri = parseUri(model);
    //如果转失败了,那必然就不用继续了
    //前面由于只是分析Modeloadr能不能处理字符串,但到这了,是判断能不能处理Uri的内容了
    if (uri == null || !uriLoader.handles(uri)) {
      return null;
    }
    //uriLoader是MultiModelLoader,所以需要进入它里面分析
    return uriLoader.buildLoadData(uri, width, height, options);
  }

}

之前我们都是用的类型去查询,去构造的,现在终于要进入干正事的时候了,就不能再靠类型去做事情,需要将字符串转成比较通用的Uri,用来定位字符串所表示的内容。

既然有具体内容了,先让所有的ModelLoader过一遍,看看能不能处理这个Uri的内容,只要有一个通过就行了,源码很简单就不进去分析了。

class MultiModelLoader {
  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);

    for (int i = 0; i < size; i++) {
      ModelLoader<Model, Data> modelLoader = modelLoaders.get(i);
      //前面只是校验有没有必要进入buildLoadData()这个方法,这次是真要构造对象了,必须一个个判断
      if (modelLoader.handles(model)) {
        LoadData<Data> loadData = modelLoader.buildLoadData(model, width, height, options);
        if (loadData != null) {
          //这里的sourceKey是用Key将model包装了一下
          //也就是Key("https://www.baidu.com")这样,但是Key有很多类型的哦
          sourceKey = loadData.sourceKey;
          //fetcher才是真正用于加载数据对象,loadData只是包装了下
          fetchers.add(loadData.fetcher);
        }
      }
    }
    //将过滤出来的数据提取器合并到一个LoadData中
    return !fetchers.isEmpty() && sourceKey != null
        ? new LoadData<>(sourceKey, new MultiFetcher<>(fetchers, exceptionListPool))
        : null;

  }
}

这里有两个点比较疑惑:

  • sourceKey的赋值方式是覆盖的,而生成sourceKeyKey的类型不一定一样,一个LoadData可能对应一种Key,这样覆盖会不会有影响。

  • 就上一个疑惑点,如果一个LoadData对应一种Key,那么还将所有的fetcher聚合一起的意义是什么,又不能精准查询每个fetcher了。

由于我们的固定参数无法测出多样性,所以这里就先这样吧,如果有了解的小伙伴可以告知一下。

那么按我们关注的StringLoader在调用buildLoadData()时,只有HttpGlideUrlLoader能处理,所以最后还是进入到了HttpGlideUrlLoader.buildLoadData()

class HttpGlideUrlLoader {
  public LoadData<InputStream> buildLoadData(
      @NonNull GlideUrl model, int width, int height, @NonNull Options options) {
    //GlideUrl是在UrlUriLoader生成的,并不是很复杂就不分析了
    GlideUrl url = model;
    if (modelCache != null) {
      url = modelCache.get(model, 0, 0);
      if (url == null) {
        modelCache.put(model, 0, 0, model);
        url = model;
      }
    }
    //从参数中获取超时时间
    int timeout = options.get(TIMEOUT);
    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));

  }
}

代码很简单,就是为了构造个HttpUrlFetcher网络数据提取器。

那么经过调用三个StringLoader.buildLoaData,其实只有HttpGlideUrlLoader这个能处理,所以最终生成了个LoadData(GlideUrl,MultiFetcher(HttpUrlFetcher))这样的对象,这就是我们数据的加载方案。

个人理解getLoadData()是获取资源数据前的加载方案,getLoadPath()是获取资源数据后的处理方案,一个前,一个后,一个加载,一个处理。

总结getLoadData()是用model来构建适合的加载方案,虽然任可能有多种加载方案,但比之前用类型筛选出来的已经更准确了,不过以我们固定参数为例,只有一种加载方案能处理。


getCacheKeys()

List<Key> getCacheKeys() {
  if (!isCacheKeysSet) {
    isCacheKeysSet = true;
    cacheKeys.clear();
    List<LoadData<?>> loadData = getLoadData();
    //将LoadData的sourceKey存起来
    for (int i = 0, size = loadData.size(); i < size; i++) {
      LoadData<?> data = loadData.get(i);
      if (!cacheKeys.contains(data.sourceKey)) {
        cacheKeys.add(data.sourceKey);
      }
      //应该已经过时了,没有应用的逻辑
      for (int j = 0; j < data.alternateKeys.size(); j++) {
        if (!cacheKeys.contains(data.alternateKeys.get(j))) {
          cacheKeys.add(data.alternateKeys.get(j));
        }
      }
    }
  }
  return cacheKeys;
}

看完getLoadData()再看getCacheKes()会发现这逻辑简直不要太简单,就是将LoadData.sourceKey存起来。

sourceKey都是知道,就是用Key(model)包装了资源,Key的类型不是完全一致的,大部分用的是ObjectKey,网络请求用的是GlideUrl,它实现了Key

那么存起来的Key有什么作用呢,目前为止发现的两个作用:

  • 将源数据编码到本地文件的时候,需要一个Key,这是个聚合的Key,里面就包括了LoadData.sourceKey,下次取缓存的时候需要的Key

  • DecodeHelper.isSourceKey()判断当前Key是否是sourceKey,这个方法由于功能虽然简单,但用途不明确,以及分析来下必然返回true的情况看来,无法得知作用是什么,有知道的小伙伴请务必告知。

总结getCacheKey()用于将从getLoadData()获取到的所有的LoadData.sourceKey缓存起来。可以给三级缓存查询本地缓存(ResourceCacheGenerator,DataCacheGenerator)都可以,因为四级缓存(SourceGenerator)是用LoaData.sourceKey作为聚合Key,将文件缓存到三级缓存的


由于本人的技术水平和写作水平有限,可能有什么地方理解不到位或者解释不到位的请多多担待,也请不吝指点,但不喜勿喷。

这原本是本人的学习源码的手记,意在给本人快速回忆对源码的理解,所以大部分的用词或说明都是以我本人的喜好和习惯书写。但既然要分享出来让大伙也能容易懂,所以在统一用词和专业词汇之间改写了非常多遍,在这方面花的时间可比分析源码要费劲得多,也可能是写作水平的问题。

如果本文对您有帮助,请多多发表您的评论以及收藏或关注。

以上全部源码以及说明都是由本人亲自码出来的,请各位看官尊重劳动成果,切勿在本人未允许的情况下转载。如已经本人同意转载,请标明文章来源,附带原文链接。

有兴趣探讨技术或者有疑问的小伙伴可以加我好友,我会在评论区回复,本人菜狗一只,如果有大佬想带我就更好了^_^。