Glide源码很多,直接的钻进去看的话会感到头大,我们现在已用过一些加载框架,也可能大概看过一些源码,在这个基础上,假如自己要构建一个图片加载框架的话,我们需要怎么去设计。
我们可能需要考虑到:
1、从哪里加载,根据图片资源的来源,可以大概分为网络上的图片和本地的图片
2、既然从不同地方加载,那么就需要不同的方法去加载,比如网络上的我们需要用的http请求获取,本地的图需要用的流去获取。
3、我们第二步获取到的是图片的二进制流,那么就需要转换成我们需要的类型,比如bitmap、Drawable、File
3、我们同时可能加载多张图片,那么就需要用到多线程加载,比如线程池
4、我们可能多次加载同一张图,那么我们不应该重复去加载,所以需要用到缓存,缓存的话又涉及到内存缓存与磁盘缓存,缓存如何高效的去管理
5、图片占用的内存很大,如何避免内存溢出
6、如何去管理加载的请求
7、还有一些我们考虑不到的东西,等等。。。。。
Glide的设计者肯定也考虑到了这些,只多不少,通过阅读源码,确实是这样子的,但是并不像我们上面说的那么简单,每一点都够我们看个半天,其中的设计思想也值得我们学习。
资源加载
来源
Glide有一个构造方法,它里面有很多东西,我们现在只看其中一部分
register(File.class, ParcelFileDescriptor.class, new FileDescriptorFileLoader.Factory());
register(File.class, InputStream.class, new StreamFileLoader.Factory());
register(int.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
register(int.class, InputStream.class, new StreamResourceLoader.Factory());
register(Integer.class, ParcelFileDescriptor.class, new FileDescriptorResourceLoader.Factory());
register(Integer.class, InputStream.class, new StreamResourceLoader.Factory());
register(String.class, ParcelFileDescriptor.class, new FileDescriptorStringLoader.Factory());
register(String.class, InputStream.class, new StreamStringLoader.Factory());
register(Uri.class, ParcelFileDescriptor.class, new FileDescriptorUriLoader.Factory());
register(Uri.class, InputStream.class, new StreamUriLoader.Factory());
register(URL.class, InputStream.class, new StreamUrlLoader.Factory());
//注意一下这个,后面我们会用到
register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
register(byte[].class, InputStream.class, new StreamByteArrayLoader.Factory());
---------------------
public <T, Y> void register(Class<T> modelClass, Class<Y> resourceClass, ModelLoaderFactory<T, Y> factory) {
ModelLoaderFactory<T, Y> removed = loaderFactory.register(modelClass, resourceClass, factory);
if (removed != null) {
removed.teardown();
}
}
modelClass,有File、resourceId、文件路径、网络链接、Uri、GlideUrl、二进制数组这集中类型
加载后的目标资源类型
resourceClass,有ParcelFileDescriptor、InputStream这两类
资源加载工厂
ModelLoaderFactory,资源加载工厂,通过它来确定如何加载,它会根据modelClass和resouceClass构建一个ModelLoader,这个ModelLoader接口有一个getResourceFetcher方法,它返回一个DataFetcher,就是通过这个DataFetcher去实际加载了资源
Glide会通过register(Class<T> modelClass, Class<Y> resourceClass, ModelLoaderFactory<T, Y> factory)方法,将modelClass、resourceClass、ModelLoaderFactory的保存在GenericLoaderFactory中,当我们去加载具体的modelClass类型的时候,就去这个GenericLoaderFactory当中查找对应的ModelLoaderFactory。
public class GenericLoaderFactory {
//以modelClass为key,resourceClass和ModelLoaderFactory组成的map为value保存
private final Map<Class/*T*/, Map<Class/*Y*/, ModelLoaderFactory/*T, Y*/>> modelClassToResourceFactories =
new HashMap<Class, Map<Class, ModelLoaderFactory>>();
......
}
DataFetcher
DataFetcher有以下实现类,我们从名字可以看出来它们具体用来加载哪种类型的modelClass
DataFetcher.png我们最常用的就是HttpUrlFetcher了,我们看一下是怎么通过图片链接(modelClass)找到DataFetcher,也就是HttpUrlFetcher的。
modelClass是String,我们去GenericLoaderFactory找,找到FileDescriptorStringLoader.Factory和StreamStringLoader.Factory,然后分别得到FileDescriptorStringLoader和StreamStringLoader,它们都继承了StringLoader
public class StringLoader<T> implements ModelLoader<String, T> {
private final ModelLoader<Uri, T> uriLoader;
//子类传入
public StringLoader(ModelLoader<Uri, T> uriLoader) {
this.uriLoader = uriLoader;
}
}
public static class Factory implements ModelLoaderFactory<String, ParcelFileDescriptor> {
@Override
public ModelLoader<String, ParcelFileDescriptor> build(Context context, GenericLoaderFactory factories) {
return new FileDescriptorStringLoader(factories.buildModelLoader(Uri.class, ParcelFileDescriptor.class));
}
}
public static class Factory implements ModelLoaderFactory<String, InputStream> {
@Override
public ModelLoader<String, InputStream> build(Context context, GenericLoaderFactory factories) {
return new StreamStringLoader(factories.buildModelLoader(Uri.class, InputStream.class));
}
}
发现他们的modelLoader分别通过factories.buildModelLoader(Uri.class, ParcelFileDescriptor.class)和factories.buildModelLoader(Uri.class, InputStream.class)从注册的工厂类中查找,得到FileDescriptorUriLoader和StreamUriLoader
public static class Factory implements ModelLoaderFactory<Uri, ParcelFileDescriptor> {
@Override
public ModelLoader<Uri, ParcelFileDescriptor> build(Context context, GenericLoaderFactory factories) {
return new FileDescriptorUriLoader(context, factories.buildModelLoader(GlideUrl.class,
ParcelFileDescriptor.class));
}
}
public static class Factory implements ModelLoaderFactory<Uri, InputStream> {
@Override
public ModelLoader<Uri, InputStream> build(Context context, GenericLoaderFactory factories) {
return new StreamUriLoader(context, factories.buildModelLoader(GlideUrl.class, InputStream.class));
}
}
然后发现我们还没有得到我们想要的ModelLoader,还得继续去找,通过factories.buildModelLoader(GlideUrl.class,ParcelFileDescriptor.class)和factories.buildModelLoader(GlideUrl.class, InputStream.class),我们之前在Glide的构造方法中注册了这个,我们要找的其实就是他了,我们想要的ModelLoader就是HttpUrlGlideUrlLoader,它返回的DataFetcher就是我们需要的HttpUrlFetcher了
register(GlideUrl.class, InputStream.class, new HttpUrlGlideUrlLoader.Factory());
public class HttpUrlGlideUrlLoader implements StreamModelLoader<GlideUrl> {
@Override
public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
// GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time spent parsing urls.
GlideUrl url = model;
if (modelCache != null) {
url = modelCache.get(model, 0, 0);
if (url == null) {
modelCache.put(model, 0, 0, model);
url = model;
}
}
return new HttpUrlFetcher(url);
}
}
看看HttpUrlFetcher
public class HttpUrlFetcher implements DataFetcher<InputStream> {
private static final String TAG = "HttpUrlFetcher";
private static final int MAXIMUM_REDIRECTS = 5;
private static final HttpUrlConnectionFactory DEFAULT_CONNECTION_FACTORY = new DefaultHttpUrlConnectionFactory();
private final GlideUrl glideUrl;
private final HttpUrlConnectionFactory connectionFactory;
private HttpURLConnection urlConnection;
private InputStream stream;
private volatile boolean isCancelled;
public HttpUrlFetcher(GlideUrl glideUrl) {
this(glideUrl, DEFAULT_CONNECTION_FACTORY);
}
// Visible for testing.
HttpUrlFetcher(GlideUrl glideUrl, HttpUrlConnectionFactory connectionFactory) {
this.glideUrl = glideUrl;
this.connectionFactory = connectionFactory;
}
@Override
public InputStream loadData(Priority priority) throws Exception {
return loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/, glideUrl.getHeaders());
}
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl, Map<String, String> headers)
throws IOException {
//重定向超过5,就直接报异常了,这个问题开发当中还真遇到过,特别是服务器重启的时候
if (redirects >= MAXIMUM_REDIRECTS) {
throw new IOException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new IOException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
//终于看到实际的请求网络了
urlConnection = connectionFactory.build(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
urlConnection.setConnectTimeout(2500);
urlConnection.setReadTimeout(2500);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
//请求成功
if (statusCode / 100 == 2) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (statusCode / 100 == 3) {
//重定向
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new IOException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
//获取到重定向的url,继续请求
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else {
if (statusCode == -1) {
throw new IOException("Unable to retrieve response code from HttpUrlConnection.");
}
throw new IOException("Request failed " + statusCode + ": " + urlConnection.getResponseMessage());
}
}
private InputStream getStreamForSuccessfulRequest(HttpURLConnection urlConnection)
throws IOException {
if (TextUtils.isEmpty(urlConnection.getContentEncoding())) {
int contentLength = urlConnection.getContentLength();
stream = ContentLengthInputStream.obtain(urlConnection.getInputStream(), contentLength);
} else {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Got non empty content encoding: " + urlConnection.getContentEncoding());
}
stream = urlConnection.getInputStream();
}
return stream;
}
@Override
public void cleanup() {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignore
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
@Override
public String getId() {
return glideUrl.getCacheKey();
}
@Override
public void cancel() {
// TODO: we should consider disconnecting the url connection here, but we can't do so directly because cancel is
// often called on the main thread.
isCancelled = true;
}
interface HttpUrlConnectionFactory {
HttpURLConnection build(URL url) throws IOException;
}
private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {
@Override
public HttpURLConnection build(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
}
}
资源的处理
DataLoadProvider
HttpUrlFetcher加载返回的是一个流,我们展示图片的时候传递给ImageView的应该是一个Bitmap或者Drawable,前面说到在Glide的构造函数里注册了很多Glide支持的资源来源类型modelClass以及加载后的资源类型resourceClass,resourceClass是如何转换成我们需要的Bitmap或者Drawable的呢,就是通过DataLoadProvider,DataLoadProvider在Glide的构造函数中注册,从名字我们可以明白它们的功能,比如StreamBitmapDataLoadProvider,就是将Stream转换成Bitmap
dataLoadProviderRegistry = new DataLoadProviderRegistry();
//,
StreamBitmapDataLoadProvider streamBitmapLoadProvider =
new StreamBitmapDataLoadProvider(bitmapPool, decodeFormat);
dataLoadProviderRegistry.register(InputStream.class, Bitmap.class, streamBitmapLoadProvider);
FileDescriptorBitmapDataLoadProvider fileDescriptorLoadProvider =
new FileDescriptorBitmapDataLoadProvider(bitmapPool, decodeFormat);
dataLoadProviderRegistry.register(ParcelFileDescriptor.class, Bitmap.class, fileDescriptorLoadProvider);
ImageVideoDataLoadProvider imageVideoDataLoadProvider =
new ImageVideoDataLoadProvider(streamBitmapLoadProvider, fileDescriptorLoadProvider);
dataLoadProviderRegistry.register(ImageVideoWrapper.class, Bitmap.class, imageVideoDataLoadProvider);
GifDrawableLoadProvider gifDrawableLoadProvider =
new GifDrawableLoadProvider(context, bitmapPool);
dataLoadProviderRegistry.register(InputStream.class, GifDrawable.class, gifDrawableLoadProvider);
dataLoadProviderRegistry.register(ImageVideoWrapper.class, GifBitmapWrapper.class,
new ImageVideoGifDrawableLoadProvider(imageVideoDataLoadProvider, gifDrawableLoadProvider, bitmapPool));
dataLoadProviderRegistry.register(InputStream.class, File.class, new StreamFileDataLoadProvider());
我们只看StreamBitmapDataLoadProvider,其他的Provider虽然转换的源类型与目标类型不一致,其实懂了一个,其他的也就懂了,StreamBitmapDataLoadProvider实现了DataLoadProvider<T, Z>接口,
public class StreamBitmapDataLoadProvider implements DataLoadProvider<InputStream, Bitmap> {
//加载原始数据InputStream为Bitmap
private final StreamBitmapDecoder decoder;
//将加载以及转换后的Bitmap资源写入磁盘缓存
private final BitmapEncoder encoder;
//将原始数据写入硬盘缓存
private final StreamEncoder sourceEncoder;
//从硬盘读取
private final FileToStreamDecoder<Bitmap> cacheDecoder;
public StreamBitmapDataLoadProvider(BitmapPool bitmapPool, DecodeFormat decodeFormat) {
sourceEncoder = new StreamEncoder();
decoder = new StreamBitmapDecoder(bitmapPool, decodeFormat);
encoder = new BitmapEncoder();
cacheDecoder = new FileToStreamDecoder<Bitmap>(decoder);
}
@Override
public ResourceDecoder<File, Bitmap> getCacheDecoder() {
return cacheDecoder;
}
@Override
public ResourceDecoder<InputStream, Bitmap> getSourceDecoder() {
return decoder;
}
@Override
public Encoder<InputStream> getSourceEncoder() {
return sourceEncoder;
}
@Override
public ResourceEncoder<Bitmap> getEncoder() {
return encoder;
}
}
转换的并不是StreamBitmapDataLoadProvider来完成,而是通过它里面的encoder、decoder、sourceEncoder、sourceEncoder
//加载原始数据InputStream为Bitmap
StreamBitmapDecoder decoder;
@Override
public Resource<Bitmap> decode(InputStream source, int width, int height) {
Bitmap bitmap = downsampler.decode(source, bitmapPool, width, height, decodeFormat);
return BitmapResource.obtain(bitmap, bitmapPool);
}
//将加载以及转换后的Bitmap资源写入磁盘缓存
BitmapEncoder encoder;
@Override
public boolean encode(Resource<Bitmap> resource, OutputStream os) {
final Bitmap bitmap = resource.get();
long start = LogTime.getLogTime();
Bitmap.CompressFormat format = getFormat(bitmap);
bitmap.compress(format, quality, os);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Compressed with type: " + format + " of size " + Util.getBitmapByteSize(bitmap) + " in "
+ LogTime.getElapsedMillis(start));
}
return true;
}
//将原始数据写入硬盘缓存
StreamEncoder sourceEncoder;
public boolean encode(InputStream data, OutputStream os) {
byte[] buffer = ByteArrayPool.get().getBytes();
try {
int read;
while ((read = data.read(buffer)) != -1) {
os.write(buffer, 0, read);
}
return true;
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to encode data onto the OutputStream", e);
}
return false;
} finally {
ByteArrayPool.get().releaseBytes(buffer);
}
}
//从硬盘读取
FileToStreamDecoder<Bitmap> cacheDecoder;
@Override
public Resource<T> decode(File source, int width, int height) throws IOException {
InputStream is = null;
Resource<T> result = null;
try {
is = fileOpener.open(source);
result = streamDecoder.decode(is, width, height);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
// Do nothing.
}
}
}
return result;
}
其实在加载与处理的过程中还涉及到很多,比如缓存,我们知道Glide的缓存包括缓存包括磁盘缓存和内存缓存,接下来的文章继续
网友评论