Glide学习---模型转换器

ModelLoader到底代表了什么?
------ 现在还无法太好的回答. ModelLoader是Glide使用的一种资源转换, 资源加载时对数据源进行描述的一种机制.

ModelLoader定义了一个方法:

LoadData buildLoadData(@NonNull Model model, int width, int height,
      @NonNull Options options);

看到其目的就是返回一个LoadData对象. 从Model类型到LoadData对象, 这就是ModelLoader的作用, 一种对应关系, 一种结构化的配置.

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2018/0403/9555.html

这个篇文章列出了几乎所有(我没具体统计是不是绝对的)的模型转化器定义.

先从简单的开始理解吧:

FileLoader

image.png

可以看到FileLoader这个类本身并不是一个转换器, 它更像是一个工具类,或者容器.

具体的模型装换是StreamFactory和FileDescriptorFactory这两个类.
这两个类的超类ModelLoaderFactory是要关注的重点. ModelLoaderFactory定义了一个build方法

@NonNull
  ModelLoader build(@NonNull MultiModelLoaderFactory multiFactory);

此方法返回ModelLoader对象.
具体到StreamFactory和FileDescriptorFactory这两个类, 都返回一个FileLoader对象, 如下:

public final ModelLoader build(@NonNull MultiModelLoaderFactory multiFactory) {
      return new FileLoader<>(opener);
    }

不同的是其中的参数opener不同, 一个是FileOpener 一个是FileOpener. 分别去实现打开一个流和一个文件描述符.
而FileLoader作为一个ModelLoader子类, 其实现的buildLoadData()方法如下:

@Override
  public LoadData buildLoadData(@NonNull File model, int width, int height,
      @NonNull Options options) {
    return new LoadData<>(new ObjectKey(model), new FileFetcher<>(model, fileOpener));
  }

于是我们又发现了一个新类: FileFetcher . 这是DataFetcher的实现类,

public interface DataFetcher {
    ....
    void loadData(@NonNull Priority priority, @NonNull DataCallback callback);
    ...

这个类/接口通过loadData(方法)定义了加载的功能, 具体到FileFetcher , 如下实现:

 @Override
    public void loadData(@NonNull Priority priority, @NonNull DataCallback callback) {
      try {
        data = opener.open(file);
      } catch (FileNotFoundException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to open file", e);
        }
        callback.onLoadFailed(e);
        return;
      }
      callback.onDataReady(data);
    }

定义了从文件加载数据的逻辑.

大量的泛型的使用, 看的人很晕.
ModelLoaderFactory是为了构建ModelLoader对象.
ModelLoader是为了创建出一个LoadData对象

class LoadData {
    public final Key sourceKey;
    public final List alternateKeys;
    public final DataFetcher fetcher;

    ....
  }

而LoadData是一个载体, 里面有要加载数据模型的描述, 有具体执行加载操作的DataFetcher实例.

实际上, LoadData只是个静态的数据集(bean), 自身并不具备加载数据的能力. 加载工作还是要委托给DataFetcher变量.

FileLoader类中的FileOpener接口是自定义的, 内部使用, 主要就是FileFetcher在用.具体的加载操作都是FileOpener在做, 实际上就是很简单的把file转换成FileInputStream或者FileDescriptorFactory

一个FileLoader类, 定义了对应的factory类, DataFetcher类, 这两者是整个ModelLoader体系的概念.
还定义了FileOpener接口,及其具体实现类(两个匿名内部类).
这很好的演示了高聚合(所有file->data的逻辑都放在一起), 低耦合(所有的概念组件之间通过组合形成)
赞 ! 我都忘记了我只是在学习glide代码.

StringLoader

再看看StringLoader


image.png

这里面的实现逻辑又跟FileLoader里的完全不同了.

StringLoader的buildLoadData实现完全是委托给变量uriLoader来实现的. 本身只负责把String类型的模型数据转换为uriLoader可以处理的模型类型.


@Override
  public LoadData 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);
  }

可以看到其中有三个factory工厂类: StreamFactory, FileDescriptorFactory, AssetFileDescriptorFactory

