Volley徹底解析

從前在學校用的最多的網絡請求框架就是AsyncHttpClient,用久了發現這樣一個問題,就是代碼複用太難,基本上作不到,因此有一段時間又迴歸了HttpURLConnection和HttpClient,再後來又學習了OKHttp的使用,雖然這幾種網絡請求各有各的優點,可是原理基本上都是同樣的,在android6.0中Google已經不提供HttpClient的API了,因此從長遠的角度來考慮,推薦你們多用OKHttp,關於OKHttp的使用能夠參見OKHttp的簡單使用。除了上面說的這幾種通訊方式,Google在2013年(好早呀尷尬)的I/O大會上還推出了一個網絡請求框架Volley,這和AsyncHttpClient的使用很是像,以前一直沒有總結過Volley的使用,週末有時間總結一下,與你們分享。java

Volley適用於交互頻繁可是數據量小的網絡請求,好比咱們在上一篇博客中介紹的新聞列表,這種狀況下使用Volley就是很是合適的,可是對於一些數據量大的網絡請求,好比下載,Volley就顯得略有力不從心。android

Volley是一個開源項目,咱們能夠在GitHub上得到它的源代碼,地址https://github.com/mcxiaoke/android-volley,拿到以後咱們能夠將之打包成jar包使用,也能夠直接將源碼拷貝到咱們的項目中使用,我的推薦第二種方式,這樣發生錯誤的時候方便咱們調試,同時也有利於咱們修改源碼,定製咱們本身的Volley。若是要拷貝源碼,咱們只須要將「android-volley-master\android-volley-master\src\main\java」這個文件夾下的com包拷貝到咱們的項目中便可。git

1.請求字符數據

Volley的使用,咱們要先得到一個隊列,咱們的全部請求將會放在這個隊列中一個一個執行:
RequestQueue mQueue = Volley.newRequestQueue(this);
得到一個請求隊列只須要一個參數,就是Context,這裏由於在MainActivity發起請求,因此直接用this。字符型數據的請求,咱們使用StringRequest:
		StringRequest sr = new StringRequest("http://www.baidu.com",
				new Response.Listener<String>() {

					@Override
					public void onResponse(String response) {
						Log.i("lenve", response);
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {

					}
				});

StringRequest一共須要三個參數,第一個是咱們要請求的http地址,第二個是數據調用成功的回調函數,第三個參數是數據調用失敗的回調函數。咱們能夠在回調函數中直接更新UI,Volley的這個特色和AsyncHttpClient仍是很是類似的,關於這裏邊的原理咱們後邊有時間能夠詳細介紹。得到一個StringRequest對象的實例以後,咱們把它添加到隊列之中,這樣就能夠請求到網絡數據了:
mQueue.add(sr);
嗯,就是這麼簡單。那咱們不由有疑問了,剛纔這個請求時get請求仍是post請求?咱們如何本身設置網絡請求方式?咱們看一下這個構造方法的源碼:

    /**
     * Creates a new GET request.
     *
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

原來這個構造方法調用了另一個構造方法,而且第一個參數傳遞了Method.GET,也就是說上面的請求實際上是一個GET請求,那麼咱們若是要使用POST請求就直接經過下面這個構造方法來實現就能夠了:
    /**
     * Creates a new request with the given method.
     *
     * @param method the request {@link Method} to use
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

那麼使用了POST請求以後咱們該怎麼樣來傳遞參數呢?咱們看到StringRequest中並無相似的方法來讓咱們完成工做,可是StringRequest繼承自Request,咱們看看Request中有沒有,很幸運,咱們找到了:
    /**
     * Returns a Map of parameters to be used for a POST or PUT request.  Can throw
     * {@link AuthFailureError} as authentication may be required to provide these values.
     *
     * <p>Note that you can directly override {@link #getBody()} for custom data.</p>
     *
     * @throws AuthFailureError in the event of auth failure
     */
    protected Map<String, String> getParams() throws AuthFailureError {
        return null;
    }
看註釋咱們大概就明白這個方法是幹什麼的了,它將POST請求或者PUT請求須要的參數封裝成一個Map對象,那麼咱們若是在POST請求中須要傳參的話,直接重寫這個方法就能夠了,代碼以下:
		StringRequest sr = new StringRequest("http://www.baidu.com",
				new Response.Listener<String>() {

					@Override
					public void onResponse(String response) {
						Log.i("lenve", response);
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {

					}
				}) {
			/**
			 * 重寫getParams(),能夠本身組裝post要提交的參數
			 */
			@Override
			protected Map<String, String> getParams() throws AuthFailureError {
				Map<String, String> map = new HashMap<String, String>();
				map.put("params1", "value1");
				map.put("params1", "value1");
				return map;
			}
		};
好了,請求字符數據就是這麼簡單。

2.請求JSON數據

關於json數據的請求,Volley已經給咱們提供了相關的類了:
		JsonObjectRequest jsonReq = new JsonObjectRequest(HTTPURL,
				new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						try {
							JSONObject jo = response.getJSONObject("paramz");
							JSONArray ja = jo.getJSONArray("feeds");
							for (int i = 0; i < ja.length(); i++) {
								JSONObject jo1 = ja.getJSONObject(i)
										.getJSONObject("data");
								Log.i("lenve", jo1.getString("subject"));
							}
						} catch (JSONException e) {
							e.printStackTrace();
						}
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {

					}
				});
		mQueue.add(jsonReq);

咱們看看打印出來的結果:



OK,沒問題,就是這麼簡單,以此類推,你應該也就會使用JSONArray了。


3.使用ImageLoader加載圖片

Volley提供的另一個很是好用的工具就是ImageLoader,這個網絡請求圖片的工具類,帶給咱們許多方便,首先它會自動幫助咱們把圖片緩存在本地,若是本地有圖片,它就不會從網絡獲取圖片,若是本地沒有緩存圖片,它就會從網絡獲取圖片,同時若是本地緩存的數據超過咱們設置的最大緩存界限,它會自動移除咱們在最近用的比較少的圖片。咱們看一下代碼:
ImageLoader il = new ImageLoader(mQueue, new BitmapCache());
		ImageListener listener = ImageLoader.getImageListener(iv,
				R.drawable.ic_launcher, R.drawable.ic_launcher);
		il.get(IMAGEURL, listener);

先實例化一個ImageLoader,實例化ImageLoader須要兩個參數,第一個就是咱們前文說的網絡請求隊列,第二個參數就是一個圖片緩存類,這個類要咱們本身來實現,其實只須要實現ImageCache接口就能夠了,裏面具體的緩存邏輯由咱們本身定義,咱們使用Google提供的LruCache來實現圖片的緩存。而後就是咱們須要一個listener,得到這個listener須要三個參數,第一個是咱們要加載網絡圖片的ImageView,第二個參數是默認圖片,第三個參數是加載失敗時候顯示的圖片。最後經過get方法來實現圖片的加載,經過源碼追蹤咱們發現這個get方法會來到這樣一個方法中:
    public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight, ScaleType scaleType) {

        // only fulfill requests that were initiated from the main thread.
        throwIfNotOnMainThread();

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

        // 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<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType,
                cacheKey);

        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }

