您的位置:首页 > 理论基础 > 计算机网络

[置顶] Android网络通信Volley框架源码浅析(三)

2016-06-13 10:21 429 查看


[置顶] Android网络通信Volley框架源码浅析(三)

标签: Volleyandroid开发框架源码

尊重原创 http://write.blog.csdn.net/postedit/26002961

通过前面浅析(一)浅析(二)的分析,相信大家对于Volley有了初步的认识,但是如果想更深入的理解,还需要靠大家多多看源码。
这篇文章中我们主要来研究一下使用Volley框架请求大量图片的原理,在Android的应用中,通过http请求获取的数据主要有三类:
1、json

2、xml

3、Image
其中json和xml的获取其实原理很简单,使用Volley获取感觉有点大财小用了,了解Volley获取图片的原理才是比较有意义的,因为里面涉及到很多知识点,比如获取大量图片如何防止OOM。

那么我们就开始研究源码吧。

(1) ImageLoader.java

通过它的名字我们就知道是用来加载Image的工具类

[java] view plain copy print?





/**

通过调用ImageLoader的get方法就可以获取到图片,然后通过一个Listener回调,将图片设置到ImgeView中(这个方法务必在主线程中调用)

*/

public class ImageLoader {

/** 前面已经接触过,请求队列(其实不是真实的队列,里面包含了本地队列和网络队列) */

private final RequestQueue mRequestQueue;

/** 图片缓冲,这个缓存不是前面提到的磁盘缓存,这个是内存缓存,我们可以通过LruCache实现这个接口 */

private final ImageCache mCache;

/**

* 用于存放具有相同cacheKey的请求

*/

private final HashMap<String, BatchedImageRequest> mInFlightRequests =

new HashMap<String, BatchedImageRequest>();

/** 用于存放具有相同Key,并且返回了数据的请求*/

private final HashMap<String, BatchedImageRequest> mBatchedResponses =

new HashMap<String, BatchedImageRequest>();

/** Handler to the main thread. */

private final Handler mHandler = new Handler(Looper.getMainLooper());

/** Runnable for in-flight response delivery. */

private Runnable mRunnable;

/**

* Simple cache adapter interface. If provided to the ImageLoader, it

* will be used as an L1 cache before dispatch to Volley. Implementations

* must not block. Implementation with an LruCache is recommended.

*/

public interface ImageCache {

public Bitmap getBitmap(String url);

public void putBitmap(String url, Bitmap bitmap);

}

/**

* 构造函数需要传入一个RequestQueue对象和一个内存缓存对象

* @param queue The RequestQueue to use for making image requests.

* @param imageCache The cache to use as an L1 cache.

*/

public ImageLoader(RequestQueue queue, ImageCache imageCache) {

mRequestQueue = queue;

mCache = imageCache;

}

/**

* 用于图片获取成功或者失败的回调

* @param imageView 需要设置图片的ImageView.

* @param defaultImageResId 默认显示图片.

* @param errorImageResId 出错时显示的图片.

*/

public static ImageListener getImageListener(final ImageView view,

final int defaultImageResId, final int errorImageResId) {

return new ImageListener() {

@Override

public void onErrorResponse(VolleyError error) {

//出错并且设置了出错图片,那么显示出错图片

if (errorImageResId != 0) {

view.setImageResource(errorImageResId);

}

}

@Override

public void onResponse(ImageContainer response, boolean isImmediate) {

if (response.getBitmap() != null) {

//成功获取到了数据,则显示

view.setImageBitmap(response.getBitmap());

} else if (defaultImageResId != 0) {

//数据为空,那么显示默认图片

view.setImageResource(defaultImageResId);

}

}

};

}

/**

* 判断图片是否已经缓存,不同尺寸的图片的cacheKey是不一样的

* @param requestUrl 图片的url

* @param maxWidth 请求图片的宽度.

* @param maxHeight 请求图片的高度.

* @return 返回true则缓存.

*/

public boolean isCached(String requestUrl, int maxWidth, int maxHeight) {

throwIfNotOnMainThread();

String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);

return mCache.getBitmap(cacheKey) != null;

}