通过三个工厂类的声明, 可以感觉这三个类分别是把String类型的数据模型转换成:InputStream, ParcelFileDescriptor. AssetFileDescriptor. 但是具体怎么转换的呢? 它们各自的build方法又是委托给了注入的参数MultiModelLoaderFactory multiFactory. 三者都是如此.

有可以说实际上, StringLoader只做了一件事, 就是把string转换为uri, 然后具体的架子工作又委托给了UriLoader了.


@Override
  public LoadData 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);
  }

那实际上StringLoader也没什么可研究的了, 去看看UriLoader吧.

UriLoader

image.png

首先他的public LoadData buildLoadData(@NonNull Uri model, int width, int height,

@NonNull Options options) 方法逻辑是典型的, 返回一个LoadData对象. 前面说过, 这个对象只是一个载体, 具体的加载操作是其中的DataFetcher负责.

在UriLoader类中, 定义了一个本地工厂接口:LocalUriFetcherFactory, 用来构建DataFetcher对象.


public interface LocalUriFetcherFactory {
    DataFetcher build(Uri uri);
  }

注意: 但凡是这种在类内部定义的接口, 都是为了内部实现的方便, 不是整个glide体系内的公用接口.

它这种实现方式值得仔细体会, 深入学习. 这比glide具体的流程研究更重要.

仔细理解一下三个工厂类的实现, 都是在build()方法中简单的返回UriLoader类实例. 都有把自身作为LocalUriFetcherFactory参数.


public ModelLoader build(MultiModelLoaderFactory multiFactory) {
      return new UriLoader<>(this);
    }

同时每个工厂类也都实现了LocalUriFetcherFactory的接口方法:DataFetcher build(Uri uri);

简单一想, 可能觉得LocalUriFetcherFactory没多大用啊, 就是中间又加了一层, 多了一次委托罢了.

实际并不然.

如果在每个ModelLoaderFactory工厂类的build方法中就直接把对应的DataFetcher实例传给UriLoader构造方法的话, 实际是做不到的.

看这里:

new StreamLocalUriFetcher(contentResolver, uri);

new FileDescriptorLocalUriFetcher(contentResolver, uri);

new AssetFileDescriptorLocalUriFetcher(contentResolver, uri);

都需要参数uri作为第二个参数. 但是在ModelLoaderFactory的build方法中无法提供这个参数值.

这里应该是为了绕开这个限制, 才有增加了一个本地的工厂接口LocalUriFetcherFactory, 相当于把DataFetcher的实例构建推后了一层, 一直到UriLoader的buildLoadData方法中.

image.png

继续看这个UrlUriLoader, 这个类的实现跟前面分析的StringLoader是一样的. 都是委托其他ModelLoader来做实际的操作.

我们知道StringLoader是把具体工作都委托给了UriLoader, 那这里是委托给谁了呢? 答案是ModelLoader urlLoader. 也就是说UrlUriLoader只负责把http, https开头的uri请求转换为GlideUrl类型. (这步转换很简单, 就一句: GlideUrl glideUrl = new GlideUrl(uri.toString());) 然后再把后续所有工作交给ModelLoader

多说一句: 那么这个委托对象ModelLoader是哪里来的呢?

---- 哪里来都可以, 这里更一般的是通过另一个工厂类来返回.


