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