Volley网络通信框架

一、Volley介绍:

(一)、为什么要用Volley?

Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮。

我们平时在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据。Android系统中主要提供了两种方式来进行HTTP通信,HttpURLConnection和HttpClient,几乎在任何项目的代码中我们都能看到这两个类的身影,使用率非常高。此外,ListView 自定义adapter中getView()方法重复调用,如果ImageView不利用缓存机制,那么网络的负荷就会更大!

不过HttpURLConnection和HttpClient的用法还是稍微有些复杂的,如果不进行适当封装的话,很容易就会写出不少重复代码。于是乎,一些Android网络通信框架也就应运而生,比如说AsyncHttpClient,它把HTTP所有的通信细节全部封装在了内部,我们只需要简单调用几行代码就可以完成通信操作了。再比如Universal-Image-Loader,它使得在界面上显示网络图片的操作变得极度简单,开发者不用关心如何从网络上获取图片,也不用关心开启线程、回收图片资源等细节,Universal-Image-Loader已经把一切都做好了。

Android开发团队也是意识到了有必要将HTTP的通信操作再进行简单化,于是在2013年Google I/O大会上推出了一个新的网络通信框架——Volley。Volley可是说是把AsyncHttpClient和Universal-Image-Loader的优点集于一身,既可以像AsyncHttpClient一样非常简单地进行HTTP通信,也可以像Universal-Image-Loader一样轻松加载网络图片。除了简单易用之外,Volley在性能方面也进行了大幅度的调整,它的设计目标就是:适合进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。

Volley.newRequestQueue()方法在一个app最好执行一次,可以使用单例设计模式或者在application完成初始化 .

(二)、核心类分析:

1、StringRequest:网络请求,返回字符串

2、JsonObjectRequest:网络请求,返回JsonObject对象

3、ImageRequest类:

  •  使用volley加载网络图片。

4、ImageLoader类:

  •  使用volley加载网络图片,主要用到其中的ImageLoader类。
  •  ImageLoader类主要是帮我们载入和缓存从远程网络加载的图片。构造方法中我们需要传入请求队列和一个ImageCache接口的实现(这个地方谷歌并没有为我们做好图片的缓存,我们需要按照自己的思路去实现这些功能,比如LRU,LFU,FIFO等)。另外,getImageListener(ImageView view, int defaultImageResId, int errorImageResId)为我们提供了默认的ImageLoader.ImageListener实现。还有一个注意事项:所有的请求都必须在主线程中发出。 
  • ImageLoader提供了两个get方法,值得注意的地方是,get(java.lang.String requestUrl, ImageLoader.ImageListener imageListener, int maxWidth, int maxHeight)这个方法中,我们可以通过设置最大宽高来限制加载到内存中的图片的大小,减少OOM的发生,当加载一些大图片时,效果还是非常明显的。 

5、NetworkImageView

  • NetworkImageView是对ImageView的再封装加入网络请求
  • 缺点就是加载大量图片的时候可能更容易出现OOM问题,因为NetworkImageView没有处理图片的压缩,当然,当保证加载的图片很小时适用NetworkImageView。或者重写NetworkImageView,在其中添加一个设置加载尺寸的方法。 

(三)、ImageLoaderImageRequest 源码分析对比:

