程序员人生 网站导航

Android Volley框架完全解析

栏目:综合技术时间:2016-11-17 09:42:38

2013年Google I/O大会上推出了1个新的网络通讯框架——Volley。Volley可是说是把AsyncHttpClient和Universal-Image-Loader的优点集于了1身,既可以像AsyncHttpClient1样非常简单地进行HTTP通讯,也能够像Universal-Image-Loader1样轻松加载网络上的图片。除简单易用以外,Volley在性能方面也进行了大幅度的调剂,它的设计目标就是非常合适去进行数据量不大,但通讯频繁的网络操作,而对大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟。

准备工作

导入JAR包(下载地址),申请网络权限

<uses-permission android:name="android.permission.INTERNET" />

HTTP要求与响应

1. 使用StringRequest接收String类型的响应

1个最基本的HTTP要求与响应主要就是进行以下3步操作:

  • 创建1个RequestQueue对象。
  • 创建1个StringRequest对象(以StringRequest为例,后面还会介绍其他Request)。
  • 将StringRequest对象添加到RequestQueue里面。

(1)初始化要求队列对象——RequestQueue

RequestQueue mQueue = Volley.newRequestQueue(context);

RequestQueue是1个要求队列对象,它可以缓存所有的HTTP要求,然后依照1定的算法并发地发出这些要求。RequestQueue内部的设计就是非常适合高并发的,因此我们没必要为每次HTTP要求都创建1个RequestQueue对象,这是非常浪费资源的。所以这里建议用单例模式定义这个对象。固然,你可以选择在1个activity中定义1个RequestQueue对象,但这样可能会比较麻烦,而且还可能出现要求队列包括activity强援用的问题。

(2)使用StringRequest接收String类型的响应
前面定义了要求对象,那末自然就有接收响应的对象了,这个框架中有多个响应对象,像StringRequest接遭到的响应就是string类型的;JsonRequest接收的响应就是Json类型对象。其实它们都是继承自Request<\T>,然后根据不同的响应数据来进行特殊的处理。
这里写图片描述

来看StringRequest的两个构造函数

/** method:要求方法 url:要求的地址 listener:响应成功的监听器 errorListener:出错时的监听器 **/ public StringRequest(int method, String url, Listenerlistener, ErrorListener errorListener) /**不传入method,默许会调用GET方式进行要求**/ public StringRequest(String url, Listenerlistener, ErrorListener errorListener) { this(Method.GET, url, listener, errorListener); }

GET方式要求网络,代码以下:

StringRequest stringRequest = new StringRequest("http://www.baidu.com", new Response.Listener() { @Override public void onResponse(String response) { Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show(); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { showlog(error.getMessage()); } });

POST方式要求网络,1般我们的POST都是要带1些参数的,Volley没有提供附加参数的方法,所以我们必须要在StringRequest的匿名类中重写getParams()方法,代码以下所示:

StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener) { @Override protected Map<String, String> getParams() throws AuthFailureError { Map<String, String> map = new HashMap<String, String>(); map.put("params1", "value1"); map.put("params2", "value2"); return map; } };

这样就传入了value1和value2两个参数了。现在可能有人会问为啥这个框架不提供这个传参的方法,还非得让我们重写。个人觉得这个框架本身的目的就是履行频繁的网络要求,比以下载图片,解析json数据甚么的,用GET就可以很好的实现了,所以就没有提供传参的POST方法。

(3)发送要求
发送要求很简单,将StringRequest对象添加到RequestQueue里面便可。

mQueue.add(stringRequest);

运行1下程序,发出1条HTTP要求,把服务器返回的string用Toast展现出来:
这里写图片描述
没错,百度返回给我们的就是这样1长串的HTML代码,虽然我们看起来会有些费劲,但是阅读器却可以轻松地对这段HTML代码进行解析,然后将百度的首页展现出来。

2. 使用JsonObjectRequest接收Json类型的响应

类似于StringRequest,JsonRequest也是继承自Request类的,不过由于JsonRequest是1个抽象类,因此我们没法直接创建它的实例,那末只能从它的子类入手了。JsonRequest有两个直接的子类,JsonObjectRequest和JsonArrayRequest,从名字上你应当能就看出它们的区分了吧?1个是用于要求1段JSON数据的,1个是用于要求1段JSON数组的。
这里看1下JsonObjectRequest的构造函数:

//jsonRequest:POST要求携带的参数,可以为空,表示不携带参数 public JsonObjectRequest(int method, String url, JSONObject jsonRequest, Listenerlistener, ErrorListener errorListener) { super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, errorListener); } //如果jsonRequest为空,默许使用GET要求,否则使用POST public JsonObjectRequest(String url, JSONObject jsonRequest, Listenerlistener, ErrorListener errorListener) { this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest, listener, errorListener); }

和StringRequest1样,遵守3步走原则:

RequestQueue mQueue = Volley.newRequestQueue(context); JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://weather.51wnl.com/weatherinfo/GetMoreWeather?cityCode=101020100&weatherType=0", null, new Response.Listener() { @Override public void onResponse(JSONObject response) { Toast.makeText(MainActivity.this, response.toString(), Toast.LENGTH_SHORT).show(); try { response = response.getJSONObject("weatherinfo"); showlog("city = " + response.getString("city")); showlog("weather1 = " + response.getString("weather1")); } catch (JSONException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { showlog(error.getMessage()); } }); mQueue.add(jsonObjectRequest);

注意JsonObjectRequest的POST方式携带参数和StringRequest有些不同,上面StringRequest的方式在这里不起作用。需要下面方式实现:

Map<String, String> params = new HashMap<String, String>(); params.put("name1", "value1"); params.put("name2", "value2"); JSONObject jsonRequest= new JSONObject(params); JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Method.POST, url, jsonRequest, listener, errorListener)

上面我们要求的地址是中央天气预报的上海天气,看1下运行效果:
这里写图片描述
可以看出,服务器返回给我们的数据确切是JSON格式的,并且onResponse()方法中携带的参数也正是1个JSONObject对象,以后只需要从JSONObject对象取出我们想要得到的那部份数据就能够了。
这里写图片描述

3. 使用ImageRequest来要求图片

首先来看1下ImageRequest的构造函数

public ImageRequest(String url, Response.Listenerlistener, 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,1个响应结果监听器,图片的最大宽度,图片的最大高度,图片的色彩属性,出错响应的监听器。
第3第4个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片“等比例”进行紧缩,指定成0的话就表示不管图片有多大,都不会进行紧缩。第5个参数用于指定图片的色彩属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展现最好的色彩属性,每一个图片像素占据4个字节的大小,而RGB_565则表示每一个图片像素占据2个字节大小。
3步走开始:

RequestQueue mQueue = Volley.newRequestQueue(context); ImageRequest imageRequest = new ImageRequest( "/upload/images/2016/11/17/20161117582d0ed590f56.jpg", new Response.Listener() { @Override public void onResponse(Bitmap response) { image.setImageBitmap(response); } }, 0, 0, Config.RGB_565, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { image.setImageResource(R.drawable.default_image); } }); mQueue.add(imageRequest);

看运行效果图:
这里写图片描述

加载图片— ImageLoader & NetworkImageView

Volley有无其他的,更好的方式来获得图片呢?固然有的,比如ImageLoader、NetworkImageView这样的对象,它们可以更加方便的获得图片。值得1提的是这两个对象的内部都是使用了ImageRequest进行操作的,也就是说ImageRequest是本质。

1. ImageLoader加载图片

ImageLoader也能够用于加载网络上的图片,不过ImageLoader明显要比ImageRequest更加高效,由于它不但可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送要求。
由于ImageLoader已不是继承自Request的了,所以它的用法也和我们之前学到的内容有所不同,总结起来大致可以分为以下4步:

  • 创建1个RequestQueue对象。
  • 创建1个ImageLoader对象。
  • 获得1个ImageListener对象。
  • 调用ImageLoader的get()方法加载网络上的图片。

(1)创建1个RequestQueue对象
我们前面已写过很多遍了,不再重复介绍了

(2)创建1个ImageLoader对象
示例代码以下所示:

ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() { @Override public void putBitmap(String url, Bitmap bitmap) { } @Override public Bitmap getBitmap(String url) { return null; } });

可以看到,ImageLoader的构造函数接收两个参数,第1个参数就是RequestQueue对象,第2个参数是1个ImageCache对象(不能传null!),这里的ImageCache就是为我们做内存缓存用的,我们可以定制自己的实现方式,现在主流的实现是LruCache,关于LruCache可以参考我之前写的1篇文章Android的缓存技术:LruCache和DiskLruCache。

ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache());
//BitmapCache的实现类 public class BitmapCache implements ImageCache { private LruCache mCache; public BitmapCache() { int maxSize = 10 * 1024 * 1024; mCache = new LruCache(maxSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } }; @Override public Bitmap getBitmap(String url) { return mCache.get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { mCache.put(url, bitmap); } }

