詳細解讀Volley(二)—— ImageRequest & Request簡介

上篇文章咱們講到了如何用volley進行簡單的網絡請求,咱們能夠很容易的接受到string、JsonObjec類型的返回結果,以前的例子僅僅是一次請求,這裏須要說明volley自己就是適合高併發的,因此它能夠運行你用volley在短期內進行屢次請求,而且不用去手動管理線程數。僅僅是請求文字過於基礎了,本篇將講述如何用volley從網絡下載圖片。
html

 

1、用ImageRequest來請求圖片android

ImageRequest是一個圖片請求對象,它繼承自Request<Bitmap>,因此請求獲得的結果是一個bitmap。express

1.1 使用步驟
apache

ImageRequest仍舊是一個request對象,因此使用方式和StringRequest、JsonObjectRequest、JsonArrayRequest十分類似。緩存

步驟:網絡

  1. 創建一個RequestQueue對象
  2. 創建一個ImageRequest對象
  3. 將ImageRequest添加到RequestQueue中

第一步、第三步咱們在上篇文章中已經作好了,若是不清楚的話能夠去上一篇文章查看。併發

 

1.2 分析構造函數app

源碼中的構造函數是這樣定義的:less

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

默認的請求方式是GET,初始化方法須要傳入:圖片的url,一個響應結果監聽器,圖片的最大寬度,圖片的最大高度,圖片的顏色屬性,出錯響應的監聽器。dom

說明:圖片的顏色屬性,Bitmap.Config下的幾個常量均可以在這裏使用,其中ARGB_8888能夠展現最好的顏色屬性,每一個圖片像素佔據4個字節的大小,而RGB_565則表示每一個圖片像素佔據2個字節大小

    /** Socket timeout in milliseconds for image requests */
    private static final int IMAGE_TIMEOUT_MS = 1000;

    /** Default number of retries for image requests */
    private static final int IMAGE_MAX_RETRIES = 2;

    /** Default backoff multiplier for image requests */
    private static final float IMAGE_BACKOFF_MULT = 2f;
  • 設定超時時間:1000ms;
  • 最大的請求次數:2次;
  • 發生衝突時的重傳延遲增長數:2f(這個應該和TCP協議有關,衝突時須要退避一段時間,而後再次請求);

 

1.3 解釋maxWidth,maxHeight參數

註釋中詳細說明了圖片寬高的意義和做用,爲了便於理解我再詳細說一下。

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

先來完整解釋下注釋的意思:

  • 創建一個請求對象,按照最大寬高進行解碼 。
  • 若是設定的寬和高都是0,那麼下載到的圖片將會按照實際的大小進行解碼,也就是不壓縮。
  • 若是寬和高中的一個或兩個值不爲0,那麼圖片的寬/高(取決於你設定了寬仍是高)會壓縮至設定好的值,而另外一個寬/高將會按原始比例改變。
  • 若是寬和高都不是0,那麼獲得的圖片將會「按比例」解碼到你設定的寬高,也就是說最終獲得的圖片大小不必定是你最初設定的大小。

舉個例子:

個人圖片本來像素是:850x1200.

maxWidth = 0,maxHeight = 0時,最終獲得的bitmap的寬高是850x1200

當maxWidth = 0,maxHeight = 600時,獲得的bitmap是425x600.這就說明它會按照一個不爲0的邊的值,將圖片進行等比縮放。

當maxWidth = 100,maxHeight = 600時,咱們獲得的bitmap居然是100x141,是按照100進行等比縮小後的圖片,而不是100x600.

要弄清這個問題,咱們還得看源碼,源碼中解析響應結果的方法叫作doParse(…)

    /**
     * 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) {
       // 若是寬高都是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.
      // 若是咱們已經重設了image的尺寸(寬高中有一個或兩個不爲0),那麼先獲得原始的大小

            decodeOptions.inJustDecodeBounds = true; // 設置先不獲得bitmap,僅僅獲取bitmap的參數。
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); // 第一次解碼,主要得到的是bitmap的實際寬、高
            int actualWidth = decodeOptions.outWidth; // 獲得bitmap的寬
            int actualHeight = decodeOptions.outHeight; // 獲得bitmap的高

            // 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.
            // 把圖片解碼到最接近2的冪次方的大小
            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.
            // 若是有必要的話,把獲得的bitmap的最大邊進行壓縮來適應尺寸
            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                    tempBitmap.getHeight() > desiredHeight)) {
                // 經過createScaledBitmap來壓縮到目標尺寸
                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));
        }
    }
    
    /**
     * 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.
    static int findBestSampleSize(
            int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
                // 計算inSampleSize的方法,詳細知識自行百度吧。最終原圖會被壓縮爲inSampleSize分之一
                // inSampleSize的值計算出來都是2的冪次方
        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;
    }

此時咱們發現重要的方法是getResizedDimension,它最終肯定了圖片的最終尺寸。

    /**
     * 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
     */
    private static int 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;
    }