1)、ImageLoader的核心代码:

 

 publicclass ImageLoader {

    /** RequestQueue for dispatching ImageRequests onto. */

    privatefinal RequestQueue mRequestQueue;

 

    /** Amount of time to wait after first response arrives before delivering all responses. */

    privateintmBatchResponseDelayMs = 100;

 

    /** The cache implementation to be used as an L1 cache before calling into volley. */

    privatefinal ImageCache mCache;

 

    /**

     * HashMap of Cache keys -> BatchedImageRequest used to track in-flight requests so

     * that we can coalesce multiple requests to the same URL into a single network request.

     */

    privatefinal HashMap<String, BatchedImageRequest> mInFlightRequests =

            new HashMap<String, BatchedImageRequest>();

 

    /** HashMap of the currently pending responses (waiting to be delivered). */

    privatefinal HashMap<String, BatchedImageRequest> mBatchedResponses =

            new HashMap<String, BatchedImageRequest>();

 

    /** Handler to the main thread. */

    privatefinal 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.

     */

    publicinterface ImageCache {

        public Bitmap getBitmap(String url);

        publicvoid putBitmap(String url, Bitmap bitmap);

    }

 

    /**

     * Constructs a new ImageLoader.

     * @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;

    }

 

    /**

     * The default implementation of ImageListener which handles basic functionality

     * of showing a default image until the network response is received, at which point

     * it will switch to either the actual image or the error image.

     * @param imageView The imageView that the listener is associated with.

     * @param defaultImageResId Default image resource ID to use, or 0 if it doesn't exist.

     * @param errorImageResId Error image resource ID to use, or 0 if it doesn't exist.

     */

    publicstatic ImageListener getImageListener(final ImageView view,

            finalint defaultImageResId, finalint errorImageResId) {

        returnnew ImageListener() {

            @Override

            publicvoid onErrorResponse(VolleyError error) {

                if (errorImageResId != 0) {

                    view.setImageResource(errorImageResId);

                }

            }

 

            @Override

            publicvoid onResponse(ImageContainer response, boolean isImmediate) {

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

                    view.setImageBitmap(response.getBitmap());

                } elseif (defaultImageResId != 0) {

                    view.setImageResource(defaultImageResId);

                }

            }

        };

    }

 

    /**

     * Interface for the response handlers on image requests.

     *

     * The call flow is this:

     * 1. Upon being  attached to a request, onResponse(response, true) will

     * be invoked to reflect any cached data that was already available. If the

     * data was available, response.getBitmap() will be non-null.

     *

     * 2. After a network response returns, only one of the following cases will happen:

     *   - onResponse(response, false) will be called if the image was loaded.

     *   or

     *   - onErrorResponse will be called if there was an error loading the image.

     */

    publicinterface ImageListener extends ErrorListener {

        /**

         * Listens for non-error changes to the loading of the image request.

         *

         * @param response Holds all information pertaining to the request, as well

         * as the bitmap (if it is loaded).

         * @param isImmediate True if this was called during ImageLoader.get() variants.

         * This can be used to differentiate between a cached image loading and a network

         * image loading in order to, for example, run an animation to fade in network loaded

         * images.

         */

        publicvoid onResponse(ImageContainer response, boolean isImmediate);

    }

 

    /**

     * Checks if the item is available in the cache.

     * @param requestUrl The url of the remote image

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

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

     * @return True if the item exists in cache, false otherwise.

     */

    publicboolean isCached(String requestUrl, int maxWidth, int maxHeight) {

        throwIfNotOnMainThread();

 

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

        returnmCache.getBitmap(cacheKey) != null;

    }

 

    /**

     * Returns an ImageContainer for the requested URL.

     *

     * The ImageContainer will contain either the specified default bitmap or the loaded bitmap.

     * If the default was returned, the {@link ImageLoader} will be invoked when the

     * request is fulfilled.

     *

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

    }

 

    /**

     * Issues a bitmap request with the given URL if that image is not available

     * in the cache, and returns a bitmap container that contains all of the data

     * relating to the request (as well as the default image if the requested

     * image is not available).

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

 

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

 

        // Try to look up the request in the cache of remote images.

        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);

        if (cachedBitmap != null) {

            // Return the cached bitmap.

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

            imageListener.onResponse(container, true);

            return container;

        }

 

        // The bitmap did not exist in the cache, fetch it!

        ImageContainer imageContainer =

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

 

        // Update the caller to let them know that they should use the default bitmap.

        imageListener.onResponse(imageContainer, true);

 

        // Check to see if a request is already in-flight.

        BatchedImageRequest request = mInFlightRequests.get(cacheKey);

        if (request != null) {

            // If it is, add this request to the list of listeners.

            request.addContainer(imageContainer);

            return imageContainer;

        }

 

        // The request is not already in flight. Send the new request to the network and

        // track it.

        Request<?> newRequest =

            new ImageRequest(requestUrl, new Listener<Bitmap>() {

                @Override

                publicvoid onResponse(Bitmap response) {

                    onGetImageSuccess(cacheKey, response);

                }

            }, maxWidth, maxHeight,

            Config.RGB_565, new ErrorListener() {

                @Override

                publicvoid onErrorResponse(VolleyError error) {

                    onGetImageError(cacheKey, error);

                }

            });

 

        mRequestQueue.add(newRequest);

        mInFlightRequests.put(cacheKey,

                new BatchedImageRequest(newRequest, imageContainer));

        return imageContainer;

    }

 

    /**

     * Sets the amount of time to wait after the first response arrives before delivering all

     * responses. Batching can be disabled entirely by passing in 0.

     * @param newBatchedResponseDelayMs The time in milliseconds to wait.

     */

    publicvoid setBatchedResponseDelay(int newBatchedResponseDelayMs) {

        mBatchResponseDelayMs = newBatchedResponseDelayMs;

    }

 

    /**

     * 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.

     */

    privatevoid onGetImageSuccess(String cacheKey, Bitmap response) {

        // cache the image that was fetched.

        mCache.putBitmap(cacheKey, response);

 

        // remove the request from the list of in-flight requests.

        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.

     */

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

 

        // Set the error for this request

        request.setError(error);

 

        if (request != null) {

            // Send the batched response

            batchResponse(cacheKey, request);

        }

    }

 

    /**

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

     */

    publicclass ImageContainer {

        /**

         * The most relevant bitmap for the container. If the image was in cache, the

         * Holder to use for the final bitmap (the one that pairs to the requested URL).

         */

        private Bitmap mBitmap;

 

        privatefinal ImageListener mListener;

 

        /** The cache key that was associated with the request */

        privatefinal String mCacheKey;

 

        /** The request URL that was specified */

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

        }

 

        /**

         * Releases interest in the in-flight request (and cancels it if no one else is listening).

         */

        publicvoid cancelRequest() {

            if (mListener == null) {

                return;

            }

 

            BatchedImageRequest request = mInFlightRequests.get(mCacheKey);

            if (request != null) {

                boolean canceled = request.removeContainerAndCancelIfNecessary(this);

                if (canceled) {

                    mInFlightRequests.remove(mCacheKey);

                }

            } else {

                // check to see if it is already batched for delivery.

                request = mBatchedResponses.get(mCacheKey);

                if (request != null) {

                    request.removeContainerAndCancelIfNecessary(this);

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

                        mBatchedResponses.remove(mCacheKey);

                    }

                }

            }

        }

 

        /**

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

         */

        public Bitmap getBitmap() {

            returnmBitmap;

        }

 

        /**

         * Returns the requested URL for this container.

         */

        public String getRequestUrl() {

            returnmRequestUrl;

        }

    }

 

    /**

     * Wrapper class used to map a Request to the set of active ImageContainer objects that are

     * interested in its results.

     */

    privateclass BatchedImageRequest {

        /** The request being tracked */

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

 

        /** List of all of the active ImageContainers that are interested in the request */

        privatefinal 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

         */

        publicvoid setError(VolleyError error) {

            mError = error;

        }

 

        /**

         * Get the error for this response

         */

        public VolleyError getError() {

            returnmError;

        }

 

        /**

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

         * the request.

         */

        publicvoid addContainer(ImageContainer container) {

            mContainers.add(container);

        }

 

        /**

         * Detatches the bitmap container from the request and cancels the request if no one is

         * left listening.

         * @param container The container to remove from the list

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

         */

        publicboolean removeContainerAndCancelIfNecessary(ImageContainer container) {

            mContainers.remove(container);

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

                mRequest.cancel();

                returntrue;

            }

            returnfalse;

        }

    }

 

    /**

     * Starts the runnable for batched delivery of responses if it is not already started.

     * @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).

     */

    privatevoid 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

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

        }

    }

 

    privatevoid throwIfNotOnMainThread() {

        if (Looper.myLooper() != Looper.getMainLooper()) {

            thrownew IllegalStateException("ImageLoader must be invoked from the main thread.");

        }

    }

    /**

     * Creates a cache key for use with the L1 cache.

     * @param url The URL of the request.

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

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

     */

    privatestatic String getCacheKey(String url, int maxWidth, int maxHeight) {

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

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

    }

}

  • ImageLoader 源码:

首先,检查当前是不是在主线程,接着检查当前请求的图片是不是在缓存中,如果是就直接返回,否则继续。缓存中不存在的话,先创建一个ImageContainer对象,这个时候就可以通知界面显示默认图片了。在正式创建一个图片请求之前,再去检查一下这个请求是否已经存在,这个可以避免重复请求的发生。请求存在,则返回,不存在则正式创建一个图片请求对象——ImageRequest,最后返回。 

2)、ImageRequest的核心代码:

 

 publicclass ImageRequest extends Request<Bitmap> {

    /** Socket timeout in milliseconds for image requests */

    privatestaticfinalintIMAGE_TIMEOUT_MS = 1000;

 

    /** Default number of retries for image requests */

    privatestaticfinalintIMAGE_MAX_RETRIES = 2;

 

    /** Default backoff multiplier for image requests */

    privatestaticfinalfloatIMAGE_BACKOFF_MULT = 2f;

 

    privatefinal Response.Listener<Bitmap> mListener;

    privatefinal Config mDecodeConfig;

    privatefinalintmMaxWidth;

    privatefinalintmMaxHeight;

 

    /** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */

    privatestaticfinal Object sDecodeLock = new Object();

 

    /**

     * Creates a new image request, decoding to a maximum specified width and

     * height. If both width and height are zero, the image will be decoded to

     * its natural size. If one of the two is nonzero, that dimension will be

     * clamped and the other one will be set to preserve the image's aspect

     * ratio. If both width and height are nonzero, the image will be decoded to

     * be fit in the rectangle of dimensions width x height while keeping its

     * aspect ratio.

     *

     * @param url URL of the image

     * @param listener Listener to receive the decoded bitmap

     * @param maxWidth Maximum width to decode this bitmap to, or zero for none

     * @param maxHeight Maximum height to decode this bitmap to, or zero for

     *            none

     * @param decodeConfig Format to decode the bitmap to

     * @param errorListener Error listener, or null to ignore errors

     */

    public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,

            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;

    }

 

    @Override

    public Priority getPriority() {

        return Priority.LOW;

    }

 

    /**

     * Scales one side of a rectangle to fit aspect ratio.

     *

     * @param maxPrimary Maximum size of the primary dimension (i.e. width for

     *        max width), or zero to maintain aspect ratio with secondary

     *        dimension

     * @param maxSecondary Maximum size of the secondary dimension, or zero to

     *        maintain aspect ratio with primary dimension

     * @param actualPrimary Actual size of the primary dimension

     * @param actualSecondary Actual size of the secondary dimension

     */

    privatestaticint getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,

            int actualSecondary) {

        // If no dominant value at all, just return the actual.

        if (maxPrimary == 0 && maxSecondary == 0) {

            return actualPrimary;

        }

 

        // If primary is unspecified, scale primary to match secondary's scaling ratio.

        if (maxPrimary == 0) {

            double ratio = (double) maxSecondary / (double) actualSecondary;

            return (int) (actualPrimary * ratio);

        }

 

        if (maxSecondary == 0) {

            return maxPrimary;

        }

 

        double ratio = (double) actualSecondary / (double) actualPrimary;

        int resized = maxPrimary;

        if (resized * ratio > maxSecondary) {

            resized = (int) (maxSecondary / ratio);

        }

        return resized;

    }

 

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

            }

        }

    }

 

    /**

     * The real guts of parseNetworkResponse. Broken out for readability.

     */

    private Response<Bitmap> doParse(NetworkResponse response) {

        byte[] data = response.data;

        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();

        Bitmap bitmap = null;

        if (mMaxWidth == 0 && mMaxHeight == 0) {

            decodeOptions.inPreferredConfig = mDecodeConfig;

            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

        } else {

            // If we have to resize this image, first get the natural bounds.

            decodeOptions.inJustDecodeBounds = true;

            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

            int actualWidth = decodeOptions.outWidth;

            int actualHeight = decodeOptions.outHeight;

 

            // Then compute the dimensions we would ideally like to decode to.

            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,

                    actualWidth, actualHeight);

            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,

                    actualHeight, actualWidth);

 

            // Decode to the nearest power of two scaling factor.

            decodeOptions.inJustDecodeBounds = false;

            // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?

            // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;

            decodeOptions.inSampleSize =

                findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);

            Bitmap tempBitmap =

                BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

 

            // If necessary, scale down to the maximal acceptable size.

            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||

                    tempBitmap.getHeight() > desiredHeight)) {

                bitmap = Bitmap.createScaledBitmap(tempBitmap,

                        desiredWidth, desiredHeight, true);

                tempBitmap.recycle();

            } else {

                bitmap = tempBitmap;

            }

        }

 

        if (bitmap == null) {

            return Response.error(new ParseError(response));

        } else {

            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));

        }

    }

 

    @Override

    protectedvoid deliverResponse(Bitmap response) {

        mListener.onResponse(response);

    }

 

    /**

     * Returns the largest power-of-two divisor for use in downscaling a bitmap

     * that will not result in the scaling past the desired dimensions.

     *

     * @param actualWidth Actual width of the bitmap

     * @param actualHeight Actual height of the bitmap

     * @param desiredWidth Desired width of the bitmap

     * @param desiredHeight Desired height of the bitmap

     */

    // Visible for testing.

    staticint findBestSampleSize(

            int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {

        double wr = (double) actualWidth / desiredWidth;

        double hr = (double) actualHeight / desiredHeight;

        double ratio = Math.min(wr, hr);

        float n = 1.0f;

        while ((n * 2) <= ratio) {

            n *= 2;

        }

 

        return (int) n;

    }

}

  • ImageRequest的代码:

首先是构造方法,比我们之前介绍的Requst多了三个参数,int maxWidth,int maxHeight,Config decodeConfig,decodeConfig就是告诉如何去解码bitmap,默认给的是Config.RGB_565,长宽这个字面意思不用解释,最大宽高,不传默认为实际大小。接着就是

protected Response parseNetworkResponse(NetworkResponse response),

关键代码在  Response doParse(NetworkResponse response)  中,在这里我们可以看到,如果我们不设置最大宽高,就会直接解码图片,这个加载的就是实际大小的图片,占用内存就不太好控制了。如果我们设置了就会按照一定的规则去重新创建一个原始图片的缩略图,这个可以使加载到内存中的图片更好,避免OOM。

二、Volley加载网络图片的三种方式:

(一)、三种加载方式:默认情况下,volley会提供图片的文件缓存,路径在data/data/包名/cache/volley目录下。

  • 方法1:利用ImageRequest为ImageView加载网络图片,不带图片内存缓存
  • 方法2:利用ImageLoader为ImageView加载网络图片,需要自定义图片内存缓存
  • 方法3:利用ImageLoaderNetworkImageView为ImageView加载网络图片,需要自定义图片内存缓存

(二)、核心代码:

1、Java程序核心代码:

 

publicclass MainActivity extends Activity {

 

private RequestQueue mRequestQueue = null;

 

private String urlString = "http://avatar.csdn.net/6/6/D/1_lfdfhl.jpg";

 

private ImageView imageView_main;

 

private NetworkImageView networkImageView_main;

 

 

 

@Override

 

publicvoid onCreate(Bundle savedInstanceState) {

 

super.onCreate(savedInstanceState);

 

setContentView(R.layout.activity_main);

 

 

 

imageView_main = (ImageView) findViewById(R.id.imageView_main);

 

networkImageView_main = (NetworkImageView) findViewById(R.id.networkImageView_main);

 

// 初始化请求序列

 

mRequestQueue = Volley.newRequestQueue(this);

 

 

 

// Volley加载方法1:利用ImageRequest为ImageView加载网络图片,不带缓存

 

// volleyImageRequest();

 

// setTitle("ImageRequest");

 

 

 

// Volley加载方法2:利用ImageLoader为ImageView加载网络图片

 

// volleyImageLoader();

 

// setTitle("ImageLoader");

 

 

 

// Volley加载方法3:利用ImageLoader和NetworkImageView为ImageView加载网络图片

 

volleyNetworkImageView();

 

setTitle("NetworkImageView");

 

}

 

 

 

// 利用ImageRequest为ImageView加载网络图片,不带缓存

 

privatevoidvolleyImageRequest() {

 

ImageRequest mImageRequest = new ImageRequest(urlString,

 

new Response.Listener<Bitmap>() {

 

@Override

 

publicvoid onResponse(Bitmap response) {

 

imageView_main.setImageBitmap(response);

 

}

 

}, 300, 300, Config.ARGB_8888, new Response.ErrorListener() {

 

@Override

 

publicvoid onErrorResponse(VolleyError error) {

 

// 获取图片失败,加载一个默认图片

 

imageView_main.setImageResource(R.drawable.ic_launcher);

 

}

 

});

 

mRequestQueue.add(mImageRequest);

 

}

 

 

 

// 利用ImageLoader为ImageView加载网络图片

 

privatevoidvolleyImageLoader() {

 

ImageLoader mImageLoader = new ImageLoader(mRequestQueue,

 

new MyBitmapCache().getInstance());

 

ImageListener mImageListener = ImageLoader.getImageListener(

 

imageView_main, R.drawable.ic_launcher, R.drawable.ic_launcher);

 

// mImageLoader.get(mUrl, mImageListener);

 

mImageLoader.get(urlString, mImageListener, 250, 250);

 

}

 

 

 

// 利用ImageLoader和NetworkImageView为ImageView加载网络图片

 

privatevoid volleyNetworkImageView() {

 

ImageLoader mImageLoader = new ImageLoader(mRequestQueue,

 

new MyBitmapCache().getInstance());

 

networkImageView_main.setDefaultImageResId(R.drawable.ic_launcher);

 

networkImageView_main.setErrorImageResId(R.drawable.ic_launcher);

 

networkImageView_main.setImageUrl(urlString, mImageLoader);

 

}

 

 

 

staticclass MyBitmapCache implements ImageCache {

 

privatestatic MyBitmapCache imageCache = null;

 

privatestatic LruCache<String, Bitmap> lruCache = null;

 

 

 

private MyBitmapCache() {

 

int memoryCount = (int) Runtime.getRuntime().maxMemory();

 

// 获取剩余内存的8分之一作为缓存

 

int cacheSize = memoryCount / 8;

 

lruCache = new LruCache<String, Bitmap>(cacheSize) {

 

@Override

 

protectedint sizeOf(String key, Bitmap bitmap) {

 

return bitmap.getRowBytes() * bitmap.getHeight();

 

// return bitmap.getByteCount();

 

}

 

};

 

}

 

 

 

publicstatic MyBitmapCache getInstance() {

 

if (imageCache == null) {

 

imageCache = new MyBitmapCache();

 

}

 

returnimageCache;

 

}

 

 

 

@Override

 

public Bitmap getBitmap(String url) {

 

returnlruCache.get(url);

 

}

 

 

 

@Override

 

publicvoid putBitmap(String url, Bitmap bitmap) {

 

lruCache.put(url, bitmap);

 

}

 

}

 

}

