zoukankan      html  css  js  c++  java
  • Volley完全解析

    从前在学校用的最多的网络请求框架就是AsyncHttpClient,用久了发现这样一个问题,就是代码复用太难,基本上做不到,所以有一段时间又回归了HttpURLConnection和HttpClient,再后来又学习了OKHttp的使用,虽然这几种网络请求各有各的优势,但是原理基本上都是一样的,在android6.0中Google已经不提供HttpClient的API了,所以从长远的角度来考虑,推荐大家多用OKHttp,关于OKHttp的使用可以参见OKHttp的简单使用。除了上面说的这几种通信方式,Google在2013年(好早呀尴尬)的I/O大会上还推出了一个网络请求框架Volley,这和AsyncHttpClient的使用非常像,之前一直没有总结过Volley的使用,周末有时间总结一下,与大家分享。

    Volley适用于交互频繁但是数据量小的网络请求,比如我们在上一篇博客中介绍的新闻列表,这种情况下使用Volley就是非常合适的,但是对于一些数据量大的网络请求,比如下载,Volley就显得略有力不从心。

    Volley是一个开源项目,我们可以在GitHub上获得它的源代码,地址https://github.com/mcxiaoke/android-volley,拿到之后我们可以将之打包成jar包使用,也可以直接将源码拷贝到我们的项目中使用,个人推荐第二种方式,这样发生错误的时候方便我们调试,同时也有利于我们修改源码,定制我们自己的Volley。如果要拷贝源码,我们只需要将“android-volley-masterandroid-volley-mastersrcmainjava”这个文件夹下的com包拷贝到我们的项目中即可。

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

  • 相关阅读:
    JQuery源码解析(十一)
    Oracle帮助类
    JQuery正则验证
    MVC的过滤器
    JQuery源码解析(十)
    JavaScript封装的几种方式
    ps小技巧
    Fragment 的用法小技巧
    onCreateOptionsMenu 和 onPrepareOptionsMenu 的区别
    Axure 注册码
  • 原文地址:https://www.cnblogs.com/lenve/p/5865931.html
Copyright © 2011-2022 走看看