咱們看到在這個方法中會先判斷本地緩存中是否有咱們須要的圖片,若是有的話直接從本地加載,沒有才會請求網絡數據,這正是使用ImageLoader的方便之處。

4.使用NetworkImageView加載網絡圖片

NetworkImageView和前面說的ImageLoader其實很是相像,不一樣的是若是使用NetworkImageView的話,咱們的控件就不是ImageView了,而是NetworkImageView,咱們看看佈局文件:
    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/iv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

在MainActivity中拿到它:
NetworkImageView niv = (NetworkImageView) this.findViewById(R.id.iv2);

設置網絡請求參數:
niv.setDefaultImageResId(R.drawable.ic_launcher);
		ImageLoader il = new ImageLoader(mQueue, new BitmapCache());
		niv.setImageUrl(IMAGEURL, il);

首先設置默認圖片,而後是一個ImageLoader,這個ImageLoader和前文說的如出一轍,最後就是設置請求圖片的URL地址和一個ImageLoader,咱們基本能夠判斷NetworkImageView也會自動幫咱們處理圖片的緩存問題。事實上的確如此,咱們經過追蹤setImageUrl這個方法的源碼,發現它最終也會執行到咱們在上面貼出來的那個方法中:
public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight, ScaleType scaleType) {
    ....
}
沒錯,NetworkImageView和ImageLoader最終都會到達這個方法,因此說這兩個東東其實很是像,那麼在實際開發中究竟用哪一個要視狀況而定。

5.ImageRequest的使用

ImageRequest也是用來加載網絡圖片的,用法與請求字符串數據和請求json數據差很少:
		ImageRequest ir = new ImageRequest(IMAGEURL, new Listener<Bitmap>() {

			@Override
			public void onResponse(Bitmap response) {
				iv3.setImageBitmap(response);
			}
		}, 200, 200, ScaleType.CENTER, Bitmap.Config.ARGB_8888,
				new ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {

					}
				});
		mQueue.add(ir);

ImageRequest一共須要七個參數,第一個是要請求的圖片的IP地址,第二個請求成功的回調函數,第三個參數和第四個參數表示容許圖片的最大寬高,若是圖片的大小超出了設置會自動縮小,縮小方法依照第五個參數的設定,第三個和第四個參數若是設置爲0則不會對圖片的大小作任何處理(原圖顯示),第六個參數表示繪圖的色彩模式,第七個參數是請求失敗時的回調函數。這個和前面兩種加載圖片的方式比較起來仍是稍微有點麻煩。

6.定製本身的Request

上面介紹了Volley能夠實現的幾種請求,可是畢竟仍是比較有限,而咱們在項目中遇到的狀況多是各類各樣的,好比服務端若是傳給咱們的是一個XML數據,那麼咱們該怎樣使用Volley?Volley的強大之處除了上文咱們說的以外,還在於它是開源的,咱們能夠根據本身的須要來定製Volley。那麼咱們就看看怎麼定製XMLRequest,在定製已以前咱們先看看JSONRequest是怎麼實現的?
public class JsonObjectRequest extends JsonRequest<JSONObject> {