2、布局核心代码:

 

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent">

 

    <ImageView

        android:id="@+id/imageView_main"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_alignParentLeft="true"

        android:layout_alignParentTop="true"

        android:src="@drawable/ic_launcher"/>

 

    <com.android.volley.toolbox.NetworkImageView

        android:id="@+id/networkImageView_main"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_below="@+id/imageView"

        android:src="@drawable/ic_launcher"/>

 

</RelativeLayout> 

三、Volley案例——ListView使用Volley,实现《糗事百科》:

(一)、步骤:

1、初始化View控件,创建请求队列

  • Volley.newRequestQueue(this);
  • ListView控件设置适配器;

2、加载数据

  • 实例化StringRequest
    • json解析,生成List集合;
    • 数据源追加数据:totalList.addAll(result);
    • 数据发生改变刷新adapter:adapter.notifyDataSetChanged();
  • 将请求对象添加进请求队列
    • requestQueue.add(request);

3、自定义适配器

  • 适配器的构造方法中实例化ImageLoader对象;
    • imageLoader = new ImageLoader(queue, MyImageCache.getInstance());
  • getView()方法中使用Volley异步加载图片;
    • mHolder.networkImageView_main_cover.setImageUrl(imgUrl ,  imageLoader);

4、自定义图片缓冲类

  • class MyImageCache implements ImageCache{}
  • 缓冲类中的构造方法中实例化LruCache对象(建议采用单例模式);

