[关闭]
@flyouting 2014-07-12T14:51:33.000000Z 字数 6168 阅读 3500

Volley源码学习笔记三

Android volley


图片获取

volley中有个ImageLoader用于专门加载图片,看一下其中的重要对象:

  1. /** 处理ImageRequest的请求队列. */
  2. private final RequestQueue mRequestQueue;
  3. /** 第一级缓冲区,在进入volley前首先会这里查看有没有缓存 */
  4. private final ImageCache mCache;
  5. /**
  6. * 一个keys -> BatchedImageRequest的HashMap,
  7. * 用来跟踪动态请求,这样我们可以合并多个请求相同的URL到一个网络请求。
  8. */
  9. private final HashMap<String, BatchedImageRequest> mInFlightRequests =
  10. new HashMap<String, BatchedImageRequest>();
  11. /** 一个保存当前需要等待响应HashMap */
  12. private final HashMap<String, BatchedImageRequest> mBatchedResponses =
  13. new HashMap<String, BatchedImageRequest>();

ImageLoader的构造方法只需要传入一个请求队列对象,和一个缓冲区对象。

  1. public ImageLoader(RequestQueue queue, ImageCache imageCache) {
  2. mRequestQueue = queue;
  3. mCache = imageCache;
  4. }

这个一级缓冲区对象我们可以自行扩展,只需要实现ImageCache接口即可。常用的就是LRUCahce

在看ImageLoader中执行网络请求获得图片的代码前,需要先了解ImageContainerBatchedImageRequest这两个类是什么概念,因为这关系到重复行url请求的合并。先看下ImageContainer

  1. public class ImageContainer {
  2. private Bitmap mBitmap;
  3. private final ImageListener mListener;
  4. private final String mCacheKey;
  5. private final String mRequestUrl;
  6. public ImageContainer(Bitmap bitmap, String requestUrl,
  7. String cacheKey, ImageListener listener) {
  8. mBitmap = bitmap;
  9. mRequestUrl = requestUrl;
  10. mCacheKey = cacheKey;
  11. mListener = listener;
  12. }
  13. public void cancelRequest() {
  14. if (mListener == null) {
  15. return;
  16. }
  17. BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
  18. if (request != null) {
  19. boolean canceled = request.removeContainerAndCancelIfNecessary(this);
  20. if (canceled) {
  21. mInFlightRequests.remove(mCacheKey);
  22. }
  23. } else {
  24. // check to see if it is already batched for delivery.
  25. request = mBatchedResponses.get(mCacheKey);
  26. if (request != null) {
  27. request.removeContainerAndCancelIfNecessary(this);
  28. if (request.mContainers.size() == 0) {
  29. mBatchedResponses.remove(mCacheKey);
  30. }
  31. }
  32. }
  33. }
  34. }

这个类其实就是对图片请求相关数据进行了一次封装,包含了图片对象,图片缓存key,图片URL,图片的回调监听器。并包含了一个取消请求的方法。

再来看看BatchedImageRequest,这个其实是图片网络请求的一个封装,跟上个类不同的是,一个是对数据的封装,一个是对请求的封装,上边那个类对应的是一个具体位置的请求,这个类是对应的某一个URL的请求,比如app中有三个位置的imageview请求同一个url的图片,那就会有三个ImageContainer,而只有一个BatchedImageRequest

  1. private class BatchedImageRequest {
  2. /** 被追踪的请求 */
  3. private final Request<?> mRequest;
  4. /** 请求返回的图片数据 */
  5. private Bitmap mResponseBitmap;
  6. /** 返回出现错误 */
  7. private VolleyError mError;
  8. /** 对于同一个url的request的有效的ImageContainers列表 */
  9. private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();
  10. public BatchedImageRequest(Request<?> request, ImageContainer container) {
  11. mRequest = request;
  12. mContainers.add(container);
  13. }
  14. /**
  15. * 如果出现其他同地址的请求,把ImageContainer添加进来
  16. */
  17. public void addContainer(ImageContainer container) {
  18. mContainers.add(container);
  19. }
  20. /**
  21. * 某个ImageContainer被取消,即某个位置的图片请求取消,从当前列表中移除,如果是最后一个,代表这个请求不需要了,会取消整个请求
  22. */
  23. public boolean removeContainerAndCancelIfNecessary(ImageContainer container) {
  24. mContainers.remove(container);
  25. if (mContainers.size() == 0) {
  26. mRequest.cancel();
  27. return true;
  28. }
  29. return false;
  30. }
  31. }

简单说就是 BatchedImageRequest对象是请求某一个url的图片数据的执行者,如果好几个ImageView都需要加载同一个url的图片,那就会生成若干个ImageContainer加到BatchedImageRequest里来。代表这些都是同一请求。

