研究完Volley框架的源码,继续来看看ImageLoader的内部是怎么实现的,据说是封装了ImageRequest,来探探究竟。
首先使用ImageLoader,两步
1、实例化ImageLoader
2、使用ImageLoader.get(String url,ImageLister listener)。
首先第一步很简单,构造参数为一个RequestQueue和一个ImageCache,RequestQueue在前一篇文章已经说明,那么ImageCache是什么呢:
public interface ImageCache {
public Bitmap getBitmap(String url);
public void putBitmap(String url, Bitmap bitmap);
}
很简单,一个缓存类的接口,只生命了get和put方法,让子类去定义如何读写缓存,Volley并没有提供默认的子类,用户可以使用LruCache去实现内存缓存,也可以使用LruCache+DiskLruCache去实现内存+磁盘缓存。靠用户定义,ImageLoader只需要从ImageCache中能够读写缓存就行。
ImageLoader构造方法:
public ImageLoader(RequestQueue queue, ImageCache imageCache) {
mRequestQueue = queue;
mCache = imageCache;
}
只是给成员变量赋值而已。
第二步是使用get方法去加载图片,get方法有诸多重载,最后被调用的重载方法为:
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight, ScaleType scaleType) {
可以看见参数有5个,不多解释意义,做过图片压缩的就知道那个maxWidth和maxHeight是用来做图片压缩时,计算inSampleSize的。如果不传这两个参数,那么默认都为0,即显示的图片不经过压缩。
这里需要解释几个变量和类:
CacheKey:标志请求的唯一性的Key,同时也是在Cache里面的Key,在ImageLoader中有一个getCacheKey方法:
private static String getCacheKey(String url, int maxWidth, int maxHeight, ScaleType scaleType) {
return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
.append("#H").append(maxHeight).append("#S").append(scaleType.ordinal()).append(url)
.toString();
}
可以看见参数为get方法中的5个参数,所以可以得知,一个url,maxWidth,maxHeight,scaleType都相同的Request被视为同一个Request,它们的CacheKey相同。CacheKey的计算不包括Listener,所以同一个请求可以传入不同的Listener。
ImageContainer:每次的get()方法被调用代表着一次请求,每一次的请求都用ImageContainer封装起来
public class ImageContainer {
/**
* The most relevant bitmap for the container. If the image was in cache, the
* Holder to use for the final bitmap (the one that pairs to the requested URL).
*/
private Bitmap mBitmap;
private final ImageListener mListener;
/** The cache key that was associated with the request */
private final String mCacheKey;
/** The request URL that was specified */
private final String mRequestUrl;
/**
* Constructs a BitmapContainer object.
* @param bitmap The final bitmap (if it exists).
* @param requestUrl The requested URL for this container.
* @param cacheKey The cache key that identifies the requested URL for this container.
*/
public ImageContainer(Bitmap bitmap, String requestUrl,
String cacheKey, ImageListener listener) {
mBitmap = bitmap;
mRequestUrl = requestUrl;
mCacheKey = cacheKey;
mListener = listener;
}
包装了这次请求的返回值BItmap,观察者Listener,CacheKey和URL。
BatchedImageRequest:由上述可知,CacheKey相同的请求被视为同一个 请求,因为url,maxWidth,maxHeight,scaleType都相同的请求,返回的Bitmap必是一样的,所以对于这些同样的请求,只需要做一次联网加载图片即可,所以就诞生了BatchedImageRequest,它将代表同一个请求的所有ImageContainer都收集起来,然后用一个Request去加载,返回的Bitmap就是所有ImageContainer所需要的Bitmap
private class BatchedImageRequest {
/** 对这些请求的唯一一个Request对象*/
private final Request<?> mRequest;
/**请求的Bitmap */
private Bitmap mResponseBitmap;
/** 错误*/
private VolleyError mError;
/** 所有请求的封装集合 */
private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();
public BatchedImageRequest(Request<?> request, ImageContainer container) {
mRequest = request;
mContainers.add(container);
}
/**
* Adds another ImageContainer to the list of those interested in the results of
* the request.
*/
public void addContainer(ImageContainer container) {
mContainers.add(container);
}
}
这样就可以继续去讲ImageLoader.get到底干了什么事:
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight, ScaleType scaleType) {
// 这里确保了get只能在主线程执行
throwIfNotOnMainThread();
//根据4个参数算出CacheKey
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
//检查该CacheKey对应的Bitmap是否在缓存中
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// 如果在缓存中,直接读取,new 出一个ImageContainer,将Bitmap丢进去
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
//通知Listener,然后返回
imageListener.onResponse(container, true);
return container;
}
// 如果缓存没有,new 出一个请求的封装,注意这里的Bitmap参数为空,等待加载
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
// 这里的意思是告诉调用者,imageContainer的Bitmap为空,需要让ImageView显示一个默认的图片然后等待加载
//所以在调用端需要判断ImageContainer.bitmap是否为空
imageListener.onResponse(imageContainer, true);
//检查,这个chaceKey对应的请求是否是一个新的请求
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// 如果此请求已经发生过,那么将ImageContainer放入BatchedImageRequest
//说明这个请求的观察者又多了一个
request.addContainer(imageContainer);
return imageContainer;
}
// 如果这是一个新请求,则new出一个Request,放入RequestQueue,等待NetworkDispatcher加载
Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
cacheKey);
mRequestQueue.add(newRequest);
//然后把这个请求放入BatchedImageRequest,代表这个请求已经发起过了,以后再有同样的请求,不会再生成Request。
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
上面的中文注释将过程已经说的很清楚了,唯一不清楚的可能就是makeImageRequest()发生了什么:
protected Request<Bitmap> makeImageRequest(String requestUrl, int maxWidth, int maxHeight,
ScaleType scaleType, final String cacheKey) {
return new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight, scaleType, Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});
}
public class ImageRequest extends Request<Bitmap>
就是生成一个Request的实现类,ImageRequest而已。
然后的工作就是CacheDispatcher和NetworkDispatcher他们的了,无论从哪个方式获取到NetworkResponse,都会调用Request.parseNetworkResponse()来进行对返回值的加工:
@Override
protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
// Serialize all decode on a global lock to reduce concurrent heap usage.
synchronized (sDecodeLock) {
try {
return doParse(response);
} catch (OutOfMemoryError e) {
VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
return Response.error(new ParseError(e));
}
}
}
doParse()主要做的事就是判断maxWidth和maxHeight是不是都为0,如果是就直接从NetworkResponse读取Bitmap返回,如果不是就说明要求做图片压缩,就进行图片压缩再返回压缩后的Bitmap。源码就不进去了
这个方法被调用后CacheDispatcher或者NetworkDispatcher会继续调用Delivery.postResponse(),从而使ImageRequest.deliverResponse()被调用
@Override
protected void deliverResponse(Bitmap response) {
mListener.onResponse(response);
}
这个Listener是在哪里被传入的呢,还记得ImageLoader中,来了个新请求时,就创建一个新的ImageRequest并add进RequestQueue吗,就在上面的makeImageRequest方法中,可以看出listener.onResponse()方法调用了onGetImageSuccess:
protected void onGetImageSuccess(String cacheKey, Bitmap response) {
// cache the image that was fetched.
mCache.putBitmap(cacheKey, response);
// remove the request from the list of in-flight requests.
BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
if (request != null) {
// Update the response bitmap.
request.mResponseBitmap = response;
// Send the batched response
batchResponse(cacheKey, request);
}
}
这里干的事就是表示这个cacheKey对应的请求已经完成了,于是将BatchedImageRequest从队列中取出,然后进入batchResponse()
private void batchResponse(String cacheKey, BatchedImageRequest request) {
mBatchedResponses.put(cacheKey, request);
// If we don't already have a batch delivery runnable in flight, make a new one.
// Note that this will be used to deliver responses to all callers in mBatchedResponses.
if (mRunnable == null) {
mRunnable = new Runnable() {
@Override
public void run() {
for (BatchedImageRequest bir : mBatchedResponses.values()) {
for (ImageContainer container : bir.mContainers) {
// If one of the callers in the batched request canceled the request
// after the response was received but before it was delivered,
// skip them.
if (container.mListener == null) {
continue;
}
if (bir.getError() == null) {
container.mBitmap = bir.mResponseBitmap;
container.mListener.onResponse(container, false);
} else {
container.mListener.onErrorResponse(bir.getError());
}
}
}
mBatchedResponses.clear();
mRunnable = null;
}
};
// Post the runnable.
mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
}
}
在这里就简单了,同一个请求有很多个发起者,就对该请求的所有ImageContainer.bitmap进行赋值,然后通知各自的listener。如果是error,调用的就是listener.onErrorResponse(),这样,对于该请求的所有发起者,都得到了结果。(该过程在被封装在一个Runnable中,被Handler送去主线程执行。)