(3)获得1个ImageListener对象

ImageListener listener = ImageLoader.getImageListener(imageView, R.drawable.default_image, R.drawable.fail_image);

我们通过调用ImageLoader的getImageListener()方法能够获得到1个ImageListener对象,getImageListener()方法接收3个参数,第1个参数指定用于显示图片的ImageView控件,第2个参数指定加载图片的进程中显示的图片,第3个参数指定加载图片失败的情况下显示的图片。

(4)调用ImageLoader的get()方法加载网络上的图片

imageLoader.get("/upload/images/2016/11/17/20161117582d0ed5e4e1c.jpg", listener);

get()方法接收两个参数,第1个参数就是图片的URL地址,第2个参数则是刚刚获得到的ImageListener对象。固然,如果你想对图片的大小进行限制,也能够使用get()方法的重载,指定图片允许的最大宽度和高度,以下所示:

imageLoader.get("/upload/images/2016/11/17/20161117582d0ed5e4e1c.jpg", listener, 600, 600);

运行1下程序点击加载图片,你将看到ImageView会先显示1张默许的加载进程中图片,等到网络上的图片加载完成后,ImageView则会自动显示该图。如果我们用ImageLoader再次加载该图片,会很快显示出来而看不到默许的加载进程中图片,这是由于这次的图片是从缓存中取的,速度很快。效果以下图所示。
这里写图片描述

注:上面我们只是定制了内存缓存,查看源码,可以发现ImageLoader对图片也进行了硬盘缓存,我们在履行get()方法前可以通过imageLoader.setShouldCache(false);来取消硬盘缓存,如果你不进行设置的话默许是履行硬盘缓存的。看看控制硬盘缓存的几个方法:

public final boolean shouldCache() //查看是不是已做了磁盘缓存。 void setShouldCache(boolean shouldCache)//设置是不是运行磁盘缓存,此方法需要在get方法前使用 public boolean isCached(String requestUrl, int maxWidth, int maxHeight)//判断对象是不是已被缓存,传入url,还有图片的最大宽高

2. NetworkImageView加载图片

NetworkImageView继承自ImageView,你可以认为它是1个可以实现加载网络图片的imageview,10分简单好用。这个控件在被从父控件分离的时候,会自动取消网络要求的,即完全不用我们担心相干网络要求的生命周期问题。
NetworkImageView控件的用法大致可以分为以下5步:

  • 创建1个RequestQueue对象。
  • 创建1个ImageLoader对象。
  • 在布局文件中添加1个NetworkImageView控件。
  • 在代码中获得该控件的实例。
  • 设置要加载的图片地址。
<com.android.volley.toolbox.NetworkImageView android:id="@+id/network_image_view" android:layout_width="200dp" android:layout_height="200dp" android:layout_gravity="center_horizontal" />
/**创建RequestQueue和ImageLoader对象**/ RequestQueue mQueue = Volley.newRequestQueue(context); ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache()); /**获得NetworkImageView控件**/ NetworkImageView networkImageView = (NetworkImageView) findViewById(R.id.network_image_view); /**设置加载中显示的图片**/ networkImageView.setDefaultImageResId(R.drawable.default_image); /**加载失败时显示的图片**/ networkImageView.setErrorImageResId(R.drawable.fail_image); /**设置目标图片的URL地址**/ networkImageView.setImageUrl("/upload/images/2016/11/17/20161117582d0ed65b8d8.jpg", imageLoader);

好了,就是这么简单,现在重新运行1下程序,你将看到和使用ImageLoader来加载图片1模1样的效果,这里我就不再截图了。
NetworkImageView没有提供任何设置图片宽高的方法,这是由于它是1个控件,在加载图片的时候它会自动获得本身的宽高,然后对照网络图片的宽度,再决定是不是需要对图片进行紧缩。也就是说,紧缩进程是在内部完全自动化的,其实不需要我们关心。NetworkImageView终究会始终显现给我们1张大小比控件尺寸略大的网络图片,由于它会根据控件宽高来等比缩放原始图片,不会多占用任何1点内存,这也是NetworkImageView最简单好用的1点吧。
如果你不想对图片进行紧缩的话,只需要在布局文件中把NetworkImageView的layout_width和layout_height都设置成wrap_content就能够了,这样它就会将该图片的原始大小展现出来,不会进行任何紧缩。

自定义Request