/**

* 这个方法时个核心方法,我们主要通过它来获取图片

*

* @param requestUrl The URL of the image to be loaded.

* @param defaultImage Optional default image to return until the actual image is loaded.

*/

public ImageContainer get(String requestUrl, final ImageListener listener) {

return get(requestUrl, listener, 0, 0);

}

/**

* 这个方法比上面方法多了两个参数,如果传入则图片大小会做相应处理,如果不传默认为0,图片大小不做处理

* @param requestUrl The url of the remote image

* @param imageListener The listener to call when the remote image is loaded

* @param maxWidth The maximum width of the returned image.

* @param maxHeight The maximum height of the returned image.

* @return A container object that contains all of the properties of the request, as well as

* the currently available image (default if remote is not loaded).

*/

public ImageContainer get(String requestUrl, ImageListener imageListener,

int maxWidth, int maxHeight) {

// only fulfill requests that were initiated from the main thread.

throwIfNotOnMainThread();

//获取key,其实就是url,width,height按照某种格式拼接

final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);

// 首先从缓存里面取图片

Bitmap cachedBitmap = mCache.getBitmap(cacheKey);

if (cachedBitmap != null) {

// 如果缓存命中,则直接放回

ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);

imageListener.onResponse(container, true);

return container;

}

// 没有命中,则创建一个ImageContainer,注意此时图片数据传入的null,

ImageContainer imageContainer =

new ImageContainer(null, requestUrl, cacheKey, imageListener);

// 这就是为什么在onResponse中我们需要判断图片数据是否为空,此时就是为空的

imageListener.onResponse(imageContainer, true);

// 判断同一个key的请求是否已经存在

BatchedImageRequest request = mInFlightRequests.get(cacheKey);

if (request != null) {

// 如果存在,则直接加入request中,没有必要对一个key发送多个请求

request.addContainer(imageContainer);

return imageContainer;

}

// 发送一个请求,并加入RequestQueue

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);

VolleyLog.e("-------------->"+newRequest.getSequence());

//加入到HashMap中,表明这个key已经存在一个请求

mInFlightRequests.put(cacheKey,

new BatchedImageRequest(newRequest, imageContainer));

return imageContainer;

}

/**

* Handler for when an image was successfully loaded.

* @param cacheKey The cache key that is associated with the image request.

* @param response The bitmap that was returned from the network.

*/

private void onGetImageSuccess(String cacheKey, Bitmap response) {

// 获取图片成功,放入缓存

mCache.putBitmap(cacheKey, response);

// 将cacheKey对应的请求从mInFlightRequests中移除

BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

if (request != null) {

// Update the response bitmap.

request.mResponseBitmap = response;

// Send the batched response

batchResponse(cacheKey, request);

}

}

/**

* Handler for when an image failed to load.

* @param cacheKey The cache key that is associated with the image request.

*/

private void onGetImageError(String cacheKey, VolleyError error) {

// Notify the requesters that something failed via a null result.

// Remove this request from the list of in-flight requests.

BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

if (request != null) {

// Set the error for this request

request.setError(error);

// Send the batched response

batchResponse(cacheKey, request);

}

}

/**

* Container object for all of the data surrounding an image request.

*/

public class ImageContainer {

/**

* 保存从网络获取的图片

*/

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;

}

/**

* 取消一个图片请求

*/

public void cancelRequest() {

if (mListener == null) {

return;

}

//判断此key对应的请求有没有

BatchedImageRequest request = mInFlightRequests.get(mCacheKey);

if (request != null) {

/**如果存在,request中mContainers中的这个Container,如果mContainers的size为0,那么

removeContainerAndCancelIfNecessary返回true

*/

boolean canceled = request.removeContainerAndCancelIfNecessary(this);

if (canceled) {

//如果返回true,那么说明没有任何一个ImageView对这个请求感兴趣,需要移除它

mInFlightRequests.remove(mCacheKey);

}

} else {

// 判断是否这个request已经成功返回了

request = mBatchedResponses.get(mCacheKey);

if (request != null) {

request.removeContainerAndCancelIfNecessary(this);

if (request.mContainers.size() == 0) {

//如果已经成功返回,并且没有ImageView对他感兴趣,那么删除它

mBatchedResponses.remove(mCacheKey);

}

}

}

}