在咱們目標寬、高都不爲0時會調用下面的代碼段:

double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
        if (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }

它會計算一個ratio(比值),這就是爲啥它會按比例縮小的緣由。

 

1.4 初始化對象並使用

        ImageRequest imageRequest = new ImageRequest(
                "http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", 
                new ResponseListener(), 
                0, // 圖片的寬度,若是是0,就不會進行壓縮,不然會根據數值進行壓縮
                0, // 圖片的高度,若是是0,就不進行壓縮,不然會壓縮
                Config.ARGB_8888, // 圖片的顏色屬性
                new ResponseErrorListener());

監聽器:

    private class ResponseListener implements Response.Listener<Bitmap> {

        @Override
        public void onResponse(Bitmap response) {
           //  Log.d("TAG", "-------------\n" + response.toString());
            iv.setImageBitmap(response);
        }
    }

    private class ResponseErrorListener implements Response.ErrorListener {

        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e("TAG", error.getMessage(), error);
        }
    }

最後將其添加到請求隊列便可:

 mQueue.add(imageRequest);

 

1.5   題外話

這樣咱們就用volley得到了網絡圖片,代碼也十分簡單。你可能會說,有沒有其餘的,更好的方式來獲取圖片呢?固然有的,好比volley還提供了ImageLoader、NetworkImageView這樣的對象,它們能夠更加方便的獲取圖片。值得一提的是這兩個對象的內部都是使用了ImageRequest進行操做的,也就是說imageRequest是本質,這也就是爲啥我專門寫一篇來分析ImageRequest的緣由。

說話要言之有理,因此貼上ImageLoader、NetworkImageView源碼中部分片斷來證實其內部確實是用了ImageRequest。

 

ImageLoader的源碼片斷:

    public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight) {
        // ………// The request is not already in flight. Send the new request to the network and
        // track it.
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, cacheKey);
        newRequest.setShouldCache(mShouldCache);
        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }
    protected Request<Bitmap> makeImageRequest(String requestUrl, int maxWidth, int maxHeight, final String cacheKey) {
        return 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);
            }
        });
    }

 在ImageLoader重要的get()方法中,創建了一個newRequest對象,並將其放入請求隊列中。這裏的newRequest是經過makeImageRequest()來產生的,而makeImageRequest()實際是返回了一個ImageRequest對象。因此用到了ImageRequest對象。

 

NetworkImageView的源碼片斷:

    public void setImageUrl(String url, ImageLoader imageLoader) {
        mUrl = url;
        mImageLoader = imageLoader;
        // The URL has potentially changed. See if we need to load it.
        loadImageIfNecessary(false);
    }

它自己就調用的是ImageLoader對象,因此天然也是用到了ImageRequest。

 

2、Request簡介

2.1 前言

Request是Volley中最最核心的類,以前講到的對象都是它的子類。從字面意思看,這個對象是用來執行請求的,但經過以前的使用咱們發現,它還作了不少別的事情。先貼一個Request的子類。

        ImageRequest imageRequest = new ImageRequest(
                "http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", 
                new ResponseListener(), 
                0, // 圖片的寬度,若是是0,就不會進行壓縮,不然會根據數值進行壓縮
                0, // 圖片的高度,若是是0,就不進行壓縮,不然會壓縮
                Config.ARGB_8888, // 圖片的顏色屬性
                new ResponseErrorListener());

從中咱們能夠發現這個ImageRequest中傳入了請求的url,畢竟是request嘛,請求的url是必須的,但咱們還發現這個請求對象還處理了兩個監聽器,這就說明它不只僅作了請求,同時對於響應的結果也作了分發處理。

 

2.2 部分API

getCacheKey()

Returns the cache key for this request. By default, this is the URL.

返回這個請求對象中緩存對象的key,默認返回的是請求的URL

 

getBodyContentType()

Returns the content type of the POST or PUT body.

返回POST或PUT請求內容的類型,我測試的結果是:application/x-www-form-urlencoded; charset=UTF-8

從源碼就能看出,默認的編碼方式是UTF-8:

/**
     * Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}.
     */
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
    /**
     * Returns the content type of the POST or PUT body.
     */
    public String getBodyContentType() {
        return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
    }

 

getSequence()

Returns the sequence number of this request.

返回請求的序列數

 

getUrl()

Returns the URL of this request.

返回請求的URL

 

setShouldCache(boolean bl)

Set whether or not responses to this request should be cached.

設置這個請求是否有緩存,這個緩存是磁盤緩存,和內存緩存沒什麼事情,默認是true,也就是說若是你不設置爲false,這個請求就會在磁盤中進行緩存。其實,以前講的的StringRequest,JsonRequest,ImageRequest獲得的數據都會被緩存,不管是Json數據,仍是圖片都會自動的緩存起來。然而,一旦你設置setShouldCache(false),這些數據就不會被緩存了。

 

