Glide学习---模型转换器

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的初始化流程, 具体加载流程都有一些帮助, 至少看着这些泛型泛滥的类声明不会太眼晕了.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容