同样地,开始之前先思考1个问题:
- Glide是怎么实现那么多资源Model的加载的?比如可以从Url、Asset、FileDescriptor、Uri、File等来源加载数据
1.ModelLoader
Glide的所有数据加载都实现一个接口ModelLoader
,其中Model
类型就是来源的类型,Data
是加载得到的数据类型,看下接口的具体源码,包含一个内部类LoadData
,和两个接口方法,一个buildLoadData
方法用来构造返回一个LoadData
,另外一个方法handles
用来返回这个ModelLoader
能否处理这个Model
。LoadData
这个类里面有三个字段,一个sourceKey
用来表示这次下载,一个DataFetcher
用来获取不在缓存中的数据:
public interface ModelLoader {
/**
* @param The type of data that well be loaded.
*/
class LoadData {
public final Key sourceKey;
public final List alternateKeys;
public final DataFetcher fetcher;
public LoadData(@NonNull Key sourceKey, @NonNull DataFetcher fetcher) {
this(sourceKey, Collections.emptyList(), fetcher);
}
public LoadData(@NonNull Key sourceKey, @NonNull List alternateKeys,
@NonNull DataFetcher fetcher) {
this.sourceKey = Preconditions.checkNotNull(sourceKey);
this.alternateKeys = Preconditions.checkNotNull(alternateKeys);
this.fetcher = Preconditions.checkNotNull(fetcher);
}
}
@Nullable
LoadData buildLoadData(@NonNull Model model, int width, int height,
@NonNull Options options);
boolean handles(@NonNull Model model);
}
看下Glide里面所有的实现类,这些种类就代表了Glide支持从哪些数据类型下载数据:
比如我们看一个最常用的从http/https uris下载数据的UrlUriLoader
, 先看它的接口方法handles
,如果scheme类型是http/https类型的就返回true;另外一个接口方法buildLoadData
通过字段urlloader
返回一个LoadData,还有一个内部工厂类StramFactory
, UrlUriLoader
不建议外面通过构造函数实例化,而是通过这个工厂类的build
方法进行实例化。基本上ModelLoader
的实现类都是这种设计模式。
// UrlUriLoader.java
public class UrlUriLoader implements ModelLoader {
private static final Set SCHEMES = Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
"http",
"https"
)
)
);
private final ModelLoader urlLoader;
// Public API.
@SuppressWarnings("WeakerAccess")
public UrlUriLoader(ModelLoader urlLoader) {
this.urlLoader = urlLoader;
}
@Override
public LoadData buildLoadData(@NonNull Uri uri, int width, int height,
@NonNull Options options) {
GlideUrl glideUrl = new GlideUrl(uri.toString());
return urlLoader.buildLoadData(glideUrl, width, height, options);
}
@Override
public boolean handles(@NonNull Uri uri) {
return SCHEMES.contains(uri.getScheme());
}
/**
* Loads {@link java.io.InputStream InputStreams} from {@link android.net.Uri Uris} with http
* or https schemes.
*/
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.
}
}
}
StreamFactory
也是实现一个工厂接口ModelLoaderFactory
,具体看下这个接口代码:
// ModelLoaderFactory.java
/**
* An interface for creating a {@link ModelLoader} for a given model type.
*
* @param The type of the model the {@link com.bumptech.glide.load.model.ModelLoader}s built by
* this factory can handle
* @param The type of data the {@link com.bumptech.glide.load.model.ModelLoader}s built by this
* factory can load.
*/
public interface ModelLoaderFactory {
/**
* Build a concrete ModelLoader for this model type.
*
* @param multiFactory A map of classes to factories that can be used to construct additional
* {@link ModelLoader}s that this factory's {@link ModelLoader} may depend on
* @return A new {@link ModelLoader}
*/
@NonNull
ModelLoader build(@NonNull MultiModelLoaderFactory multiFactory);
/**
* A lifecycle method that will be called when this factory is about to replaced.
*/
void teardown();
}
接口比较简单,比如特殊的是还有一个工厂MultiModelLoaderFactory
,其实也可以看成一种代理模式,真正去buildLoadData
的是通过工厂类StreamFactory
构造返回的urlLoader
,它能处理的Model
类型是GlideUrl
,返回的类型是InputStream
,而urlloader
本身是通过MultiModelLoaderFactory
构造:
public ModelLoader build(MultiModelLoaderFactory multiFactory) {
return new UrlUriLoader<>(multiFactory.build(GlideUrl.class, InputStream.class));
}
所以我们总结下下ModelLoader
,UrlUriLoader
,StreamFactory
,ModelLoaderFactory
,MultiModelLoaderFactory
的关系, 具体的ModelLoader里面有一个代理的ModelLoader,这个代理的ModelLoder由MultiModelLoaderFactory
实例化,不管是外层的ModelLoader
或者是代理ModelLoader
都是由Factory
这种方式生成,外层的ModelLoader
的Factory
在自己的类名下面,而代理ModelLoader
统一都由MultiModelLoaderFactory
这个工厂生成,类似门面模式。
StreamFactory
是在Glide初始化的时候注册到ModelLoaderRegistry
中:
//Glide.java
registry.append(URL.class, InputStream.class, new UrlLoader.StreamFactory())
而ModelLoaderRegistry
其实只是MultiModelLoaderFactory
的外壳,它会把Glide初始化注册的所有ModelFactory
保存到MultiModelLoaderFactory
:
// ModelLoaderRegistry.java
private final MultiModelLoaderFactory multiModelLoaderFactory;
private final ModelLoaderCache cache = new ModelLoaderCache();
public synchronized void append(
@NonNull Class modelClass,
@NonNull Class dataClass,
@NonNull ModelLoaderFactory extends Model, ? extends Data> factory) {
multiModelLoaderFactory.append(modelClass, dataClass, factory);
cache.clear();
}
按照上面的逻辑,再看一个具体的ModelLoader
实现类FileLoader implements ModelLoader
, 实现从File加载Data
,这里Data
有两个类型java.io.InputStream
和java.io.FileDescriptor
,看下具体实现。实际工作会通过内部实例变量FileOpener
完成,在FileFetcher loadData
中通过FileOpener
打开文件,返回需要的数据类型,所以关键就在这个FileOpener
,在这里是对文件打开关闭的一个封装接口,根据具体的返回类型构造不同的FileOpener
.
// FileLoader.java
public class FileLoader implements ModelLoader {
private static final String TAG = "FileLoader";
private final FileOpener fileOpener;
// Public API.
@SuppressWarnings("WeakerAccess")
public FileLoader(FileOpener fileOpener) {
this.fileOpener = fileOpener;
}
@Override
public LoadData buildLoadData(@NonNull File model, int width, int height,
@NonNull Options options) {
return new LoadData<>(new ObjectKey(model), new FileFetcher<>(model, fileOpener));
}
@Override
public boolean handles(@NonNull File model) {
return true;
}
/**
* Allows opening a specific type of data from a {@link java.io.File}.
* @param The type of data that can be opened.
*/
public interface FileOpener {
Data open(File file) throws FileNotFoundException;
void close(Data data) throws IOException;
Class getDataClass();
}
private static final class FileFetcher implements DataFetcher {
private final File file;
private final FileOpener opener;
private Data data;
FileFetcher(File file, FileOpener opener) {
this.file = file;
this.opener = opener;
}
@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);
}
@Override
public void cleanup() {
if (data != null) {
try {
opener.close(data);
} catch (IOException e) {
// Ignored.
}
}
}
@Override
public void cancel() {
// Do nothing.
}
@NonNull
@Override
public Class getDataClass() {
return opener.getDataClass();
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.LOCAL;
}
}
/**
* Base factory for loading data from {@link java.io.File files}.
* @param The type of data that will be loaded for a given {@link java.io.File}.
*/
public static class Factory implements ModelLoaderFactory {
private final FileOpener opener;
public Factory(FileOpener opener) {
this.opener = opener;
}
@NonNull
@Override
public final ModelLoader build(@NonNull MultiModelLoaderFactory multiFactory) {
return new FileLoader<>(opener);
}
@Override
public final void teardown() {
// Do nothing.
}
}
在FileLoader
内部有两个静态内部类StreamFactory
和FileDescriptorFactory
分别返回两个数据类型java.io.InputStream
和java.io.FileDescriptor
。分别构造两种类型的FileOpener
,一个返回InputStream
类型,另外一个返回ParcelFileDescriptor
.
/**
* Factory for loading {@link InputStream}s from {@link File}s.
*/
public static class StreamFactory extends Factory {
public StreamFactory() {
super(new FileOpener() {
@Override
public InputStream open(File file) throws FileNotFoundException {
return new FileInputStream(file);
}
@Override
public void close(InputStream inputStream) throws IOException {
inputStream.close();
}
@Override
public Class getDataClass() {
return InputStream.class;
}
});
}
}
/**
* Factory for loading {@link ParcelFileDescriptor}s from {@link File}s.
*/
public static class FileDescriptorFactory extends Factory {
public FileDescriptorFactory() {
super(new FileOpener() {
@Override
public ParcelFileDescriptor open(File file) throws FileNotFoundException {
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
@Override
public void close(ParcelFileDescriptor parcelFileDescriptor) throws IOException {
parcelFileDescriptor.close();
}
@Override
public Class getDataClass() {
return ParcelFileDescriptor.class;
}
});
}
}
上面两个Factory
也是Glide初始化的时候注册到ModelLoaderRegistry
中:
.append(File.class, InputStream.class, new FileLoader.StreamFactory())
.append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory())
2. DataFetcher
从前面ModelLoader
中有提到过DataFetcher
这个类,这是真正做数据拉取的功能接口,基本上对应不同的ModelLoader
会有对应的DataFetcher
去实现拉取数据的工作,看下DataFetcher
这个类的接口定义。里面有个内部接口DataCallback
,在数据拉取成功后会通过回调接口返回。loadData
就是真正发起数据请求的地方,getDataSource
返回数据来源,这是个枚举。
// DataSource.java
public interface DataFetcher {
/**
* Callback that must be called when data has been loaded and is available, or when the load
* fails.
*
* @param The type of data that will be loaded.
*/
interface DataCallback {
/**
* Called with the loaded data if the load succeeded, or with {@code null} if the load failed.
*/
void onDataReady(@Nullable T data);
/**
* Called when the load fails.
*
* @param e a non-null {@link Exception} indicating why the load failed.
*/
void onLoadFailed(@NonNull Exception e);
}
void loadData(@NonNull Priority priority, @NonNull DataCallback super T> callback);
void cleanup();
void cancel();
/**
* Returns the class of the data this fetcher will attempt to obtain.
*/
@NonNull
Class getDataClass();
/**
* Returns the {@link com.bumptech.glide.load.DataSource} this fetcher will return data from.
*/
@NonNull
DataSource getDataSource();
}
看下在Glide中DataFetcher
的继承结构,实现类还是很多,可以从AssetPath/Url/File/Uri
等地方拉取数据。
看下比较常用的HttpUrlFetcher
,实现从Url加载返回一个InputStream
:
/**
* A DataFetcher that retrieves an {@link java.io.InputStream} for a Url.
*/
public class HttpUrlFetcher implements DataFetcher
在loadData
方法中,通过loadDataWithRedirects
加载url返回result,如果成功就通过onDataReady
返回数据:
// HttpUrlFetcher.java
InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
callback.onDataReady(result);
在loadDataWithRedirects
中通过HttpURLConnection
发起网络连接:
// HttpUrlFetcher.java
urlConnection = connectionFactory.build(url);
for (Map.Entry headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(),headerEntry.getValue());
}
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
urlConnection.setInstanceFollowRedirects(false);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
// Set the stream so that it's closed in cleanup to avoid resource leaks. See #2352.
stream = urlConnection.getInputStream();
看下另外几个接口方法的实现,这里DataSource
明显就是REMOTE
,返回的数据类型是InputStream.class
,在cleanup
中就是把sream和connection
关闭:
@Override
public void cleanup() {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignore
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
urlConnection = null;
}
@Override
public void cancel() {
isCancelled = true;
}
@NonNull
@Override
public Class getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
3.总结
再回头看前面提的问题,Glide提供很多ModelLoader
接口的实现类来实现不同资源的数据加载,每个ModelLoader
不是直接通过构造函数实例化,而是每个Loader内部提供的工厂类进行实例化,在Glide初始化的时候,会把这些工厂类都注册到ModelLoaderRegistry
中,构造的时候从里面取出对应的工厂实例化ModelLoader
。ModelLoader
下载数据会通过DataFetcher
,DataFetcher
的实现类基本和ModelLoader
对应。