getBody()

Returns the raw POST or PUT body to be sent.

返回POST或PUT的請求體

 

deliverError()

分發錯誤信息,這個就是調用監聽器的方法,貼源碼就明白了。

    /**
     * Delivers error message to the ErrorListener that the Request was
     * initialized with.
     *
     * @param error Error details
     */
    public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }

 

setRetryPolicy(RetryPolicy retryPolicy)

對一個request的從新請求策略的設置,不一樣的項目是否須要從新請求,從新請求幾回,請求超時的時間,這些就在這設置到裏面。

  /**
     * Sets the retry policy for this request.
     *
     * @return This Request object to allow for chaining.
     */
    public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
        mRetryPolicy = retryPolicy;
        return this;
    }

從上面的源碼能夠看出,這裏須要傳入一個RetryPlicy的子類,就是從新請求策略的子類,Volley會在構造Request時傳一個默認的對象,叫作DefaultRetryPolicy。

    /**
     * Creates a new request with the given method (one of the values from {@link Method}),
     * URL, and error listener.  Note that the normal response listener is not provided here as
     * delivery of responses is provided by subclasses, who have a better idea of how to deliver
     * an already-parsed response.
     */
    public Request(int method, String url, Response.ErrorListener listener) {
        mMethod = method;
        mUrl = url;
        mErrorListener = listener;
        setRetryPolicy(new DefaultRetryPolicy());

        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

若是你對於網絡請求有具體的要求,能夠實現RetryPolicy接口,進行自由的配置。下面貼一下DefaultRetryPolicy源碼,方便參考。

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.volley;

/**
 * Default retry policy for requests.
 */
public class DefaultRetryPolicy implements RetryPolicy {
    /** The current timeout in milliseconds. */
    private int mCurrentTimeoutMs;

    /** The current retry count. */
    private int mCurrentRetryCount;

    /** The maximum number of attempts. */
    private final int mMaxNumRetries;

    /** The backoff multiplier for the policy. */
    private final float mBackoffMultiplier;

    /** The default socket timeout in milliseconds */
    public static final int DEFAULT_TIMEOUT_MS = 2500;

    /** The default number of retries */
    public static final int DEFAULT_MAX_RETRIES = 1;

    /** The default backoff multiplier */
    public static final float DEFAULT_BACKOFF_MULT = 1f;

    /**
     * Constructs a new retry policy using the default timeouts.
     */
    public DefaultRetryPolicy() {
        this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
    }

    /**
     * Constructs a new retry policy.
     * @param initialTimeoutMs The initial timeout for the policy.
     * @param maxNumRetries The maximum number of retries.
     * @param backoffMultiplier Backoff multiplier for the policy.
     */
    public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
        mCurrentTimeoutMs = initialTimeoutMs;
        mMaxNumRetries = maxNumRetries;
        mBackoffMultiplier = backoffMultiplier;
    }

    /**
     * Returns the current timeout.
     */
    @Override
    public int getCurrentTimeout() {
        return mCurrentTimeoutMs;
    }

    /**
     * Returns the current retry count.
     */
    @Override
    public int getCurrentRetryCount() {
        return mCurrentRetryCount;
    }

    /**
     * Returns the backoff multiplier for the policy.
     */
    public float getBackoffMultiplier() {
        return mBackoffMultiplier;
    }

    /**
     * Prepares for the next retry by applying a backoff to the timeout.
     * @param error The error code of the last attempt.
     */
    @Override
    public void retry(VolleyError error) throws VolleyError {
        mCurrentRetryCount++;
        mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
        if (!hasAttemptRemaining()) {
            throw error;
        }
    }

    /**
     * Returns true if this policy has attempts remaining, false otherwise.
     */
    protected boolean hasAttemptRemaining() {
        return mCurrentRetryCount <= mMaxNumRetries;
    }
}
View Code

 

2.3 產生Request對象

雖然咱們在代碼中都會初始化一個Request對象,可是咱們要在把他添加到響應隊列中後才能獲得它的完總體。

public <T> Request<T> add(Request<T> request) {

舉例:

com.android.volley.Request<Bitmap> bitmapRequest = mQueue.add(imageRequest);

說明:若是你要設定這個request是不須要進行磁盤緩存的,那麼請在把它添加到響應隊列以前就進行設置,不然會獲得不想要的效果。緣由:源碼在添加隊列時會判斷是否須要緩存。

    /**
     * Adds a Request to the dispatch queue.
     * @param request The request to service
     * @return The passed-in request
     */
    public <T> Request<T> add(Request<T> request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) { mNetworkQueue.add(request); return request; // 若是不須要緩存,直接返回request對象,不會執行下面的代碼 }

        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }
相關文章
相關標籤/搜索