上篇文章咱們講到了如何用volley進行簡單的網絡請求,咱們能夠很容易的接受到string、JsonObjec類型的返回結果,以前的例子僅僅是一次請求,這裏須要說明volley自己就是適合高併發的,因此它能夠運行你用volley在短期內進行屢次請求,而且不用去手動管理線程數。僅僅是請求文字過於基礎了,本篇將講述如何用volley從網絡下載圖片。
html
1、用ImageRequest來請求圖片android
ImageRequest是一個圖片請求對象,它繼承自Request<Bitmap>,因此請求獲得的結果是一個bitmap。express
1.1 使用步驟
apache
ImageRequest仍舊是一個request對象,因此使用方式和StringRequest、JsonObjectRequest、JsonArrayRequest十分類似。緩存
步驟:網絡
第一步、第三步咱們在上篇文章中已經作好了,若是不清楚的話能夠去上一篇文章查看。併發
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;
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 */
先來完整解釋下注釋的意思:
舉個例子:
個人圖片本來像素是: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; } }
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; } }