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

  • 相关阅读:
    CSS中position小解
    position
    mac默认安装postgresql, 如何让postgresql可以远程访问
    The data directory was initialized by PostgreSQL version 9.6, which is not compatible with this version 10.0.
    active admin gem error
    psql 无法添加超级用户
    ubuntu 15.04 安装Balsamiq Mockups 3
    Rails html 写public里图片的路径
    rails c 历史命令
    undefined local variable or method `per' for []:ActiveRecord::Relation
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461727.html
Copyright © 2020-2023  润新知