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的使用咱们就介绍到这里,有问题欢迎留言讨论。

相关文章
相关标签/搜索