@flyouting
2014-07-12T14:51:33.000000Z
字数 6168
阅读 3532
Android
volley
在volley
中有个ImageLoader
用于专门加载图片,看一下其中的重要对象:
/** 处理ImageRequest的请求队列. */
private final RequestQueue mRequestQueue;
/** 第一级缓冲区,在进入volley前首先会这里查看有没有缓存 */
private final ImageCache mCache;
/**
* 一个keys -> BatchedImageRequest的HashMap,
* 用来跟踪动态请求,这样我们可以合并多个请求相同的URL到一个网络请求。
*/
private final HashMap<String, BatchedImageRequest> mInFlightRequests =
new HashMap<String, BatchedImageRequest>();
/** 一个保存当前需要等待响应HashMap */
private final HashMap<String, BatchedImageRequest> mBatchedResponses =
new HashMap<String, BatchedImageRequest>();
ImageLoader
的构造方法只需要传入一个请求队列对象,和一个缓冲区对象。
public ImageLoader(RequestQueue queue, ImageCache imageCache) {
mRequestQueue = queue;
mCache = imageCache;
}
这个一级缓冲区对象我们可以自行扩展,只需要实现ImageCache
接口即可。常用的就是LRUCahce
。
在看ImageLoader
中执行网络请求获得图片的代码前,需要先了解ImageContainer
和BatchedImageRequest
这两个类是什么概念,因为这关系到重复行url请求的合并。先看下ImageContainer
:
public class ImageContainer {
private Bitmap mBitmap;
private final ImageListener mListener;
private final String mCacheKey;
private final String mRequestUrl;
public ImageContainer(Bitmap bitmap, String requestUrl,
String cacheKey, ImageListener listener) {
mBitmap = bitmap;
mRequestUrl = requestUrl;
mCacheKey = cacheKey;
mListener = listener;
}
public void cancelRequest() {
if (mListener == null) {
return;
}
BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
if (request != null) {
boolean canceled = request.removeContainerAndCancelIfNecessary(this);
if (canceled) {
mInFlightRequests.remove(mCacheKey);
}
} else {
// check to see if it is already batched for delivery.
request = mBatchedResponses.get(mCacheKey);
if (request != null) {
request.removeContainerAndCancelIfNecessary(this);
if (request.mContainers.size() == 0) {
mBatchedResponses.remove(mCacheKey);
}
}
}
}
}
这个类其实就是对图片请求相关数据进行了一次封装,包含了图片对象,图片缓存key,图片URL,图片的回调监听器。并包含了一个取消请求的方法。
再来看看BatchedImageRequest
,这个其实是图片网络请求的一个封装,跟上个类不同的是,一个是对数据的封装,一个是对请求的封装,上边那个类对应的是一个具体位置的请求,这个类是对应的某一个URL的请求,比如app中有三个位置的imageview
请求同一个url的图片,那就会有三个ImageContainer
,而只有一个BatchedImageRequest
。
private class BatchedImageRequest {
/** 被追踪的请求 */
private final Request<?> mRequest;
/** 请求返回的图片数据 */
private Bitmap mResponseBitmap;
/** 返回出现错误 */
private VolleyError mError;
/** 对于同一个url的request的有效的ImageContainers列表 */
private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();
public BatchedImageRequest(Request<?> request, ImageContainer container) {
mRequest = request;
mContainers.add(container);
}
/**
* 如果出现其他同地址的请求,把ImageContainer添加进来
*/
public void addContainer(ImageContainer container) {
mContainers.add(container);
}
/**
* 某个ImageContainer被取消,即某个位置的图片请求取消,从当前列表中移除,如果是最后一个,代表这个请求不需要了,会取消整个请求
*/
public boolean removeContainerAndCancelIfNecessary(ImageContainer container) {
mContainers.remove(container);
if (mContainers.size() == 0) {
mRequest.cancel();
return true;
}
return false;
}
}
简单说就是 BatchedImageRequest
对象是请求某一个url的图片数据的执行者,如果好几个ImageView
都需要加载同一个url的图片,那就会生成若干个ImageContainer
加到BatchedImageRequest
里来。代表这些都是同一请求。
现在可以看图片获取部分的代码了:
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight) {
// only fulfill requests that were initiated from the main thread.
throwIfNotOnMainThread();
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
// Try to look up the request in the cache of remote images.
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// Return the cached bitmap.
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
// The bitmap did not exist in the cache, fetch it!
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
// Update the caller to let them know that they should use the default bitmap.
imageListener.onResponse(imageContainer, true);
// Check to see if a request is already in-flight.
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
return imageContainer;
}
// The request is not already in flight. Send the new request to the network and
// track it.
Request<?> newRequest =
new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight,
Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
需要传入图片url,回调的监听器,目标图片的宽高,首先是获取cachekey
,这个key是由图片url和宽高一起组成的。由这个key从一级缓冲区里查看有无缓存,如果有的话,封装一个默认的ImageContainer
返回,没有的话,去正在运行的图片请求集合里去查,当前url是否正在被请求,如果是的话,把当前ImageContainer
添加进入Request
里的ImageContainer
列表。否则的话,生成一个ImageRequest
,添加进入队列执行网络请求,并在正在执行的网络集合中mInFlightRequests
中添加此请求。
图片获取到之后是怎么操作的呢,在上代码中看到,会执行onGetImageSuccess(cacheKey, response)
方法。
private 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);
}
首先是把取得的bitmap
放到一级缓冲区里,然后从正在执行的Request
列表中移除,并把Request
里的mResponseBitmap
对象设置好,接着执行batchResponse(cacheKey, request)
方法。
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);
}
}
首先是把BatchedImageRequest
扔到了待处理的BatchedImageRequest
的hashmap里,即mBatchedResponses
,然后是遍历mBatchedResponses
,依次获取其中的BatchedImageRequest
对象,然后遍历这个对象中的ImageContainer
集合,依次执行ImageContainer
里的listener的回调方法。这样在默认的listener中就会设置图片到ImageView上了。
最后附上网上借用的流程图:
作者: flyouting