/**

* Returns the bitmap associated with the request URL if it has been loaded, null otherwise.

*/

public Bitmap getBitmap() {

return mBitmap;

}

/**

* Returns the requested URL for this container.

*/

public String getRequestUrl() {

return mRequestUrl;

}

}

/**

* 对Request的一个包装,将所有有共同key的请求放入一个LinkedList中

*/

private class BatchedImageRequest {

/** The request being tracked */

private final Request<?> mRequest;

/** The result of the request being tracked by this item */

private Bitmap mResponseBitmap;

/** Error if one occurred for this response */

private VolleyError mError;

/** 存放具有共同key的ImageContainer*/

private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();

/**

* Constructs a new BatchedImageRequest object

* @param request The request being tracked

* @param container The ImageContainer of the person who initiated the request.

*/

public BatchedImageRequest(Request<?> request, ImageContainer container) {

mRequest = request;

mContainers.add(container);

}

/**

* Set the error for this response

*/

public void setError(VolleyError error) {

mError = error;

}

/**

* Get the error for this response

*/

public VolleyError getError() {

return mError;

}

/**

* Adds another ImageContainer to the list of those interested in the results of

* the request.

*/

public void addContainer(ImageContainer container) {

mContainers.add(container);

}

/**

* 移除一个ImageContainer,如果此时size==0,那么需要从mInFlightRequests中移除该BatchedImageRequest

* @param container The container to remove from the list

* @return True if the request was canceled, false otherwise.

*/

public boolean removeContainerAndCancelIfNecessary(ImageContainer container) {

mContainers.remove(container);

if (mContainers.size() == 0) {

mRequest.cancel();

return true;

}

return false;

}

}

/**

* 当请求返回后,将BatchedImageRequest放入到mBatchedResponses,然后将结果发送给所有具有相同key的ImageContainer,ImageContainer通过里面的Listener发送到ImageView,从而显示出来

* @param cacheKey The cacheKey of the response being delivered.

* @param request The BatchedImageRequest to be delivered.

* @param error The volley error associated with the request (if applicable).

*/

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);

}

}

/**

* 获取一个请求的key,拼接规则就是使用#讲几个连接起来

* @param url The URL of the request.

* @param maxWidth The max-width of the output.

* @param maxHeight The max-height of the output.

*/

private static String getCacheKey(String url, int maxWidth, int maxHeight) {

return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)

.append("#H").append(maxHeight).append(url).toString();

}

}

ImageLoader的代码还是比较复杂的,但是思路还是比较清晰的,总结如下:

1、通过ImageLoader的get方法获取图片,如果我们只想获取原始图片,不用关心大小,则只用传入url和Listener,如果需要设置图片大小,那么传入你需要设置大大小

2、get方法中,先回去缓存中查找,如果命中,那么就直接放回,如果没有命中,那么就判断mInFlightRequests中是否有相同key的BatchedImageRequest,如果有则直接将ImageConainer加入BatchedImageRequest的mContainres中,因为对于同一个key没有必要发送两次请求

3、如果在mInFlightRequest中没有此key,那么需要创建一个ImageRequest对象,并加入RequestQueue中,并使用ImageRequest创建一个BatchedImageRequest加入mInFlightRequest

4、当请求返回后,将BatchedImageRequest从mInFlightRequest中移除,加入mBatchedResponses中,将返回结果返回给所有的ImageContainer

5、如果一个ImageContainer在收到返回结果之前就被cancel掉,那么需要将它从mInFlightRequest的mContainers中移除,如果移除后mContainers的size为0,说明这个请求只有一次,取消了就没有必要请求,需要把BatchedImageRequestmInFlightRequest中移走,从如果不等于0,说明这个请求被其他的ImageContainr需要,不能取消

