zoukankan      html  css  js  c++  java
  • 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的使用.....

  • 相关阅读:
    Python文件的两种用途
    模块的搜索路径
    循环导入问题
    import和from...import
    模块的四种形式
    函数小结
    面向过程编程
    内置函数
    匿名函数
    递归
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5085257.html
Copyright © 2011-2022 走看看