现在可以看图片获取部分的代码了:

  1. public ImageContainer get(String requestUrl, ImageListener imageListener,
  2. int maxWidth, int maxHeight) {
  3. // only fulfill requests that were initiated from the main thread.
  4. throwIfNotOnMainThread();
  5. final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
  6. // Try to look up the request in the cache of remote images.
  7. Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
  8. if (cachedBitmap != null) {
  9. // Return the cached bitmap.
  10. ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
  11. imageListener.onResponse(container, true);
  12. return container;
  13. }
  14. // The bitmap did not exist in the cache, fetch it!
  15. ImageContainer imageContainer =
  16. new ImageContainer(null, requestUrl, cacheKey, imageListener);
  17. // Update the caller to let them know that they should use the default bitmap.
  18. imageListener.onResponse(imageContainer, true);
  19. // Check to see if a request is already in-flight.
  20. BatchedImageRequest request = mInFlightRequests.get(cacheKey);
  21. if (request != null) {
  22. // If it is, add this request to the list of listeners.
  23. request.addContainer(imageContainer);
  24. return imageContainer;
  25. }
  26. // The request is not already in flight. Send the new request to the network and
  27. // track it.
  28. Request<?> newRequest =
  29. new ImageRequest(requestUrl, new Listener<Bitmap>() {
  30. @Override
  31. public void onResponse(Bitmap response) {
  32. onGetImageSuccess(cacheKey, response);
  33. }
  34. }, maxWidth, maxHeight,
  35. Config.RGB_565, new ErrorListener() {
  36. @Override
  37. public void onErrorResponse(VolleyError error) {
  38. onGetImageError(cacheKey, error);
  39. }
  40. });
  41. mRequestQueue.add(newRequest);
  42. mInFlightRequests.put(cacheKey,
  43. new BatchedImageRequest(newRequest, imageContainer));
  44. return imageContainer;
  45. }

需要传入图片url,回调的监听器,目标图片的宽高,首先是获取cachekey,这个key是由图片url和宽高一起组成的。由这个key从一级缓冲区里查看有无缓存,如果有的话,封装一个默认的ImageContainer返回,没有的话,去正在运行的图片请求集合里去查,当前url是否正在被请求,如果是的话,把当前ImageContainer添加进入Request里的ImageContainer列表。否则的话,生成一个ImageRequest,添加进入队列执行网络请求,并在正在执行的网络集合中mInFlightRequests中添加此请求。

图片获取到之后是怎么操作的呢,在上代码中看到,会执行onGetImageSuccess(cacheKey, response)方法。

  1. private void onGetImageSuccess(String cacheKey, Bitmap response) {
  2. // cache the image that was fetched.
  3. mCache.putBitmap(cacheKey, response);
  4. // remove the request from the list of in-flight requests.
  5. BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
  6. if (request != null) {
  7. // Update the response bitmap.
  8. request.mResponseBitmap = response;
  9. // Send the batched response
  10. batchResponse(cacheKey, request);
  11. }

首先是把取得的bitmap放到一级缓冲区里,然后从正在执行的Request列表中移除,并把Request里的mResponseBitmap对象设置好,接着执行batchResponse(cacheKey, request)方法。

  1. private void batchResponse(String cacheKey, BatchedImageRequest request) {
  2. mBatchedResponses.put(cacheKey, request);
  3. // If we don't already have a batch delivery runnable in flight, make a new one.
  4. // Note that this will be used to deliver responses to all callers in mBatchedResponses.
  5. if (mRunnable == null) {
  6. mRunnable = new Runnable() {
  7. @Override
  8. public void run() {
  9. for (BatchedImageRequest bir : mBatchedResponses.values()) {
  10. for (ImageContainer container : bir.mContainers) {
  11. // If one of the callers in the batched request canceled the request
  12. // after the response was received but before it was delivered,
  13. // skip them.
  14. if (container.mListener == null) {
  15. continue;
  16. }
  17. if (bir.getError() == null) {
  18. container.mBitmap = bir.mResponseBitmap;
  19. container.mListener.onResponse(container, false);
  20. } else {
  21. container.mListener.onErrorResponse(bir.getError());
  22. }
  23. }
  24. }
  25. mBatchedResponses.clear();
  26. mRunnable = null;
  27. }
  28. };
  29. // Post the runnable.
  30. mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
  31. }
  32. }

首先是把BatchedImageRequest扔到了待处理的BatchedImageRequest的hashmap里,即mBatchedResponses,然后是遍历mBatchedResponses,依次获取其中的BatchedImageRequest对象,然后遍历这个对象中的ImageContainer集合,依次执行ImageContainer里的listener的回调方法。这样在默认的listener中就会设置图片到ImageView上了。

最后附上网上借用的流程图:

此处输入图片的描述

作者: flyouting

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注