如果我们仅仅是获取少量图片,Volley框架为我们提供了一个NetworkImageView,这个类继承自ImageView,使用时,我们只需要调用setImageUrl即可,下面就来看其实现机制

(2) NetworkImageView.java

[java] view plain copy print?





public class NetworkImageView extends ImageView {

/** 需要加载图片的url */

private String mUrl;

/**

* 默认显示图片的id

*/

private int mDefaultImageId;

/**

* 错误图片的id

*/

private int mErrorImageId;

/** ImageLoader对象,其实就是用该对象去获取图片,所以了解了ImageLoader后,这个类很好理解 */

private ImageLoader mImageLoader;

/**把这个对象当成url和Listener的封装即可 */

private ImageContainer mImageContainer;

public NetworkImageView(Context context) {

this(context, null);

}

public NetworkImageView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public NetworkImageView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

}

/**

* 设置Url

*

* @param url The URL that should be loaded into this ImageView.

* @param imageLoader ImageLoader that will be used to make the request.

*/

public void setImageUrl(String url, ImageLoader imageLoader) {

mUrl = url;

mImageLoader = imageLoader;

// 这个方法我们后面分析

loadImageIfNecessary(false);

}

/**

* Sets the default image resource ID to be used for this view until the attempt to load it

* completes.

*/

public void setDefaultImageResId(int defaultImage) {

mDefaultImageId = defaultImage;

}

/**

* Sets the error image resource ID to be used for this view in the event that the image

* requested fails to load.

*/

public void setErrorImageResId(int errorImage) {

mErrorImageId = errorImage;

}

/**

* 这个方法在onLayout方法中传入true,其他地方传入false

* @param isInLayoutPass True if this was invoked from a layout pass, false otherwise.

*/

void loadImageIfNecessary(final boolean isInLayoutPass) {

int width = getWidth();

int height = getHeight();

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;

}

// if the URL to be loaded in this view is empty, cancel any old requests and clear the

// currently loaded image.

if (TextUtils.isEmpty(mUrl)) {

if (mImageContainer != null) {

mImageContainer.cancelRequest();

mImageContainer = null;

}

setDefaultImageOrNull();

return;

}

// if there was an old request in this view, check if it needs to be canceled.

if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {

if (mImageContainer.getRequestUrl().equals(mUrl)) {

//如果请求url相同,则直接return

return;

} else {

// 请求url不同,则cancel,并显示默认图片或者不显示图片

mImageContainer.cancelRequest();

setDefaultImageOrNull();

}

}

// Calculate the max image width / height to use while ignoring WRAP_CONTENT dimens.

int maxWidth = wrapWidth ? 0 : width;

int maxHeight = wrapHeight ? 0 : height;

//调用了get方法

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) {

// If this was an immediate response that was delivered inside of a layout

// pass do not set the image immediately as it will trigger a requestLayout

// inside of a layout. Instead, defer setting the image by posting back to

// the main thread.

if (isImmediate && isInLayoutPass) {

post(new Runnable() {

@Override

public void run() {

onResponse(response, false);

}

});

return;

}

if (response.getBitmap() != null) {

setImageBitmap(response.getBitmap());

} else if (mDefaultImageId != 0) {

setImageResource(mDefaultImageId);

}

}

}, maxWidth, maxHeight);

// update the ImageContainer to be the new bitmap container.

mImageContainer = newContainer;

}

private void setDefaultImageOrNull() {

if(mDefaultImageId != 0) {

setImageResource(mDefaultImageId);

}

else {

setImageBitmap(null);

}

}

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

super.onLayout(changed, left, top, right, bottom);

loadImageIfNecessary(true);

}

@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();

}

@Override

protected void drawableStateChanged() {

super.drawableStateChanged();

invalidate();

}

}

到目前为止Volley框架的源码分析差不多了,下一篇文章我打算使用一个GridView展示大量图片的例子来讲解Volley的使用.....
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: