@flyouting
        
        2014-07-12T06:51:33.000000Z
        字数 6168
        阅读 3811
    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>() {@Overridepublic void onResponse(Bitmap response) {onGetImageSuccess(cacheKey, response);}}, maxWidth, maxHeight,Config.RGB_565, new ErrorListener() {@Overridepublic 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 responsebatchResponse(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() {@Overridepublic 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