经过前面两篇文章的学习,我们已经掌握了Volley各种Request的使用方法,包括StringRequest、JsonRequest、ImageRequest等。其中StringRequest用于请求一条普通的文本数据,JsonRequest(JsonObjectRequest、JsonArrayRequest)用于请求一条JSON格式的数据,ImageRequest则是用于请求网络上的一张图片。
可是Volley提供给我们的Request类型就只有这么多,而我们都知道,在网络上传输的数据通常有两种格式,JSON和XML,那么如果想要请求一条XML格式的数据该怎么办呢?其实很简单,Volley提供了非常强的扩展机制,使得我们可以很轻松地定制出任意类型的Request,这也就是本篇文章的主题了。
在开始之前还是友情提醒一下,如果你还没有阅读过我前面两篇关于Volley的文章,建议先去阅读一下Android Volley完全解析(一),初识Volley的基本用法和Android Volley完全解析(二),使用Volley加载网络图片。
1. 自定义XMLRequest
下面我们准备自定义一个XMLRequest,用于请求一条XML格式的数据。那么该从哪里开始入手呢?额,好像是有些无从下手。遇到这种情况,我们应该去参考一下Volley的源码,看一看StringRequest是怎么实现的,然后就可以模仿着写出XMLRequest了。首先看下StringRequest的源码,如下所示:
01.
/**
02.
* A canned request for retrieving the response body at a given URL as a String.
03.
*/
04.
public
class
StringRequest
extends
Request<String> {
05.
private
final
Listener<String> mListener;
06.
07.
/**
08.
* Creates a new request with the given method.
09.
*
10.
* @param method the request {@link Method} to use
11.
* @param url URL to fetch the string at
12.
* @param listener Listener to receive the String response
13.
* @param errorListener Error listener, or null to ignore errors
14.
*/
15.
public
StringRequest(
int
method, String url, Listener<String> listener,
16.
ErrorListener errorListener) {
17.
super
(method, url, errorListener);
18.
mListener = listener;
19.
}
20.
21.
/**
22.
* Creates a new GET request.
23.
*
24.
* @param url URL to fetch the string at
25.
* @param listener Listener to receive the String response
26.
* @param errorListener Error listener, or null to ignore errors
27.
*/
28.
public
StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
29.
this
(Method.GET, url, listener, errorListener);
30.
}
31.
32.
@Override
33.
protected
void
deliverResponse(String response) {
34.
mListener.onResponse(response);
35.
}
36.
37.
@Override
38.
protected
Response<String> parseNetworkResponse(NetworkResponse response) {
39.
String parsed;
40.
try
{
41.
parsed =
new
String(response.data, HttpHeaderParser.parseCharset(response.headers));
42.
}
catch
(UnsupportedEncodingException e) {
43.
parsed =
new
String(response.data);
44.
}
45.
return
Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
46.
}
47.
}
可以看到,StringRequest的源码很简练,根本就没几行代码,我们一起来分析下。首先StringRequest是继承自Request类的,Request可以指定一个泛型类,这里指定的当然就是String了,接下来StringRequest中提供了两个有参的构造函数,参数包括请求类型,请求地址,以及响应回调等,由于我们已经很熟悉StringRequest的用法了,相信这几个参数的作用都不用再解释了吧。但需要注意的是,在构造函数中一定要调用super()方法将这几个参数传给父类,因为HTTP的请求和响应都是在父类中自动处理的。
另外,由于Request类中的deliverResponse()和parseNetworkResponse()是两个抽象方法,因此StringRequest中需要对这两个方法进行实现。deliverResponse()方法中的实现很简单,仅仅是调用了mListener中的onResponse()方法,并将response内容传入即可,这样就可以将服务器响应的数据进行回调了。parseNetworkResponse()方法中则应该对服务器响应的数据进行解析,其中数据是以字节的形式存放在NetworkResponse的data变量中的,这里将数据取出然后组装成一个String,并传入Response的success()方法中即可。
了解了StringRequest的实现原理,下面我们就可以动手来尝试实现一下XMLRequest了,代码如下所示:
01.
public
class
XMLRequest
extends
Request<XmlPullParser> {
02.
03.
private
final
Listener<XmlPullParser> mListener;
04.
05.
public
XMLRequest(
int
method, String url, Listener<XmlPullParser> listener,
06.
ErrorListener errorListener) {
07.
super
(method, url, errorListener);
08.
mListener = listener;
09.
}
10.
11.
public
XMLRequest(String url, Listener<XmlPullParser> listener, ErrorListener errorListener) {
12.
this
(Method.GET, url, listener, errorListener);
13.
}
14.
15.
@Override
16.
protected
Response<XmlPullParser> parseNetworkResponse(NetworkResponse response) {
17.
try
{
18.
String xmlString =
new
String(response.data,
19.
HttpHeaderParser.parseCharset(response.headers));
20.
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
21.
XmlPullParser xmlPullParser = factory.newPullParser();
22.
xmlPullParser.setInput(
new
StringReader(xmlString));
23.
return
Response.success(xmlPullParser, HttpHeaderParser.parseCacheHeaders(response));
24.
}
catch
(UnsupportedEncodingException e) {
25.
return
Response.error(
new
ParseError(e));
26.
}
catch
(XmlPullParserException e) {
27.
return
Response.error(
new
ParseError(e));
28.
}
29.
}
30.
31.
@Override
32.
protected
void
deliverResponse(XmlPullParser response) {
33.
mListener.onResponse(response);
34.
}
35.
36.
}
可以看到,其实并没有什么太多的逻辑,基本都是仿照StringRequest写下来的,XMLRequest也是继承自Request类的,只不过这里指定的泛型类是XmlPullParser,说明我们准备使用Pull解析的方式来解析XML。在parseNetworkResponse()方法中,先是将服务器响应的数据解析成一个字符串,然后设置到XmlPullParser对象中,在deliverResponse()方法中则是将XmlPullParser对象进行回调。
好了,就是这么简单,下面我们尝试使用这个XMLRequest来请求一段XML格式的数据。http://flash.weather.com.cn/wmaps/xml/china.xml这个接口会将中国所有的省份数据以XML格式进行返回,如下所示:
01.
<china dn=
"day"
slick-uniqueid=
"3"
>
02.
<city quName=
"黑龙江"
pyName=
"heilongjiang"
cityname=
"哈尔滨"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"18"
tem2=
"6"
windState=
"西北风3-4级转西风小于3级"
/>
03.
<city quName=
"吉林"
pyName=
"jilin"
cityname=
"长春"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"19"
tem2=
"6"
windState=
"西北风3-4级转小于3级"
/>
04.
<city quName=
"辽宁"
pyName=
"liaoning"
cityname=
"沈阳"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"21"
tem2=
"7"
windState=
"东北风3-4级"
/>
05.
<city quName=
"海南"
pyName=
"hainan"
cityname=
"海口"
state1=
"1"
state2=
"1"
stateDetailed=
"多云"
tem1=
"30"
tem2=
"24"
windState=
"微风"
/>
06.
<city quName=
"内蒙古"
pyName=
"neimenggu"
cityname=
"呼和浩特"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"19"
tem2=
"5"
windState=
"东风3-4级"
/>
07.
<city quName=
"新疆"
pyName=
"xinjiang"
cityname=
"乌鲁木齐"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"22"
tem2=
"10"
windState=
"微风转东南风小于3级"
/>
08.
<city quName=
"西藏"
pyName=
"xizang"
cityname=
"拉萨"
state1=
"1"
state2=
"7"
stateDetailed=
"多云转小雨"
tem1=
"18"
tem2=
"4"
windState=
"微风"
/>
09.
<city quName=
"青海"
pyName=
"qinghai"
cityname=
"西宁"
state1=
"0"
state2=
"1"
stateDetailed=
"晴转多云"
tem1=
"18"
tem2=
"2"
windState=
"微风"
/>
10.
<city quName=
"宁夏"
pyName=
"ningxia"
cityname=
"银川"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"19"
tem2=
"8"
windState=
"微风"
/>
11.
<city quName=
"甘肃"
pyName=
"gansu"
cityname=
"兰州"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"21"
tem2=
"6"
windState=
"微风"
/>
12.
<city quName=
"河北"
pyName=
"hebei"
cityname=
"石家庄"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"25"
tem2=
"12"
windState=
"北风小于3级"
/>
13.
<city quName=
"河南"
pyName=
"henan"
cityname=
"郑州"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"24"
tem2=
"13"
windState=
"微风"
/>
14.
<city quName=
"湖北"
pyName=
"hubei"
cityname=
"武汉"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"24"
tem2=
"12"
windState=
"微风"
/>
15.
<city quName=
"湖南"
pyName=
"hunan"
cityname=
"长沙"
state1=
"2"
state2=
"1"
stateDetailed=
"阴转多云"
tem1=
"20"
tem2=
"15"
windState=
"北风小于3级"
/>
16.
<city quName=
"山东"
pyName=
"shandong"
cityname=
"济南"
state1=
"1"
state2=
"1"
stateDetailed=
"多云"
tem1=
"20"
tem2=
"10"
windState=
"北风3-4级转小于3级"
/>
17.
<city quName=
"江苏"
pyName=
"jiangsu"
cityname=
"南京"
state1=
"2"
state2=
"2"
stateDetailed=
"阴"
tem1=
"19"
tem2=
"13"
windState=
"西北风4-5级转3-4级"
/>
18.
<city quName=
"安徽"
pyName=
"anhui"
cityname=
"合肥"
state1=
"2"
state2=
"1"
stateDetailed=
"阴转多云"
tem1=
"20"
tem2=
"12"
windState=
"西北风转北风3-4级"
/>
19.
<city quName=
"山西"
pyName=
"shanxi"
cityname=
"太原"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"22"
tem2=
"8"
windState=
"微风"
/>
20.
<city quName=
"陕西"
pyName=
"sanxi"
cityname=
"西安"
state1=
"1"
state2=
"0"
stateDetailed=
"多云转晴"
tem1=
"21"
tem2=
"9"
windState=
"东北风小于3级"
/>
21.
<city quName=
"四川"
pyName=
"sichuan"
cityname=
"成都"
state1=
"1"
state2=
"1"
stateDetailed=
"多云"
tem1=
"26"
tem2=
"15"
windState=
"南风小于3级"
/>
22.
<city quName=
"云南"
pyName=
"yunnan"
cityname=
"昆明"
state1=
"7"
state2=
"7"
stateDetailed=
"小雨"
tem1=
"21"
tem2=
"13"
windState=
"微风"
/>
23.
<city quName=
"贵州"
pyName=
"guizhou"
cityname=
"贵阳"
state1=
"1"
state2=
"3"
stateDetailed=
"多云转阵雨"
tem1=
"21"
tem2=
"11"
windState=
"东风小于3级"
/>
24.
<city quName=
"浙江"
pyName=
"zhejiang"
cityname=
"杭州"
state1=
"3"
state2=
"1"
stateDetailed=
"阵雨转多云"
tem1=
"22"
tem2=
"14"
windState=
"微风"
/>
25.
<city quName=
"福建"
pyName=
"fujian"
cityname=
"福州"
state1=
"1"
state2=
"2"
stateDetailed=
"多云转阴"
tem1=
"28"
tem2=
"18"
windState=
"微风"
/>
26.
<city quName=
"江西"
pyName=
"jiangxi"
cityname=
"南昌"
state1=
"2"
state2=
"1"
stateDetailed=
"阴转多云"
tem1=
"23"
tem2=
"15"
windState=
"北风3-4级转微风"
/>
27.
<city quName=
"广东"
pyName=
"guangdong"
cityname=
"广州"
state1=
"3"
state2=
"2"
stateDetailed=
"阵雨转阴"
tem1=
"26"
tem2=
"20"
windState=
"微风"
/>
28.
<city quName=
"广西"
pyName=
"guangxi"
cityname=
"南宁"
state1=
"3"
state2=
"3"
stateDetailed=
"阵雨"
tem1=
"23"
tem2=
"19"
windState=
"东北风小于3级"
/>
29.
<city quName=
"北京"
pyName=
"beijing"
cityname=
"北京"
state1=
"0"
state2=
"0"
stateDetailed=
"晴"
tem1=
"26"
tem2=
"10"
windState=
"微风"
/>
30.
<city quName=
"天津"
pyName=
"tianjin"
cityname=
"天津"
state1=
"1"
state2=
"0"
stateDetailed=
"多云转晴"
tem1=
"22"
tem2=
"13"
windState=
"东北风3-4级转小于3级"
/>
31.
<city quName=
"上海"
pyName=
"shanghai"
cityname=
"上海"
state1=
"7"
state2=
"1"
stateDetailed=
"小雨转多云"
tem1=
"20"
tem2=
"16"
windState=
"西北风3-4级"
/>
32.
<city quName=
"重庆"
pyName=
"chongqing"
cityname=
"重庆"
state1=
"1"
state2=
"3"
stateDetailed=
"多云转阵雨"
tem1=
"21"
tem2=
"14"
windState=
"微风"
/>
33.
<city quName=
"香港"
pyName=
"xianggang"
cityname=
"香港"
state1=
"3"
state2=
"1"
stateDetailed=
"阵雨转多云"
tem1=
"26"
tem2=
"22"
windState=
"微风"
/>
34.
<city quName=
"澳门"
pyName=
"aomen"
cityname=
"澳门"
state1=
"3"
state2=
"1"
stateDetailed=
"阵雨转多云"
tem1=
"27"
tem2=
"22"
windState=
"东北风3-4级转微风"
/>
35.
<city quName=
"台湾"
pyName=
"taiwan"
cityname=
"台北"
state1=
"9"
state2=
"7"
stateDetailed=
"大雨转小雨"
tem1=
"28"
tem2=
"21"
windState=
"微风"
/>
36.
<city quName=
"西沙"
pyName=
"xisha"
cityname=
"西沙"
state1=
"3"
state2=
"3"
stateDetailed=
"阵雨"
tem1=
"30"
tem2=
"26"
windState=
"东北风4-5级"
/>
37.
<city quName=
"南沙"
pyName=
"nanshadao"
cityname=
"南沙"
state1=
"1"
state2=
"1"
stateDetailed=
"多云"
tem1=
"32"
tem2=
"27"
windState=
"东风4-5级"
/>
38.
<city quName=
"钓鱼岛"
pyName=
"diaoyudao"
cityname=
"钓鱼岛"
state1=
"7"
state2=
"1"
stateDetailed=
"小雨转多云"
tem1=
"23"
tem2=
"19"
windState=
"西南风3-4级转北风5-6级"
/>
39.
</china>
01.
XMLRequest xmlRequest =
new
XMLRequest(
03.
new
Response.Listener<XmlPullParser>() {
04.
@Override
05.
public
void
onResponse(XmlPullParser response) {
06.
try
{
07.
int
eventType = response.getEventType();
08.
while
(eventType != XmlPullParser.END_DOCUMENT) {
09.
switch
(eventType) {
10.
case
XmlPullParser.START_TAG:
11.
String nodeName = response.getName();
12.
if
(
"city"
.equals(nodeName)) {
13.
String pName = response.getAttributeValue(
0
);
14.
Log.d(
"TAG"
,
"pName is "
+ pName);
15.
}
16.
break
;
17.
}
18.
eventType = response.next();
19.
}
20.
}
catch
(XmlPullParserException e) {
21.
e.printStackTrace();
22.
}
catch
(IOException e) {
23.
e.printStackTrace();
24.
}
25.
}
26.
},
new
Response.ErrorListener() {
27.
@Override
28.
public
void
onErrorResponse(VolleyError error) {
29.
Log.e(
"TAG"
, error.getMessage(), error);
30.
}
31.
});
32.
mQueue.add(xmlRequest);
现在运行一下代码,观察控制台日志,就可以看到每个省的名字都从XML中解析出来了,如下图所示。
2. 自定义GsonRequest
JsonRequest的数据解析是利用Android本身自带的JSONObject和JSONArray来实现的,配合使用JSONObject和JSONArray就可以解析出任意格式的JSON数据。不过也许你会觉得使用JSONObject还是太麻烦了,还有很多方法可以让JSON数据解析变得更加简单,比如说GSON。遗憾的是,Volley中默认并不支持使用自家的GSON来解析数据,不过没有关系,通过上面的学习,相信你已经知道了自定义一个Request是多么的简单,那么下面我们就来举一反三一下,自定义一个GsonRequest。
首先我们需要把gson的jar包添加到项目当中,jar包的下载地址是:https://code.google.com/p/google-gson/downloads/list 。
接着定义一个GsonRequest继承自Request,代码如下所示:
01.
public
class
GsonRequest<T>
extends
Request<T> {
02.
03.
private
final
Listener<T> mListener;
04.
05.
private
Gson mGson;
06.
07.
private
Class<T> mClass;
08.
09.
public
GsonRequest(
int
method, String url, Class<T> clazz, Listener<T> listener,
10.
ErrorListener errorListener) {
11.
super
(method, url, errorListener);
12.
mGson =
new
Gson();
13.
mClass = clazz;
14.
mListener = listener;
15.
}
16.
17.
public
GsonRequest(String url, Class<T> clazz, Listener<T> listener,
18.
ErrorListener errorListener) {
19.
this
(Method.GET, url, clazz, listener, errorListener);
20.
}
21.
22.
@Override
23.
protected
Response<T> parseNetworkResponse(NetworkResponse response) {
24.
try
{
25.
String jsonString =
new
String(response.data,
26.
HttpHeaderParser.parseCharset(response.headers));
27.
return
Response.success(mGson.fromJson(jsonString, mClass),
28.
HttpHeaderParser.parseCacheHeaders(response));
29.
}
catch
(UnsupportedEncodingException e) {
30.
return
Response.error(
new
ParseError(e));
31.
}
32.
}
33.
34.
@Override
35.
protected
void
deliverResponse(T response) {
36.
mListener.onResponse(response);
37.
}
38.
39.
}
那么下面我们就来测试一下这个GsonRequest能不能够正常工作吧,调用http://www.weather.com.cn/data/sk/101010100.html这个接口可以得到一段JSON格式的天气数据,如下所示:
1.
{
"weatherinfo"
:{
"city"
:
"北京"
,
"cityid"
:
"101010100"
,
"temp"
:
"19"
,
"WD"
:
"南风"
,
"WS"
:
"2级"
,
"SD"
:
"43%"
,
"WSE"
:
"2"
,
"time"
:
"19:45"
,
"isRadar"
:
"1"
,
"Radar"
:
"JC_RADAR_AZ9010_JB"
}}
01.
public
class
Weather {
02.
03.
private
WeatherInfo weatherinfo;
04.
05.
public
WeatherInfo getWeatherinfo() {
06.
return
weatherinfo;
07.
}
08.
09.
public
void
setWeatherinfo(WeatherInfo weatherinfo) {
10.
this
.weatherinfo = weatherinfo;
11.
}
12.
13.
}
01.
public
class
WeatherInfo {
02.
03.
private
String city;
04.
05.
private
String temp;
06.
07.
private
String time;
08.
09.
public
String getCity() {
10.
return
city;
11.
}
12.
13.
public
void
setCity(String city) {
14.
this
.city = city;
15.
}
16.
17.
public
String getTemp() {
18.
return
temp;
19.
}
20.
21.
public
void
setTemp(String temp) {
22.
this
.temp = temp;
23.
}
24.
25.
public
String getTime() {
26.
return
time;
27.
}
28.
29.
public
void
setTime(String time) {
30.
this
.time = time;
31.
}
32.
33.
}
01.
GsonRequest<Weather> gsonRequest =
new
GsonRequest<Weather>(
02.
"http://www.weather.com.cn/data/sk/101010100.html"
, Weather.
class
,
03.
new
Response.Listener<Weather>() {
04.
@Override
05.
public
void
onResponse(Weather weather) {
06.
WeatherInfo weatherInfo = weather.getWeatherinfo();
07.
Log.d(
"TAG"
,
"city is "
+ weatherInfo.getCity());
08.
Log.d(
"TAG"
,
"temp is "
+ weatherInfo.getTemp());
09.
Log.d(
"TAG"
,
"time is "
+ weatherInfo.getTime());
10.
}
11.
},
new
Response.ErrorListener() {
12.
@Override
13.
public
void
onErrorResponse(VolleyError error) {
14.
Log.e(
"TAG"
, error.getMessage(), error);
15.
}
16.
});
17.
mQueue.add(gsonRequest);
这样的话,XMLRequest和GsonRequest的功能就基本都实现了,我们也是借助这两个例子深刻地理解了自定义Request的方法,对Volley的认识也是更加深入了。好了,本篇文章就到此结束,下篇文章中我们将对Volley进行更深层次的研究