(二)、核心代码:

1、Java程序代码:

 

publicclass MainActivity extends Activity {

 

privatestaticfinal String TAG = "MainActivity";

 

private ListView listView_main;

 

private String urlString = "http://m2.qiushibaike.com/article/list/suggest?page=";

 

private List<Map<String, Object>> totalList = new ArrayList<Map<String, Object>>();

 

private MyAdapter adapter = null;

 

private RequestQueue requestQueue = null;

 

 

 

@Override

 

protectedvoid onCreate(Bundle savedInstanceState) {

 

super.onCreate(savedInstanceState);

 

setContentView(R.layout.activity_main);

 

 

 

// 初始化ListView控件

 

initView();

 

// 加载数据

 

loadData();

 

}

 

 

 

// 初始化控件

 

privatevoid initView() {

 

// 创建请求队列

 

requestQueue = Volley.newRequestQueue(this);

 

listView_main = (ListView) findViewById(R.id.listView_main);

 

adapter = new MyAdapter(totalList, requestQueue);

 

listView_main.setAdapter(adapter);

 

}

 

 

 

// 加载数据

 

privatevoid loadData() {

 

StringRequest request = new StringRequest(urlString,

 

new Response.Listener<String>() {

 

@Override

 

publicvoid onResponse(String response) {

 

// 请求成功回调函数 返回json字符串,主线程,直接可以操作ui;

 

List<Map<String, Object>> result = jsonStringToList(response);

 

if (result != null) {

 

totalList.addAll(result);

 

// 数据发生改变刷新adapter;

 

adapter.notifyDataSetChanged();

 

}

 

}

 

}, new Response.ErrorListener() {

 

@Override

 

publicvoid onErrorResponse(VolleyError error) {

 

// 请求失败,

 

}

 

});

 

// 添加进请求队列中

 

requestQueue.add(request);

 

}

 

 

 

class MyAdapter extends BaseAdapter {

 

private ImageLoader imageLoader;

 

private List<Map<String, Object>> list = null;

 

 

 

public MyAdapter(List<Map<String, Object>> result, RequestQueue queue) {

 

this.list = result;

 

imageLoader = new ImageLoader(queue, MyImageCache.getInstance());

 

}

 

 

 

@Override

 

publicint getCount() {

 

returnlist.size();

 

}

 

 

 

@Override

 

public Object getItem(int position) {

 

returnlist.get(position);

 

}

 

 

 

@Override

 

publiclong getItemId(int position) {

 

return position;

 

}

 

 

 

@Override

 

public View getView(int position, View convertView, ViewGroup parent) {

 

final ViewHolder mHolder;

 

if (convertView == null) {

 

mHolder = new ViewHolder();

 

convertView = getLayoutInflater().inflate(

 

R.layout.item_listview_main, parent, false);

 

mHolder.text_item_content = (TextView) convertView

 

.findViewById(R.id.text_item_content);

 

mHolder.text_item_conmmentscount = (TextView) convertView

 

.findViewById(R.id.text_item_conmmentscount);

 

mHolder.networkImageView_main_cover = (NetworkImageView) convertView

 

.findViewById(R.id.networkImageView_main_cover);

 

convertView.setTag(mHolder);

 

} else {

 

mHolder = (ViewHolder) convertView.getTag();

 

}

 

mHolder.text_item_content.setText(list.get(position).get("content")

 

.toString());

 

mHolder.text_item_conmmentscount.setText(list.get(position)

 

.get("comments_count").toString());

 

 

 

// 开始加载图片

 

String imgUrl = getImageUrl(list.get(position).get("image")

 

.toString());

 

// Log.i(TAG, "==imgUrl:" + imgUrl);

 

if (imgUrl.equals("")) {

 

Log.i(TAG, "==imgUrl:null");

 

mHolder.networkImageView_main_cover

 

.setImageResource(R.drawable.icon02);

 

} else {

 

// 使用volley框架异步加载图片开始

 

// 设置图片未下载完成时候的默认图片

 

mHolder.networkImageView_main_cover

 

.setDefaultImageResId(R.drawable.icon01);

 

// 设置图片下载错误时的图片

 

mHolder.networkImageView_main_cover

 

.setErrorImageResId(R.drawable.ic_launcher);

 

mHolder.networkImageView_main_cover.setImageUrl(imgUrl,

 

imageLoader);

 

}

 

return convertView;

 

}

 

 

 

privateclass ViewHolder {

 

public NetworkImageView networkImageView_main_cover;

 

public TextView text_item_content;

 

public TextView text_item_conmmentscount;

 

}

 

}

 

 

 

// 根据json解析获取到的图片名称,生成该图片的url地址

 

private String getImageUrl(String imageName) {

 

String prefixUrlString = "http://pic.qiushibaike.com/system/pictures/";

 

Pattern pattern = Pattern.compile("(\\d{4})\\d+");

 

Matcher matcher = pattern.matcher(imageName);

 

StringBuffer url = new StringBuffer();

 

if (matcher.find()) {

 

url.append(prefixUrlString).append(matcher.group(1)).append('/')

 

.append(matcher.group(0)).append("/small/")

 

.append(imageName);

 

return url.toString();

 

} else {

 

return"";

 

}

 

}

 

 

 

// json解析,生成List集合

 

private List<Map<String, Object>> jsonStringToList(String jsonString) {

 

List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

 

try {

 

JSONObject jsonObject = new JSONObject(jsonString);

 

JSONArray jsonArray_items = jsonObject.getJSONArray("items");

 

for (int i = 0; i < jsonArray_items.length(); i++) {

 

JSONObject jonsObject_item = jsonArray_items.getJSONObject(i);

 

 

 

Map<String, Object> map = new HashMap<String, Object>();

 

map.put("image", jonsObject_item.getString("image"));

 

map.put("content", jonsObject_item.getString("content"));

 

map.put("comments_count",

 

jonsObject_item.getString("comments_count"));

 

list.add(map);

 

}

 

return list;

 

} catch (JSONException e) {

 

e.printStackTrace();

 

}

 

returnnull;

 

}

 

 

 

// 图片缓存类,使用单例模式

 

staticclass MyImageCache implements ImageCache {

 

privatestatic MyImageCache imageCache = null;

 

privatestatic LruCache<String, Bitmap> lruCache = null;

 

 

 

private MyImageCache() {

 

int memoryCount = (int) Runtime.getRuntime().maxMemory();

 

// 获取剩余内存的8分之一作为缓存

 

int cacheSize = memoryCount / 8;

 

lruCache = new LruCache<String, Bitmap>(cacheSize) {

 

@Override

 

protectedint sizeOf(String key, Bitmap bitmap) {

 

return bitmap.getRowBytes() * bitmap.getHeight();

 

// return bitmap.getByteCount();

 

}

 

};

 

}

 

 

 

publicstatic MyImageCache getInstance() {

 

if (imageCache == null) {

 

imageCache = new MyImageCache();

 

}

 

returnimageCache;

 

}

 

 

 

@Override

 

public Bitmap getBitmap(String url) {

 

returnlruCache.get(url);

 

}

 

 

 

@Override

 

publicvoid putBitmap(String url, Bitmap bitmap) {

 

lruCache.put(url, bitmap);

 

}

 

}

 

}