public static class StreamFactory implements ModelLoaderFactory {

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

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

所以外部更典型的用法应该是使用UrlUriLoader.StreamFactory这个工厂类, 通过它的build方法去创建UrlUriLoader, 而build方法调用的时候就需要创建一个GlideUrl模型对应的MultiModelLoaderFactory .

image.png

这个跟UrlUriLoader基本一样, 没什么可说的.

继续往下看

image.png

基本与上一个UrlUriLoader实现是一样的, 唯一的区别就是这里UrlLoader没有在类声明中使用泛型, 而是指定了转换的具体类型:

public class UrlLoader implements ModelLoader {

可能是要表达这样一个意思: URL只能转换为InputStream.

其他没什么可说的, 也是委托给ModelLoader glideUrlLoader去具体实现.

image.png

这个应该就是前面UrlUriLoader, UrlLoader要委托的具体对象了.

这个类没有什么花活, 很直接. 唯一多了一个GlideUrl缓存的概念;

ModelCache modelCache 这个的具体实现方式后续再研究分析. -- 这个应该合并到上一章的缓存分析里.

这里使用的DataFetcher类是: new HttpUrlFetcher(url, timeout) 可以看到带了一个2500毫秒的超时参数.

我们继续看XxxLoader类.

image.png

ResourceLoader的目标模型是android的资源ID, 所以这个目标模型类型是integer.

还是委托. ResourceLoader只做了一件integer资源id转换为uri的工作:

Uri uri = getResourceUri(model);

其他还是交给了ModelLoader uriLoader去做.

这里面的UriFactory实现很有意思. 咋看不理解, 从它的签名看是把Integer转换为uri,但是因为ResourceLoader已经通过Uri uri = getResourceUri(model);进行了转换, 所以它的ModelLoader uriLoader实际已经没什么可做的了. 所以就有了这么一个什么都不做的ModelLoader: UnitModelLoader. 这个模型转换器, 就是简单的一步交换, 什么实际工作都没做.

看这里这句:


public ModelLoader build(MultiModelLoaderFactory multiFactory) {
      return new ResourceLoader<>(resources, UnitModelLoader.getInstance());
    }

UnitModelLoader.getInstance()这种用法还是第一次见到, 又露怯了.!

image.png

逻辑也很直接, 没有什么花活. 把文件转换为ByteBuffer, 显而易见就是读取文件数据到ByteBuffer中

可以研究下里面的ByteBufferFetcher实现.

学习下人家是怎么读取文件的


@Override
    public void loadData(@NonNull Priority priority,
        @NonNull DataCallback callback) {
      ByteBuffer result;
      try {
        result = ByteBufferUtil.fromFile(file);
      } catch (IOException e) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "Failed to obtain ByteBuffer for file", e);
        }
        callback.onLoadFailed(e);
        return;
      }

      callback.onDataReady(result);
    }


@NonNull
  public static ByteBuffer fromFile(@NonNull File file) throws IOException {
    RandomAccessFile raf = null;
    FileChannel channel = null;
    try {
      long fileLength = file.length();
      // See #2240.
      if (fileLength > Integer.MAX_VALUE) {
        throw new IOException("File too large to map into memory");
      }
      // See b/67710449.
      if (fileLength == 0) {
        throw new IOException("File unsuitable for memory mapping");
      }

      raf = new RandomAccessFile(file, "r");
      channel = raf.getChannel();
      return channel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength).load();
    } finally {
      if (channel != null) {
        try {
          channel.close();
        } catch (IOException e) {
          // Ignored.
        }
      }
      if (raf != null) {
        try {
          raf.close();
        } catch (IOException e) {
          // Ignored.
        }
      }
    }
  }

就读个文件, 还得考虑文件是否太大, 用的是FileChannel , RandomAccessFile

学习了!

image.png

首先得搞清楚什么是DataUrl, http://www.ietf.org/rfc/rfc2397.txt

竟然还能这么做, 还能通过一串字符串就可以传递一个图片, 可能以后用到这种数据源模型的机会很少. 简单看看这个转换器吧.

但是也没什么太特别的. 基本上搞清楚了DataUrl这种模式的格式, 也就不会对这个模型转换器的实现由什么迷惑了.

看了上面这些模型转换器ModelLoader 再回答最开始的问题:

ModelLoader到底代表了什么?

---仍然是无法更好的回答, 在对整个glide的架构, 实现思想没有搞清楚之前, 整个问题只能先放着了.

但是把所有这些模型转换器都过一遍, 对我们后续分析Glide的初始化流程, 具体加载流程都有一些帮助, 至少看着这些泛型泛滥的类声明不会太眼晕了.

你可能感兴趣的:(Glide学习---模型转换器)