Volley中提供了几个经常使用Request(StringRequest、JsonObjectRequest、JsonArrayRequest、ImageRequest),如果我们有自己特殊的需求,其实完全可以自定义自己的Request。
自定义Request之前,我们先来看看StringRequest的源码实现:

package com.android.volley.toolbox; public class StringRequest extends Request<String> { // 建立监听器来取得响应成功时返回的结果 private final ListenermListener; // 传入要求方法,url,成功时的监听器,失败时的监听器 public StringRequest(int method, String url, Listenerlistener, ErrorListener errorListener) { super(method, url, errorListener); // 初始化成功时的监听器 mListener = listener; } /** * Creates a new GET request. * 建立1个默许的GET要求,调用了上面的构造函数 */ public StringRequest(String url, Listenerlistener, ErrorListener errorListener) { this(Method.GET, url, listener, errorListener); } @Override protected void deliverResponse(String response) { // 用监听器的方法来传递下响应的结果 mListener.onResponse(response); } @Override protected ResponseparseNetworkResponse(NetworkResponse response) { String parsed; try { // 调用了new String(byte[] data, String charsetName) 这个构造函数来构建String对象,将byte数组依照特定的编码方式转换为String对象,主要部份是data parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { parsed = new String(response.data); } return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); } }

首先StringRequest是继承自Request类的,Request可以指定1个泛型类,这里指定确当然就是String了,接下来StringRequest中提供了两个有参的构造函数,参数包括要求类型,要求地址,和响应回调等。但需要注意的是,在构造函数中1定要调用super()方法将这几个参数传给父类,由于HTTP的要求和响应都是在父类中自动处理的。
另外,由于Request类中的deliverResponse()和parseNetworkResponse()是两个抽象方法,因此StringRequest中需要对这两个方法进行实现。deliverResponse()方法中的实现很简单,仅仅是调用了mListener中的onResponse()方法,并将response内容传入便可,这样就能够将服务器响应的数据进行回调了。parseNetworkResponse()方法中则是对服务器响应的数据进行解析,其中数据是以字节的情势寄存在NetworkResponse的data变量中的,这里将数据取出然后组装成1个String,并传入Response的success()方法中便可。

1. 自定义XMLRequest

了解了StringRequest的实现原理,下面我们就能够动手来尝试实现1下XMLRequest了,代码以下所示:

public class XMLRequest extends Request<XmlPullParser> { private final ListenermListener; public XMLRequest(int method, String url, Listenerlistener, ErrorListener errorListener) { super(method, url, errorListener); mListener = listener; } public XMLRequest(String url, Listenerlistener, ErrorListener errorListener) { this(Method.GET, url, listener, errorListener); } @Override protected ResponseparseNetworkResponse(NetworkResponse response) { try { String xmlString = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser xmlPullParser = factory.newPullParser(); xmlPullParser.setInput(new StringReader(xmlString)); return Response.success(xmlPullParser, HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } catch (XmlPullParserException e) { return Response.error(new ParseError(e)); } } @Override protected void deliverResponse(XmlPullParser response) { mListener.onResponse(response); } }

可以看到,其实并没有甚么太多的逻辑,基本都是仿照StringRequest写下来的,XMLRequest也是继承自Request类的,只不过这里指定的泛型类是XmlPullParser,说明我们准备使用Pull解析的方式来解析XML。在parseNetworkResponse()方法中,先是将服务器响应的数据解析成1个字符串,然后设置到XmlPullParser对象中,在deliverResponse()方法中则是将XmlPullParser对象进行回调。
下面我们尝试使用这个XMLRequest来要求1段XML格式的数据,http://flash.weather.com.cn/wmaps/xml/china.xml这个接口会将中国所有的省分数据以XML格式进行返回,以下所示:
这里写图片描述

XMLRequest xmlRequest = new XMLRequest("http://flash.weather.com.cn/wmaps/xml/china.xml", new Response.Listener() { @Override public void onResponse(XmlPullParser response) { try { int eventType = response.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { switch (eventType) { case XmlPullParser.START_TAG: String nodeName = response.getName(); if ("city".equals(nodeName)) { String pName = response.getAttributeValue(0); String cName = response.getAttributeValue(2); showlog("省分:" + pName + " 城市:" + cName); } break; } eventType = response.next(); } } catch (XmlPullParserException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { showlog(error.getMessage()); } }); mQueue.add(xmlRequest);

这里写图片描述

2. 自定义GsonRequest

JsonRequest的数据解析是利用Android本身自带的JSONObject和JSONArray来实现的,配合使用JSONObject和JSONArray就能够解析出任意格式的JSON数据。不过或许你会觉得使用JSONObject还是太麻烦了,还有很多方法可让JSON数据解析变得更加简单,比如说GSON对象。遗憾的是,Volley中默许其实不支持使用自家的GSON来解析数据,不过没有关系,通过上面的学习,相信你已知道了自定义1个Request是多么的简单,那末下面我们就来举1反31下,自定义1个GsonRequest。
首先我们需要把GSON的jar包导入到项目当中,接着定义1个GsonRequest继承自Request,代码以下所示:

public class GsonRequestextends Request{ private final ListenermListener; private Gson mGson; private ClassmClass; public GsonRequest(int method, String url, Classclazz, Listenerlistener, ErrorListener errorListener) { super(method, url, errorListener); mGson = new Gson(); mClass = clazz; mListener = listener; } public GsonRequest(String url, Classclazz, Listenerlistener, ErrorListener errorListener) { this(Method.GET, url, clazz, listener, errorListener); } @Override protected ResponseparseNetworkResponse(NetworkResponse response) { try { String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); return Response.success(mGson.fromJson(jsonString, mClass), HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } } @Override protected void deliverResponse(T response) { mListener.onResponse(response); } }

GsonRequest是继承自Request类的,并且一样提供了两个构造函数。在parseNetworkResponse()方法中,先是将服务器响应的数据解析出来,然后通过调用Gson的fromJson方法将数据组装成对象。在deliverResponse方法中依然是将终究的数据进行回调。
下面我们就来测试1下这个GsonRequest能不能够正常工作吧,一样调用http://www.weather.com.cn/data/sk/101020100.html这个接口可以得到1段JSON格式的天气数据,以下所示:

{"weatherinfo":{"city":"上海","city_en":"","cityid":101020100,"date":"","date_y":"2016年09月20日","fchh":0,"fl1":"","fl2":"","fl3":"","fl4":"","fl5":"","fl6":"","fx1":"","fx2":"","img1":"1","img10":"1","img11":"1","img12":"1","img2":"1","img3":"1","img4":"1","img5":"1","img6":"1","img7":"1","img8":"1","img9":"1","img_single":0,"img_title1":"","img_title10":"","img_title11":"","img_title12":"","img_title2":"","img_title3":"","img_title4":"","img_title5":"","img_title6":"","img_title7":"","img_title8":"","img_title9":"","img_title_single":"","index":"","index48":"","index48_d":"","index48_uv":"","index_ag":"","index_cl":"","index_co":"","index_d":"","index_ls":"","index_tr":"","index_uv":"","index_xc":"","st1":0,"st2":0,"st3":0,"st4":0,"st5":0,"st6":0,"temp1":"20℃~28℃","temp2":"20℃~26℃","temp3":"19℃~26℃","temp4":"21℃~26℃","temp5":"23℃~28℃","temp6":"22℃~27℃","tempF1":"","tempF2":"","tempF3":"","tempF4":"","tempF5":"","tempF6":"","weather1":"多云","weather2":"多云","weather3":"多云","weather4":"多云","weather5":"多云","weather6":"多云","week":"","wind1":"","wind2":"","wind3":"","wind4":"","wind5":"","wind6":""}}

我们需要使用对象的方式将这段JSON字符串表示出来。下面新建两个Bean文件:

public class Weather { public WeatherInfo weatherinfo; }
public class WeatherInfo { public String city; public String cityid; public String date_y; public String temp1; public String weather1; }

下面就是用GsonRequest要求json数据了

GsonRequestgsonRequest = new GsonRequest( "http://weather.51wnl.com/weatherinfo/GetMoreWeather?cityCode=101020100&weatherType=0", Weather.class, new Response.Listener() { @Override public void onResponse(Weather weather) { WeatherInfo weatherInfo = weather.weatherinfo; showlog("city is " + weatherInfo.city); showlog("cityid is " + weatherInfo.cityid); showlog("date_y is " + weatherInfo.date_y); showlog("temp1 is " + weatherInfo.temp1); showlog("weather1 is " + weatherInfo.weather1); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { showlog(error.getMessage()); } }); mQueue.add(gsonRequest);

这里onResponse()方法的回调中直接返回了1个Weather对象,我们通过它就能够得到WeatherInfo对象,接着就可以从中取出JSON中的相干数据了。运行1下程序,打印Log以下:
这里写图片描述

3. 自定义GsonRequestWithAuth

上面自定义的Request并没有携带参数,如果我们访问服务器时需要传参呢?比方通过客户端访问服务器服务器对客户端进行身份校验后,返回用户信息,客户端直接拿到对象。
先写Bean文件:

public class User { private String name; private int age; }

自定义GsonRequestWithAuth:

public class GsonRequestWithAuth<T> extends Request<T> { private final Gson gson = new Gson(); private final Classclazz; private final Listenerlistener; private Map mHeader = new HashMap(); private String mBody; /** http要求编码方式 */ private static final String PROTOCOL_CHARSET = "utf⑻"; /** 设置访问自己服务器时必须传递的参数,密钥等 */ static { mHeader.put("APP-Key", "Key"); mHeader.put("APP-Secret", "Secret"); } /** * @param url * @param clazz 我们终究的转化类型 * @param listener * @param appendHeader 附加头数据 * @param body 要求附带消息体 * @param errorListener */ public GsonRequestWithAuth(String url, Classclazz, Listenerlistener, Map appendHeader, String body, ErrorListener errorListener) { super(Method.POST, url, errorListener); this.clazz = clazz; this.listener = listener; mHeader.putAll(appendHeader); mBody = body; } @Override public Map getHeaders() throws AuthFailureError { // 默许返回 return Collections.emptyMap(); return mHeader; } @Override public byte[] getBody() { try { return mBody == null ? null : mBody.getBytes(PROTOCOL_CHARSET); } catch (UnsupportedEncodingException uee) { VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", mUserName, PROTOCOL_CHARSET); return null; } } @Override protected void deliverResponse(T response) { listener.onResponse(response); } @Override protected ResponseparseNetworkResponse(NetworkResponse response) { try { /** 得到返回的数据 */ String jsonStr = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); /** 转化成对象 */ return Response.success(gson.fromJson(jsonStr, clazz), HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } catch (JsonSyntaxException e) { return Response.error(new ParseError(e)); } } }

服务器代码:

public class TestServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf⑻"); /**获得APP-Key和APP-Secret */ String appKey = request.getHeader("APP-Key"); String appSecret = request.getHeader("APP-Secret"); /**获得用户名、密码 */ String username = request.getHeader("username"); String password = request.getHeader("password"); /**获得消息体 */ int size = request.getContentLength(); InputStream is = request.getInputStream(); byte[] reqBodyBytes = readBytes(is, size); String body = new String(reqBodyBytes); if ("admin".equals(username) && "123".equals(password) && "getUserInfo".equals(body)) { response.setContentType("text/plain;charset=utf⑻"); PrintWriter out = response.getWriter(); out.print("{\"name\":\"Watson\",\"age\":28}"); out.flush(); } } }

使用GsonRequestWithAuth和服务器交互要求信息:

Map<String, String> appendHeader = new HashMap<String, String>(); appendHeader.put("username", "admin"); appendHeader.put("password", "123"); String url = "http://172.27.35.1:8080/webTest/TestServlet"; GsonRequestWithAuthuserRequest = new GsonRequestWithAuth(url, User.class, new Listener() { @Override public void onResponse(User response) { Log.e("TAG", response.toString()); } }, appendHeader, "getUserInfo", null); mQueue.add(userRequest);

延伸:
看到没有,我们上面写服务器端代码时,有1句代码是设置服务器返回数据的字符集为UTF⑻

response.setContentType("text/plain;charset=utf⑻");

大部份服务器端都会在返回数据的header中指定字符集,如果在服务器端没有指定字符集那末就会默许使用 ISO⑻859⑴ 字符集。
ISO⑻859⑴的别名叫做Latin1。这个字符集支持部份是用于欧洲的语言,不支持中文,这就会致使服务器返回的中文数据乱码,很不能理解为何将这个字符集作为默许的字符集。Volley这个框架可是要用在网络通讯的环境中的。吐槽也没有用,我们来看1下如何来解决中文乱码的问题。有以下几种解决方式:

  • 服务器的返回的数据的header的中contentType加上charset=UTF⑻的声明。
  • 当你没法修改服务器程序的时候,可以定义1个新的子类。覆盖parseNetworkResponse这个方法,直接使用UTF⑻对服务器的返回数据进行转码。
public class CharsetStringRequest extends StringRequest { public CharsetStringRequest(String url, Listenerlistener, ErrorListener errorListener) { super(url, listener, errorListener); } public CharsetStringRequest(int method, String url, Listenerlistener, ErrorListener errorListener) {
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