美文网首页
Glide学习---模型转换器

Glide学习---模型转换器

作者: vb12 | 来源:发表于2018-07-12 12:10 被阅读75次

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

    ModelLoader<Model, Data>定义了一个方法:

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

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

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

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

    先从简单的开始理解吧:

    FileLoader

    image.png

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

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

    @NonNull
      ModelLoader<T, Y> build(@NonNull MultiModelLoaderFactory multiFactory);
    

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

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

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

    @Override
      public LoadData<Data> 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<T> {
        ....
        void loadData(@NonNull Priority priority, @NonNull DataCallback<? super T> callback);
        ...
    

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

     @Override
        public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super Data> 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<Data> {
        public final Key sourceKey;
        public final List<Key> alternateKeys;
        public final DataFetcher<Data> 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<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);
      }
    
    

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

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

    有可以说实际上, StringLoader只做了一件事, 就是把string转换为uri, 然后具体的架子工作又委托给了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);
      }
    
    

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

    UriLoader

    image.png

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

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

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

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

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

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

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

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

    同时每个工厂类也都实现了LocalUriFetcherFactory的接口方法:DataFetcher<Data> 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<GlideUrl, Data> urlLoader. 也就是说UrlUriLoader只负责把http, https开头的uri请求转换为GlideUrl类型. (这步转换很简单, 就一句: GlideUrl glideUrl = new GlideUrl(uri.toString());) 然后再把后续所有工作交给ModelLoader<GlideUrl, Data>

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

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

    
    public static class StreamFactory implements ModelLoaderFactory<Uri, InputStream> {
    
        @NonNull
        @Override
        public ModelLoader<Uri, InputStream> 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> {

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

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

    image.png

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

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

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

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

    我们继续看XxxLoader类.

    image.png

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

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

    Uri uri = getResourceUri(model);

    其他还是交给了ModelLoader<Uri, Data> uriLoader去做.

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

    看这里这句:

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

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

    image.png

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

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

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

    
    @Override
        public void loadData(@NonNull Priority priority,
            @NonNull DataCallback<? super ByteBuffer> 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学习---模型转换器

          本文链接:https://www.haomeiwen.com/subject/wawlpftx.html