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

通过前面浅析(一)浅析(二)的分析。相信大家对于Volley有了初步的认识,可是假设想更深入的理解。还须要靠大家多多看源代码。

这篇文章中我们主要来研究一下使用Volley框架请求大量图片的原理,在Android的应用中,通过http请求获取的数据主要有三类:

1、json 

2、xml

3、Image

当中json和xml的获取事实上原理非常easy。使用Volley获取感觉有点大財小用了,了解Volley获取图片的原理才是比較有意义的。由于里面涉及到非常多知识点,比方获取大量图片怎样防止OOM。

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

(1) ImageLoader.java

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

/**
通过调用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

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的使用.....

Android网络通信Volley框架源代码浅析(三)的更多相关文章

  1. Android网络通信Volley框架源代码浅析(一)

    尊重原创http://blog.csdn.net/yuanzeyao/article/details/25837897 从今天開始,我打算为大家呈现关于Volley框架的源代码分析的文章,Volley ...

  2. Android网络通信Volley框架源代码浅析(二)

    尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列: ...

  3. Volley框架源代码分析

    Volley框架分析Github链接 Volley框架分析 Volley源代码解析 为了学习Volley的网络框架,我在AS中将Volley代码又一次撸了一遍,感觉这样的照抄代码也是一种挺好的学习方式 ...

  4. Android 网络通信框架Volley简介(Google IO 2013)

    1. 什么是Volley 在这之前,我们在程序中需要和网络通信的时候,大体使用的东西莫过于AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient( ...

  5. Android 网络通信框架Volley(一)

    转自:http://blog.csdn.net/t12x3456/article/details/9221611 1. 什么是Volley 在这之前,我们在程序中需要和网络通信的时候,大体使用的东西莫 ...

  6. [转]Android 网络通信框架Volley简介(Google IO 2013)

    Volley主页 https://android.googlesource.com/platform/frameworks/volley http://www.youtube.com/watch?v= ...

  7. 【转】Android 网络通信框架Volley简介(Google IO 2013)

    Volley主页 https://android.googlesource.com/platform/frameworks/volley http://www.youtube.com/watch?v= ...

  8. Android 网络通信框架Volley简介

    1.1. Volley引入的背景在以前,我们可能面临如下很多麻烦的问题. 比如以前从网上下载图片的步骤可能是这样的流程: 在ListAdapter#getView()里开始图像的读取. 通过Async ...

  9. Android 网络通信框架Volley基本介绍

    Volley主页 https://android.googlesource.com/platform/frameworks/volley http://www.youtube.com/watch?v= ...

随机推荐

  1. 【android自己定义控件】自己定义View属性

    1.自己定义View的属性 2.在View的构造方法中获得我们自己定义的属性 3.重写onMesure 4.重写onDraw 3这个步骤不是必须,当然了大部分情况下还是须要重写的. 1.自己定义Vie ...

  2. session深入解读

    林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:尽管session机制在web应用程序中被採用已经非常长时间了.可是仍然有非常多人不清楚 ...

  3. 【ASP.NET】怎样使用类创建公共函数,在不同ASP.NET页面间反复调用

    为了降低代码冗余,应将公共函数写在类中,供不同ASP.NET页面调用. 1,先新建一个类,并在类中加入函数逻辑 namespace public_function_demo { public clas ...

  4. Apache的Mesos和Google的Kubernetes 有什么区别?

    Apache的Mesos和Google的Kubernetes 有什么区别?本文来自StackOverFlow上的一个问题,主要讨论Mesos和Kubernetes的区别,相信我们很多人也有同意的疑问. ...

  5. 1m网速是什么意思,1m带宽是什么意思

    1M网速下载速度应是多少?我怎么才50多KB?? 建议: 一般来说是90到100算正常.最高能达到120 带究竟该有多快 揭开ADSL真正速度之谜 常常使用ADSL的用户,你知道ADSL的真正速度吗? ...

  6. 矩形旋转碰撞,OBB方向包围盒算法实现

    怎样进行2D旋转矩形的碰撞检測.能够使用一种叫OBB的检測算法(Oriented bounding box)方向包围盒.这个算法是基于SAT(Separating Axis Theorem)分离轴定律 ...

  7. hdu1869六度分离,spfa实现求最短路

    就是给一个图.假设随意两点之间的距离都不超过7则输出Yes,否则 输出No. 因为之前没写过spfa,无聊的试了一下. 大概说下我对spfa实现的理解. 因为它是bellmanford的优化. 所以之 ...

  8. Array of Objects

    You should be comfortable with the content in the modules up to and including the module "Array ...

  9. POJ 3100 &amp; ZOJ 2818 &amp; HDU 2740 Root of the Problem(数学)

    题目链接: POJ:id=3100" style="font-size:18px">http://poj.org/problem? id=3100 ZOJ:http ...

  10. 从零開始学android&lt;数据存储(1)SharedPreferences属性文件.三十五.&gt;

    在android中有五种保存数据的方法.各自是: Shared Preferences Store private primitive data in key-value pairs. 相应属性的键值 ...