上一篇介绍为了Glide 当中的缓存机制,接下来就要介绍Glide当中对整个图片的加载过程了
没错,ModelLoader就是对图片加载过程的封装;
接下来提出两个问题,我们带着这两个问题继续后面的讲解;
//sd path
Glide.width(this).load("file://"+Environment.getExternalStorageDirectory().getPath()+"/123.png");
//assets 下
Glide.width(this).load("file://android_asset/123.png");
// 网络图片 scheme 为http
Glide.width(this).load("http://img.my.csdn.NET/123.png");
// 网络图片 scheme 为https
Glide.with(this).load("https://img.my.csdn.NET/123.png");
上面这几种load方式,最终Glide都能成功的将图片加载出来并且正常的展示,那么问题来了,都是为字符串,Glide是怎么知道当前要从assets下读取,网络上读取,还是从sd卡中读取呢?
带着这个问题,我们开始对Glide进行拆解,了解图片加载过程的内部工作机制。
ModelLoader
/**
* Model和Data为定义的两个泛型
* Model代表了加载来源模型:Uri、File等
* Data则代表加载模型后的数据:InputSream、byte[]等
*/
public interface ModelLoader {
//ModelLoader工厂
interface ModelLoaderFactory {
//参数modelLoaderRegistry为注册机,具体左后后面会讲到
ModelLoader build(ModelLoaderRegistry modelLoaderRegistry);
}
//我们最终由Model经过转换之后,最终输出数据类型即为LoadData,
//LoadData内的fetcher封装了对数据的操作
class LoadData {
//key Glide缓存会用到Key
public final Key sourceKey;
//数据加载器
public final DataFetcher fetcher;
public LoadData(Key sourceKey, DataFetcher fetcher) {
this.sourceKey = sourceKey;
this.fetcher = fetcher;
}
}
/**
* 加载结构,将输入的model转换为目标数据类型,即Data
* @param model
* @return
*/
LoadData buildLoadData(Model model);
/**
* 此Loader是否可以处理对应Model,
* 即该Model类型是否符合我们当前数据类型
* @param model
* @return
*/
boolean handles(Model model);
}
使用Glide,图片可能存在于文件、网络等地方。其中Model则代表了加载来源模型:Uri、File等;Data则代表加载模型后的数据:InputSream、byte[]等。
通过buildLoadData函数创建LoadData。LoadData中的DataFetcher如下:
public interface DataFetcher {
interface DataFetcherCallback {
/**
* 数据加载完成
* @param data
*/
void onFetcherReady(T data);
/**
* 数据加载失败
* @param e
*/
void onLoadFailed(Exception e);
}
/**
* 加载数据
* @param callback
*/
void loadData(DataFetcherCallback super T> callback);
/**
* 取消
*/
void cancel();
/**
* 数据类型
* @return
*/
Class getDataClass();
}
HttpUriLoader实现类,已网络图片加载为例
public class HttpUriLoader implements ModelLoader {
/**
* http类型的uri此loader才支持
*
* @param uri
* @return
*/
@Override
public boolean handles(Uri uri) {
String scheme = uri.getScheme();
//因为来源于文件也可以转换为Uri,所以这个时候通过scheme来判断当前图片是否来源于网络
return scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https");
}
/**
* 将Uri转化为我们要的目标数据类型,并通过HttpUriFetcher对数据进行处理
*/
@Override
public LoadData buildData(Uri uri) {
return new LoadData(new ObjectKey(uri), new HttpUriFetcher(uri));
}
public static class Factory implements ModelLoaderFactory {
@Override
public ModelLoader build(ModelLoaderRegistry registry) {
return new HttpUriLoader();
}
}
}
HttpUriFetcher实现类,已网络图片加载为例
public class HttpUriFetcher implements DataFetcher {
private final Uri uri;
private boolean isCanceled;
public HttpUriFetcher(Uri uri) {
this.uri = uri;
}
@Override
public void loadData(DataFetcherCallback callback) {
HttpURLConnection conn = null;
InputStream is = null;
try {
URL url = new URL(uri.toString());
conn = (HttpURLConnection) url.openConnection();
conn.connect();
is = conn.getInputStream();
int responseCode = conn.getResponseCode();
if (isCanceled) {
return;
}
if (responseCode == HttpURLConnection.HTTP_OK) {
callback.onFetcherReady(is);
} else {
callback.onLoadFaled(new RuntimeException(conn.getResponseMessage()));
}
} catch (Exception e) {
callback.onLoadFaled(e);
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != conn) {
conn.disconnect();
}
}
}
@Override
public void cancel() {
isCanceled = true;
}
}
FileUriLoader实现类,已文件类型为例
public class FileUriLoader implements ModelLoader {
//由于该Uri来源于文件,所以需要通过ContentResolver进行加载
private final ContentResolver contentResolver;
public FileUriLoader(ContentResolver contentResolver) {
this.contentResolver = contentResolver;
}
//判断当前数据是否符合FileUriLoader的条件
@Override
public boolean handles(Uri uri) {
// ContentResolver.SCHEME_FILE
return ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme());
}
@Override
public LoadData buildData(Uri uri) {
//生成LoadData的时候需要传入对应的Key,缓存的时候需要用到
return new LoadData<>(new ObjectKey(uri), new FileUriFetcher(uri, contentResolver));
}
public static class Factory implements ModelLoaderFactory {
private final ContentResolver contentResolver;
public Factory(ContentResolver contentResolver) {
this.contentResolver = contentResolver;
}
@Override
public ModelLoader build(ModelLoaderRegistry registry) {
return new FileUriLoader(contentResolver);
}
}
}
FileUriFetcher实现类,以文件类型为例
public class FileUriFetcher implements DataFetcher {
private final Uri uri;
private final ContentResolver cr;
public FileUriFetcher(Uri uri, ContentResolver cr) {
this.uri = uri;
this.cr = cr;
}
@Override
public void loadData(DataFetcherCallback callback) {
InputStream is = null;
try {
is = cr.openInputStream(uri);
callback.onFetcherReady(is);
} catch (FileNotFoundException e) {
callback.onLoadFaled(e);
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public void cancel() {
}
}
如果Mode和Data能同时对应多个modelLoader的时候我们该怎么办呢,这个时候我们引入了MultiModelLoader,它是包含多个modelLoader的装饰类
public class MultiModelLoader implements ModelLoader {
//FileUriModelLoader HttpUriModelLoader
private final List> modelLoaders;
public MultiModelLoader(List> modelLoaders) {
this.modelLoaders = modelLoaders;
}
@Override
public boolean handles(Model model) {
for (ModelLoader modelLoader : modelLoaders) {
if (modelLoader.handles(model)) {
return true;
}
}
return false;
}
@Override
public LoadData buildData(Model model) {
for (int i = 0; i < modelLoaders.size(); i++) {
ModelLoader modelLoader = modelLoaders.get(i);
// Model=>Uri:http
if (modelLoader.handles(model)){
LoadData loadData = modelLoader.buildData(model);
return loadData;
}
}
return null;
}
}
在Glide的使用过程中,如果针对文件或者网络图片全都转成Uri的方式未免会有些比较麻烦,我们用的最多的还是直接给个资源路径,也就是Model为String类型,这样相对于Uri来说要方便很多,在这里,我们要介绍另一个常用的ModelLoader,也就是StringModelLoader
public class StringModelLoader implements ModelLoader {
private final ModelLoader loader;
public StringModelLoader(ModelLoader loader) {
this.loader = loader;
}
@Override
public boolean handles(String s) {
return true;
}
/**
* 判断当前model是否为文件路径,并且将其转换为对应的Uri,然后再将其设置给相应的ModelLoader进行处理
* 有可能你会有疑问,StringModelLoader为什么自己不进行处理还有交给其他的loader进行处理呢,
* 在这里我们是为了实现更好的插拔,不然的话就得写一堆的逻辑判断语句了
*/
@Override
public LoadData buildData(String model) {
Uri uri;
if (model.startsWith("/")) {
uri = Uri.fromFile(new File(model));
} else {
uri = Uri.parse(model);
}
return loader.buildData(uri);
}
/**
* 工厂类,构建ModelLoader实例,StringModelLoader的构建方式和其他的构建方式有点不同,在这里我们是通过注册机进行注册的,因为针对
* 我们可以有多种modelLoader与其对应,所以我们这边是通过registry.build()获取到这个modelLoader
*/
public static class StreamFactory implements ModelLoaderFactory {
@Override
public ModelLoader build(ModelLoaderRegistry registry) {
return new StringModelLoader(registry.build(Uri.class, InputStream.class));
}
}
}
注册机--ModelLoaderRegistry
ModelLoaderRegister负责注册所有的ModelLoader
public class ModelLoaderRegistry {
private List> entries = new ArrayList<>();
/**
* 注册 Loader
*
* @param modelClass 数据来源类型 String File
* @param dataClass 数据转换后类型 加载后类型 String/File->InputStream
* @param factory 创建ModelLoader的工厂
* @param
* @param
*/
public synchronized void add(Class modelClass, Class dataClass,
ModelLoader.ModelLoaderFactory factory) {
entries.add(new Entry<>(modelClass, dataClass, factory));
}
/**
* 获得 对应 model与data类型的 modelloader
*
* @param modelClass
* @param dataClass
* @param
* @param
* @return
*/
public ModelLoader build(Class modelClass, Class dataClass) {
List> loaders = new ArrayList<>();
for (Entry, ?> entry : entries) {
//找到我们需要的Model与Data类型的Loader
if (entry.handles(modelClass, dataClass)) {
loaders.add((ModelLoader) entry.factory.build(this));
}
}
//找到多个匹配的loader
if (loaders.size() > 1) {
return new MultiModelLoader<>(loaders);
} else if (loaders.size() == 1) {
return loaders.get(0);
}
throw new RuntimeException("No Match:" + modelClass.getName() + " Data:" + dataClass.getName());
}
/**
* 查找匹配的 Model类型的ModelLoader
* @param modelClass
* @param
* @return
*/
public List> getModelLoaders(Class modelClass) {
List> loaders = new ArrayList<>();
for (Entry, ?> entry : entries) {
if (entry.handles(modelClass)) {
loaders.add((ModelLoader) entry.factory.build(this));
}
}
return loaders;
}
private static class Entry {
Class modelClass;
Class dataClass;
ModelLoader.ModelLoaderFactory factory;
public Entry(Class modelClass, Class dataClass, ModelLoader.ModelLoaderFactory factory) {
this.modelClass = modelClass;
this.dataClass = dataClass;
this.factory = factory;
}
boolean handles(Class> modelClass, Class> dataClass) {
// A.isAssignableFrom(B) B和A是同一个类型 或者 B是A的子类
return this.modelClass.isAssignableFrom(modelClass) && this.dataClass.isAssignableFrom(dataClass);
}
boolean handles(Class> modelClass) {
// A.isAssignableFrom(B) B和A是同一个类型 或者 B是A的子类
return this.modelClass.isAssignableFrom(modelClass);
}
}
}
使用之前调用add函数对需要组装的Loader进行注册
.add(String.class, InputStream.class, new StringLoader.StreamFactory())
.add(Uri.class, InputStream.class, new HttpUriLoader.Factory())
.add(Uri.class, InputStream.class, new UriFileLoader.Factory(contentResolver))
.add(File.class, InputStream.class, new FileLoader.Factory())
当需要加载一个String类型的来源则会查找到StringLoader。但是一个String它可能属于文件地址也可能属于一个网络地址,所以StringLoader.StreamFactory在创建StringLoader的时候,它会根据Uri与InputStream类型创建一个MultiModelLoader对象,这个MultiModelLoader中存在一个集合,只要集合中存在一个Loader能够处理对应的Model,那么这个MultiModelLoader就可以处理对应的Model。
所以当需要处理String类型的来源的时候,会创建一个MultiModelLoader,这个MultiModelLoader中包含了一个HttpUriLoader与一个UriFileLoader。当字符串是以http或者https开头则能由HttpUriLoader处理,否则交给UriFileLoader来加载。
定位到Loader,通过buildData获得一个LoadData,使用其中的Fetcher就可以加载到一个泛型Data类型的数据,比如InputStream。然后通过注册的解码器解码InputStream获得Bitmap(解码器的注册相对于Loader更简单)。
看到这里,modelLoader的工作机制已经讲完了
Demo
public class LoaderTest {
private static final String TAG = "LoaderTest";
public static void testFindLoader(Context context) {
ModelLoaderRegistry loaderRegistry = new ModelLoaderRegistry();
//注册各种ModelLoader
loaderRegistry.add(String.class, InputStream.class, new StringModelLoader.StreamFactory());
loaderRegistry.add(Uri.class, InputStream.class, new FileUriLoader.Factory(context.getContentResolver()));
loaderRegistry.add(Uri.class, InputStream.class, new HttpUriLoader.Factory());
loaderRegistry.add(File.class, InputStream.class, new FileLoader.Factory());
// context.getAssets().open("/a/b.png");
List> modelLoaders = loaderRegistry.getModelLoaders(String.class);
ModelLoader modelLoader = modelLoaders.get(0);
//HttpUriFetcher
final ModelLoader.LoadData loadData = (ModelLoader.LoadData) modelLoader.buildData("https://ss1.bdstatic" +
".com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2669567003," +
"3609261574&fm=27&gp=0.jpg22222222asads");
new Thread() {
@Override
public void run() {
loadData.fetcher.loadData(new DataFetcher.DataFetcherCallback() {
@Override
public void onFetcherReady(InputStream o) {
try {
Log.e(TAG, "ready:" + o.available());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onLoadFaled(Exception e) {
e.printStackTrace();
}
});
}
}.start();
// modelLoader.buildData("/a/b.png");
}
public void test(Context context) {
Uri uri = Uri.parse("http://www.xxx.xxx");
HttpUriLoader httpUriLoader = new HttpUriLoader();
ModelLoader.LoadData loadData = httpUriLoader.buildData(uri);
//
loadData.fetcher.loadData(new DataFetcher.DataFetcherCallback() {
@Override
public void onFetcherReady(InputStream is) {
//
BitmapFactory.decodeStream(is);
}
@Override
public void onLoadFaled(Exception e) {
e.printStackTrace();
}
});
// FileUriLoader loader = new FileUriLoader(context.getContentResolver());
// ModelLoader.LoadData loadData1 = loader.buildData(uri);
// loadData1.fetcher.loadDta();
}
}