2、item的布局代码:

 

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

 

    android:layout_width="fill_parent"

 

    android:layout_height="fill_parent"

 

    android:orientation="vertical">

 

   

 

   <com.android.volley.toolbox.NetworkImageView

 

        android:id="@+id/networkImageView_main_cover"

 

        android:layout_width="wrap_content"

 

        android:layout_height="wrap_content"/>

 

 

 

    <TextView

 

        android:id="@+id/text_item_content"

 

        android:layout_width="match_parent"

 

        android:layout_height="wrap_content"

 

        android:textSize="12sp"

 

        android:textColor="#69f"

 

        />

 

<TextView

 

        android:id="@+id/text_item_conmmentscount"

 

        android:layout_width="match_parent"

 

        android:layout_height="wrap_content"

 

        android:layout_marginTop="5dp"

 

        android:textSize="12sp"

 

        android:textColor="#999"

 

        />

 

</LinearLayout>

四、GridView实现照片墙:

五、实现瀑布流照片墙:

(一)、原理:

        实现原理:瀑布流的布局方式虽然看起来好像排列的很随意,其实它是有很科学的排列规则的。整个界面会根据屏幕的宽度划分成等宽的若干列,由于手机的屏幕不是很大,这里我们就分成三列。每当需要添加一张图片时,会将这张图片的宽度压缩成和列一样宽,再按照同样的压缩比例对图片的高度进行压缩,然后在这三列中找出当前高度最小的一列,将图片添加到这一列中。之后每当需要添加一张新图片时,都去重复上面的操作,就会形成瀑布流格局的照片墙,示意图如下所示。

        