    /**
     * Creates a new request.
     * @param method the HTTP method to use
     * @param url URL to fetch the JSON from
     * @param requestBody A {@link String} to post with the request. Null is allowed and
     *   indicates no parameters will be posted along with request.
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonObjectRequest(int method, String url, String requestBody,
                             Listener<JSONObject> listener, ErrorListener errorListener) {
        super(method, url, requestBody, listener,
                errorListener);
    }

    /**
     * Creates a new request.
     * @param url URL to fetch the JSON from
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonObjectRequest(String url, Listener<JSONObject> listener, ErrorListener errorListener) {
        super(Method.GET, url, null, listener, errorListener);
    }

    /**
     * Creates a new request.
     * @param method the HTTP method to use
     * @param url URL to fetch the JSON from
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonObjectRequest(int method, String url, Listener<JSONObject> listener, ErrorListener errorListener) {
        super(method, url, null, listener, errorListener);
    }

    /**
     * Creates a new request.
     * @param method the HTTP method to use
     * @param url URL to fetch the JSON from
     * @param jsonRequest A {@link JSONObject} to post with the request. Null is allowed and
     *   indicates no parameters will be posted along with request.
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonObjectRequest(int method, String url, JSONObject jsonRequest,
            Listener<JSONObject> listener, ErrorListener errorListener) {
        super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener,
                errorListener);
    }

    /**
     * Constructor which defaults to <code>GET</code> if <code>jsonRequest</code> is
     * <code>null</code>, <code>POST</code> otherwise.
     *
     * @see #JsonObjectRequest(int, String, JSONObject, Listener, ErrorListener)
     */
    public JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener,
            ErrorListener errorListener) {
        this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest,
                listener, errorListener);
    }

    @Override
    protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
        try {
            String jsonString = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
            return Response.success(new JSONObject(jsonString),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JSONException je) {
            return Response.error(new ParseError(je));
        }
    }
}
哈,原來這麼簡單,光是構造方法就有五個,不過構造方法都很簡單,咱們就很少說,核心的功能在parseNetworkResponse方法中,這裏調用成功的時候返回一個Response泛型,泛型裏邊的東西是一個JSONObject,其實也很簡單,咱們若是要處理XML,那麼直接在重寫 parseNetworkResponse方法,在調用成功的時候直接返回一個Response泛型,這個泛型中是一個XmlPullParser對象,哈哈,很簡單吧,同時,結合StringRequest的實現方式,咱們實現了XMLRequest的代碼:
public class XMLRequest extends Request<XmlPullParser> {

	private Listener<XmlPullParser> mListener;

	public XMLRequest(String url, Listener<XmlPullParser> mListener,
			ErrorListener listener) {
		this(Method.GET, url, mListener, listener);
	}

	public XMLRequest(int method, String url,
			Listener<XmlPullParser> mListener, ErrorListener listener) {
		super(method, url, listener);
		this.mListener = mListener;
	}

	@Override
	protected Response<XmlPullParser> parseNetworkResponse(
			NetworkResponse response) {
		String parsed;
		try {
			parsed = new String(response.data,
					HttpHeaderParser.parseCharset(response.headers));
			XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
			XmlPullParser parser = factory.newPullParser();
			parser.setInput(new StringReader(parsed));
			return Response.success(parser,
					HttpHeaderParser.parseCacheHeaders(response));
		} catch (UnsupportedEncodingException e) {
			parsed = new String(response.data);
		} catch (XmlPullParserException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	protected void deliverResponse(XmlPullParser response) {
		if (mListener != null) {
			mListener.onResponse(response);
		}
	}

	@Override
	protected void onFinish() {
		super.onFinish();
		mListener = null;
	}

}

就是這麼簡單,在parseNetworkResponse方法中咱們先拿到一個xml的字符串,再將這個字符串轉爲一個XmlPullParser對象,最後返回一個Response泛型,這個泛型中是一個XmlPullParser對象。而後咱們就可使用這個XMLRequest了:
		XMLRequest xr = new XMLRequest(XMLURL,
				new Response.Listener<XmlPullParser>() {

					@Override
					public void onResponse(XmlPullParser parser) {
						try {
							int eventType = parser.getEventType();
							while (eventType != XmlPullParser.END_DOCUMENT) {
								switch (eventType) {
								case XmlPullParser.START_DOCUMENT:
									break;
								case XmlPullParser.START_TAG:
									String tagName = parser.getName();
									if ("city".equals(tagName)) {
										Log.i("lenve",
												new String(parser.nextText()));
									}
									break;
								case XmlPullParser.END_TAG:
									break;
								}
								eventType = parser.next();
							}
						} catch (XmlPullParserException e) {
							e.printStackTrace();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {

					}
				});
		mQueue.add(xr);


用法和JSONRequest的用法一致。會自定義XMLRequest,那麼照貓畫虎也能夠自定義一個GsonRequest,各類各樣的數據類型咱們均可以本身定製了。
好了,關於Volley的使用咱們就介紹到這裏,有問題歡迎留言討論。

相關文章
相關標籤/搜索