Android网络通信Volley框架源代码浅析(三)
尊重原创 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框架源代码浅析(三)的更多相关文章
- Android网络通信Volley框架源代码浅析(一)
尊重原创http://blog.csdn.net/yuanzeyao/article/details/25837897 从今天開始,我打算为大家呈现关于Volley框架的源代码分析的文章,Volley ...
- Android网络通信Volley框架源代码浅析(二)
尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列: ...
- Volley框架源代码分析
Volley框架分析Github链接 Volley框架分析 Volley源代码解析 为了学习Volley的网络框架,我在AS中将Volley代码又一次撸了一遍,感觉这样的照抄代码也是一种挺好的学习方式 ...
- Android 网络通信框架Volley简介(Google IO 2013)
1. 什么是Volley 在这之前,我们在程序中需要和网络通信的时候,大体使用的东西莫过于AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient( ...
- Android 网络通信框架Volley(一)
转自:http://blog.csdn.net/t12x3456/article/details/9221611 1. 什么是Volley 在这之前,我们在程序中需要和网络通信的时候,大体使用的东西莫 ...
- [转]Android 网络通信框架Volley简介(Google IO 2013)
Volley主页 https://android.googlesource.com/platform/frameworks/volley http://www.youtube.com/watch?v= ...
- 【转】Android 网络通信框架Volley简介(Google IO 2013)
Volley主页 https://android.googlesource.com/platform/frameworks/volley http://www.youtube.com/watch?v= ...
- Android 网络通信框架Volley简介
1.1. Volley引入的背景在以前,我们可能面临如下很多麻烦的问题. 比如以前从网上下载图片的步骤可能是这样的流程: 在ListAdapter#getView()里开始图像的读取. 通过Async ...
- Android 网络通信框架Volley基本介绍
Volley主页 https://android.googlesource.com/platform/frameworks/volley http://www.youtube.com/watch?v= ...
随机推荐
- poj3126解题报告
题意:简单的说就是:有一个人门牌号是一个四位数的整数,并且还是一个素数,现在他想要换成另外一个四位数且是素数的门牌号,而且,每次都只能更换这个四位数的一个位置的数 ,也就是每换一次都只改变一个数字,而 ...
- hdu 1698 Just a Hook(线段树之 成段更新)
Just a Hook Time Limit: ...
- 在自己的base脚本中实现自动补全
在90年代Linux和DOS共存的年代里,Linux的Shell们有一个最微不足道但也最实用的小功能,就是命令自动补全.而DOS那个笨蛋一直到死都没学会什么叫易用. Linux的这个微不足道的小传统一 ...
- Oracle基本流程语句
====1.IF语句==========
- String,StringBuffer以及StringBuilder的差别
1.定义: String:String 类代表字符串. Java 程序中的全部字符串字面值(如"abc" )都作为此类的实例实现. 字符串是常量:它们的值在创建之后不能更改. 字符 ...
- android4.0 USB Camera示例(五个辅助)jpg压缩
前的最后一个 我们说,一个直接yuv变成jpg该功能 但是转换不成功 主要功能是yuv420转jpg的 根据研究发现 yuv420的序列是这种 YYYY YYYY UVUV 而yuv422的隔行扫描的 ...
- session校验是否登录
由于一个网站要有好多页面,如果每个页面都写上检验session是否为空,太麻烦了,所以写个工具类,就方便了. 1首先创建一个类库Common 2,然后在这个类库添加引用 3在Common继承 :Sys ...
- CSS 初探
Css: 指层叠样式表 (Cascading Style Sheets),它是用来进行网页风格设计的.通俗的说就是进行网页美化的,没有html依然存在,多了css 它会更好.但是没有html,css就 ...
- 入门git
入门git 0x01前言 既然没有华丽的出场,那就平凡的分享,首先我要说明一点本篇文章针对Git初学者,对我自己学Git的资源的整合,其实本篇索引应该在我写Git系列文章的时候就紧跟着放上索引的, ...
- NETSH WINSOCK RESET这个命令的意义和效果?
简要地netsh winsock reset命令含义复位 Winsock 文件夹.一机多用的假设Winsock协议配置问题,那么问题会导致网络连接,我们需要使用netsh winsock reset命 ...