本文仅对Volley中关于Image Request部分的一些简单用例做解析,Http Request部分请参考这里:Android Volley库源码简析(HTTP Request部分)
从常用case入手
用Volley请求图片有两种方式,通过ImageRequest或者是使用NetworkImageView。
使用ImageRequest
// 1. 新建一个queue mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext()); ImageView mImageView; String url = "http://i.imgur.com/7spzG.png"; mImageView = (ImageView) findViewById(R.id.myImage); // 2. 新建一个ImageRequest,传入url和回调 ImageRequest request = new ImageRequest(url, new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap bitmap) { mImageView.setImageBitmap(bitmap); } }, 0, 0, null, new Response.ErrorListener() { public void onErrorResponse(VolleyError error) { mImageView.setImageResource(R.drawable.image_load_error); } }); // 3. 将image request放到queue中 mRequestQueue.add(request); 使用的具体步骤见注释。可以看出image请求与普通http请求发送流程是一样的,只是Request接口的实现不同,其中最重要的是ImageRequest实现的parseNetworkResponse(NetworkResponse response)方法。此方法实现了从data到bitmap的转换。
使用NetworkImageView
// 1. 新建ImageLoader,传入queue和imagecache mImageLoader = new ImageLoader(mRequestQueue, new ImageLoader.ImageCache() { private final LruCache<String, Bitmap> cache = new LruCache<String, Bitmap>(20); @Override public Bitmap getBitmap(String url) { return cache.get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { cache.put(url, bitmap); } }); NetworkImageView mNetworkImageView; private static final String IMAGE_URL = "http://developer.android.com/images/training/system-ui.png"; ... mNetworkImageView = (NetworkImageView) findViewById(R.id.networkImageView); // 2. 将url和ImageLoader传给NetworkImageView mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader); 使用的具体步骤见注释。可以看到使用NetworkImageView比直接使用ImageView的代码量较少,而且最重要的一点是,使用者不用手动管理bitmap和image request的生命周期,当NetworkImageView被回收或者不可见的时候,bitmap资源会被回收,正在进行的image request会被cancel。
下面先对第一个用例进行分析。
ImageRequest
ImageRequest的初始化代码如下所示:
public class ImageRequest extends Request<Bitmap> { ... public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight, ScaleType scaleType, Config decodeConfig, Response.ErrorListener errorListener) { super(Method.GET, url, errorListener); setRetryPolicy( // 设置重试策略 new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT)); mListener = listener; mDecodeConfig = decodeConfig; mMaxWidth = maxWidth; mMaxHeight = maxHeight; mScaleType = scaleType; } ... } 可以看到ImageRequest与普通http request的区别在于ImageRequest需要提供一些图片scale相关的参数,以供decode bitmap时使用,其中decode过程中最关键的parseNetworkResponse()方法源码如下所示:
public class ImageRequest extends Request<Bitmap> { ... private static final Object sDecodeLock = new Object(); ... @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)); } } } 为了避免network dispatcher同时decode bitmap引起的内存不足(OOM),这里使用了一个全局的lock来序列化decode操作。
private Response<Bitmap> doParse(NetworkResponse response) { byte[] data = response.data; BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); Bitmap bitmap = null; // 如果mMaxWidth和mMaxHeight都为0,则按照bitmap实际大小进行decode if (mMaxWidth == 0 && mMaxHeight == 0) { decodeOptions.inPreferredConfig = mDecodeConfig; bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); } else { ... // 根据mMaxWidth、mMaxHeight和scaleType来进行decode } if (bitmap == null) { return Response.error(new ParseError(response)); } else { // 最后将结果包装成Response返回给Delivery return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response)); } } ... } 其中decode相关的代码如下所示:
// 1. 先decode一次,求出图片的实际大小 decodeOptions.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); int actualWidth = decodeOptions.outWidth; int actualHeight = decodeOptions.outHeight; // 2. 求出根据给定的参数的目标宽度和长度 int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, actualWidth, actualHeight, mScaleType); int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight, actualWidth, mScaleType); // 3. 再用目标宽度和长度decode bitmap data decodeOptions.inSampleSize = findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight); Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); // 4. 再次进行downscale if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || tempBitmap.getHeight() > desiredHeight)) { bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true); tempBitmap.recycle(); } else { bitmap = tempBitmap; } scale的主要逻辑在getResizedDimension()和findBestSampleSize()中,这里不做详述。
ImageLoader
ImageLoader在RequestQueue的基础上套了一层memory cache,具体逻辑和RequesQueue很像。
先来看一下它的初始化方法:
public ImageLoader(RequestQueue queue, ImageCache imageCache) { mRequestQueue = queue; mCache = imageCache; } 要传入一个RequestQueue和ImageCache,ImageCache是一个接口,代码如下所示:
public interface ImageCache { public Bitmap getBitmap(String url); public void putBitmap(String url, Bitmap bitmap); } 不适用NetworkImageVIew的情况下,可以通过ImageLoader.get(String, ImageListener)来发起图片资源请求,get方法的代码如下所示:
public ImageContainer get(String requestUrl, final ImageListener listener) { return get(requestUrl, listener, 0, 0); } 最终调用:
public ImageContainer get(String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight, ScaleType scaleType) { // get方法必须在主线程上运行 throwIfNotOnMainThread(); // 1. 生成cache key final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType); // 2. 检查mem cache是否命中 Bitmap cachedBitmap = mCache.getBitmap(cacheKey); if (cachedBitmap != null) { // 命中直接返回ImageContainer,并调用回调 ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null); imageListener.onResponse(container, true); return container; } // 3. mem cache miss,新建ImageContainer ImageContainer imageContainer = new ImageContainer(null, requestUrl, cacheKey, imageListener); // 4. 回调listener,这里应该显示default image imageListener.onResponse(imageContainer, true); // 5. 检查是否有相同request在执行 BatchedImageRequest request = mInFlightRequests.get(cacheKey); if (request != null) { // If it is, add this request to the list of listeners. request.addContainer(imageContainer); return imageContainer; } // 6. 新建ImageRequeue,放到Request中,其后步骤跟单独发起ImageRequest相同 Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType, cacheKey); mRequestQueue.add(newRequest); // 7. 标记当前执行的cache key mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer)); return imageContainer; } 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); } }); } 其中onGetImageSuccess()的代码如下所示:
protected void onGetImageSuccess(String cacheKey, Bitmap response) { // 8. 放到mem cache中 mCache.putBitmap(cacheKey, response); // 9. request完成,清楚标记 BatchedImageRequest request = mInFlightRequests.remove(cacheKey); if (request != null) { // 10. 更新BatchedImageRequest中的bitmap request.mResponseBitmap = response; // Send the batched response batchResponse(cacheKey, request); } } 再来看一下batchResponse():
private void batchResponse(String cacheKey, BatchedImageRequest request) { mBatchedResponses.put(cacheKey, request); if (mRunnable == null) { // 在batch request被清空前只会进来一次 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;// 将runnable设为null才可以开始下一批reponse } }; // Post the runnable. mHandler.postDelayed(mRunnable, mBatchResponseDelayMs); } } 可以看出,batchResponse()的作用是确保某一个batch的第一个response能够在mBatchResponseDelayMs时间间隔后在主线程上执行。
onGetImageError的代码类似,这里不展开了。
NetworkImageView
使用NetworkImageVIew,非常方便:
mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader); 我们从setImageUrl开始分析。NetworkImageView继承于ImageView:
public class NetworkImageView extends ImageView { private String mUrl; private int mDefaultImageId; private int mErrorImageId; private ImageLoader mImageLoader; private ImageContainer mImageContainer; ... } setImageUrl()的代码如下所示:
public void setImageUrl(String url, ImageLoader imageLoader) { mUrl = url; mImageLoader = imageLoader; // The URL has potentially changed. See if we need to load it. loadImageIfNecessary(false); } 调用了loadImageIfNecessary()方法:
void loadImageIfNecessary(final boolean isInLayoutPass) { int width = getWidth(); int height = getHeight(); ScaleType scaleType = getScaleType(); boolean wrapWidth = false, wrapHeight = false; if (getLayoutParams() != null) { wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT; wrapHeight = getLayoutParams().height == LayoutParams.WRAP_CONTENT; } // if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content // view, hold off on loading the image. boolean isFullyWrapContent = wrapWidth && wrapHeight; if (width == 0 && height == 0 && !isFullyWrapContent) { return; } // 可以通过设置null url来清空imageview显示和cancel request if (TextUtils.isEmpty(mUrl)) { if (mImageContainer != null) { mImageContainer.cancelRequest(); mImageContainer = null; } setDefaultImageOrNull(); return; } // 1. 如果当前view上有request正在执行,那么 if (mImageContainer != null && mImageContainer.getRequestUrl() != null) { if (mImageContainer.getRequestUrl().equals(mUrl)) { // 如果url相同,那么忽略这次请求 return; } else { // 如果url不同,那么取消前一次 mImageContainer.cancelRequest(); setDefaultImageOrNull(); } } // 2. 计算maxWidth和maxHeight。如果view设置了LayoutParams.WRAP_CONTENT,那么不对maxWidth和maxHeight作限制 int maxWidth = wrapWidth ? 0 : width; int maxHeight = wrapHeight ? 0 : height; // 3. 调用ImageLoader的get方法发起image request ImageContainer newContainer = mImageLoader.get(mUrl, new ImageListener() { @Override public void onErrorResponse(VolleyError error) { if (mErrorImageId != 0) { setImageResource(mErrorImageId); } } @Override public void onResponse(final ImageContainer response, boolean isImmediate) { // 防止在layout过程中调用requestLayout方法 if (isImmediate && isInLayoutPass) { post(new Runnable() { @Override public void run() { onResponse(response, false); } }); return; } // 4. 拿到bitmap后,设置上去 if (response.getBitmap() != null) { setImageBitmap(response.getBitmap()); } else if (mDefaultImageId != 0) { setImageResource(mDefaultImageId); } } }, maxWidth, maxHeight, scaleType); // update the ImageContainer to be the new bitmap container. mImageContainer = newContainer; } 可见NetworkImageView也是利用了ImageLoader的get方法来实现图片下载。NetworkImageView的方便之处在于让我们不用手动管理图片资源的生命周期,和显示图片的状态切换(default, error状态)。
最后,看一下NetworkImageView自动回收资源是怎么实现的:
@Override protected void onDetachedFromWindow() { if (mImageContainer != null) { // If the view was bound to an image request, cancel it and clear // out the image from the view. mImageContainer.cancelRequest(); setImageBitmap(null); // also clear out the container so we can reload the image if necessary. mImageContainer = null; } super.onDetachedFromWindow(); } 关键在于onDetachedFromWindow()的调用时机。由这篇文章可以了解到,onDetachedFromWindow()会在view被销毁,不再显示的时候调用。所以这样子做可以确保不会在view还在显示的状态下回收image资源。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。