【备注:】瀑布流的布局非常简单,只需要使用三个LinearLayout平分整个屏幕宽度,然后动态地addView()进去就好。但是如果不断往LinearLayout里添加图片,程序很快就会OOM。因此我们还需要一个合理的方案来对图片资源进行释放,常使用LruCache算法。

【总结:】Volley: 

(一)、作用:

实现网络访问、异步加载图片

(二)、使用步骤:

1、volley.jar放置到libs

2、实例化RequestQueue:

RequestQueue requestQueue = Volley.newRequestQueue(this);

3、获取json字符串:

1)、StringRequest request = new StringRequest(url , listener , errorListener);

2)、requestQueue.add(request);

3)、做法2:JsonObjectRequest request = new JsonObjectRequst(url , null , listener , errorListener);

4、异步加载图片:

1)、做法1:ImageRequest:

ImageRequest imageRequest = new ImageRequest(url , listener ,maxWidth  ,maxHeight , config,  errorListener);

requestQueue.add(imageRequest);

2)、做法2:ImageLoader:

ImageLoader imageLoader = new ImageLoader(requestQueue , BitmapCache);

imageLoader.get(url , ImageLoader.getImageListener(图片显示的控件id ,默认的图片资源id, 加载失败显示的图片资源id ) , maxWidth  ,maxHeight);

3)、做法3:NetworkImageView + ImageLoader:

a.更改布局NetworkImageView;

b.ImageLoader imageLoader = new ImageLoader(requestQueue , BitmapCache);

c.imageView.setImageUrl(url , imageLoader);

imageView.setDefaultImageResId(资源id);

imageView.setErrorImageResId(资源id);

猜你喜欢

转载自blog.csdn.net/dubo_csdn/article/details/86014841