Android Volley 研究综述(三)

文章目录
  1. 1. 自定义 XMLRequest 的使用
  2. 2. 自定义 GsonRequest 的使用
  3. 3. 参考文献

种一棵树最好的时间是十年前,其次是现在 。

众多请求在工具箱里随时准备使用实施;如果你的响应是字符串,图片,或者是 JSON,你或许不需要实现自定义请求。本篇在 Android Volley 研究综述(二) 的基础上继续展开,分析关于 Volley 实现自定义的请求。

对于你需要实现自定义请求的场合,这是所有你需要做的:

  • 继承 Request<T> 类,<T>代表请求预期所解析过响应的类型。因此,例如,假设你解析的响应是一个字符串,通过继承 Request<String>来创建你的自定义请求。查看 Volley 工具类 StringRequest 和 ImageRequest,来作为继承 Request<T>的例子。
  • 实现抽象方法如 ParseNetworkResponse() 和 deliverResponse()。

以上来自官网,众所周知,在网络上传输的数据通常有 JSON 和 XML 两种格式,下面利用 Volley 超强的扩展机制,先谈谈请求一条 XML 格式数据的做法。

自定义 XMLRequest 的使用

XMLRequestDemo 地址: XMLRequestDemo

首先,看 StringRequest 的源码,研究它是怎么实现的,再模仿着写出 XMLRequest。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* A canned request for retrieving the response body at a given URL as a string.
*/
public class StringRequest extends Request<String> {

private final Response.Listener<String> mListener;

/**
* 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, Response.Listener<String> listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}

/**
* 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, Response.Listener<String> listener,
Response.ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}

@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}

@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
}

不得不说,看 Google 工程师们的代码真得是种享受。简单分析该源码如下:

首先,StringRequest 继承自 Request 类,Request 指定泛型类为 String。

其次,StringRequest 提供了两个有参的构造方法,参数包括请求类型、请求地址和响应回调等。需要注意的是,在构造方法中要调用 super() 方法将这几个参数传给父类,HTTP 的请求和响应都是在父类中自动处理的。

最后,StringRequest 需要实现 Request 类中 deliverResponse() 和 parseNetworkResponse() 这两个抽象方法。deliverResponse() 中,调用了 mListener 的 onResponse() 方法,然后将 response 传入,即回调了服务器响应的数据;parseNetworkResponse() 中对服务器响应的数据进行解析,其中数据是以字节的形式存放在 NetworkResponse 的 data 变量中,这里将数据取出,再组装成一个 String,最后传到 Response 的 success() 方法中。

需要注意的是,parseNetworkResponse() 将 NetworkResponse 作为它的参数,其以 byte[],HTTP 状态码以及头响应的形式包含响应负载。你的实现必须返回一个 Response<T>,它包含了指定类型的响应对象和缓存元数据或者一个错误,比如在解析失败的情况下。

学习了 StringRequest 的原理,实现 XMLRequest 如下,新建一个 XMLRequest 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class XMLRequest extends Request<XmlPullParser> {

private final Response.Listener<XmlPullParser> mListener;

public XMLRequest(int method, String url, Response.Listener<XmlPullParser> listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}

public XMLRequest(String url, Response.Listener<XmlPullParser> listener,
Response.ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}

@Override
protected void deliverResponse(XmlPullParser response) {
mListener.onResponse(response);
}

@Override
protected Response<XmlPullParser> parseNetworkResponse(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));
}
}
}

以上,基本是仿照 StringRequest 写下来的,XMLRequest 也是继承自 Request 类,这里指定的泛型类是 XmlPullParser,即使用 Pull 解析的方式来解析 XML。XmlPullParserFactory 是一个工厂,用于构建XmlPullParser 对象。在 parseNetworkResponse() 方法中,先是将服务器响应的数据解析成一个字符串,然后设置到 XmlPullParser 对象中,最后在 deliverResponse() 方法中回调 XmlPullParser 对象。

下面使用自定义的 XMLRequest 来请求一段 XML 格式的数据,该天气接口将中国所有省份及自治区的当天天气数据以 XML 格式返回。确定访问接口,使用 XMLRequest 如下,同样是在 MainActivity 的 onCreate() 方法里,思路大体和之前两篇文章一样,不分条说,直接放上整块代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
RequestQueue queue = Volley.newRequestQueue(this);

String url = "http://flash.weather.com.cn/wmaps/xml/china.xml";

XMLRequest xmlRequest = new XMLRequest(url,
new Response.Listener<XmlPullParser>() {

@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 proName = response.getAttributeValue(0);
Log.d("TAG", "proName is " + proName);
}
break;
}
eventType = response.next();
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {

@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});

try{} 语句块里是事件类型的处理。简单分析以上代码,先创建一个 XMLRequest 的实例,传入服务器接口地址,然后在 onResponse() 方法中解析响应的 XML 数据,选择性地只将每个省份及自治区的名字打印出来,最后将 XMLRequest 对象添加到 RequestQueue 当中。运行程序,查看 Logcat,部分如下:

1
2
3
4
5
6
7
05-05 11:55:20.233 20037-20037/com.iamasoldier6.xmlrequestdemo D/TAG: proName is 北京
05-05 11:55:20.233 20037-20037/com.iamasoldier6.xmlrequestdemo D/TAG: proName is 天津
05-05 11:55:20.233 20037-20037/com.iamasoldier6.xmlrequestdemo D/TAG: proName is 上海
05-05 11:55:20.233 20037-20037/com.iamasoldier6.xmlrequestdemo D/TAG: proName is 重庆
05-05 11:55:20.233 20037-20037/com.iamasoldier6.xmlrequestdemo D/TAG: proName is 香港
05-05 11:55:20.233 20037-20037/com.iamasoldier6.xmlrequestdemo D/TAG: proName is 澳门
05-05 11:55:20.233 20037-20037/com.iamasoldier6.xmlrequestdemo D/TAG: proName is 台湾

自定义 GsonRequest 的使用

GsonRequestDemo 地址: GsonRequestDemo

Gson 是一个使用映射以支持 Java 对象与 Json 之间相互转换的库文件。你可以定义和它们相应的 Json 键值同名的 Java 对象,传递类对象给 Gson,然后 Gson 将帮你去填充相应区域。

以上来自官网,总结来说,Gson 可以更简单地解析 Json。但是,Volley 中并不支持 Gson 来解析数据,下面来自定义一个 GsonRequest。

首先,下载 Gson,地址:google / gson,然后将 gson 的 jar 包添加到项目中。其他一些准备工作也自不必多说。

其次,新建一个 GsonRequest 类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class GsonRequest<T> extends Request<T> {

private final Gson gson = new Gson();
private final Class<T> clazz;
private final Response.Listener<T> listener;

public GsonRequest(String url, Class<T> clazz,
Response.Listener<T> listener, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
this.clazz = clazz;
this.listener = listener;
}

@Override
protected void deliverResponse(T response) {
listener.onResponse(response);
}

@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String json = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
return Response.success(gson.fromJson(json, clazz),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JsonSyntaxException e) {
return Response.error(new ParseError(e));
}
}
}

GsonRequest 继承自 Request 类,类似地,parseNetworkResponse() 方法中,先是将服务器响应的数据解析出来,再通过调用 Gson 的 fromJson() 方法将数据组装成对象。deliverResponse() 方法中,回调最终的数据。打算调用天气数据,由于中国天气网的接口原因,返回的城市名有点乱码,不过总归是 JSON 格式的。

然后,使用对象的方式将 JSON 字符串表示出来。新建一个 Weather 类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class Weather {

private WeatherInfo weatherinfo;

public WeatherInfo getWeatherinfo() {
return weatherinfo;
}

public void setWeatherinfo(WeatherInfo weatherinfo) {
this.weatherinfo = weatherinfo;
}
}

Weather 类引用 WeatherInfo 类,再新建 WeatherInfo 类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class WeatherInfo {

private String city;

private String temp;

private String time;

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getTemp() {
return temp;
}

public void setTemp(String temp) {
this.temp = temp;
}

public String getTime() {
return time;
}

public void setTime(String time) {
this.time = time;
}
}

WeatherInfo 类中含有 city,temp,time 字段。当然,调用方法仍然与前述的类似,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
RequestQueue queue = Volley.newRequestQueue(this);

String url = "http://www.weather.com.cn/data/sk/101010100.html";

GsonRequest<Weather> gsonRequest = new GsonRequest<Weather>(url, Weather.class,
new Response.Listener<Weather>() {

@Override
public void onResponse(Weather weather) {
WeatherInfo weatherInfo = weather.getWeatherinfo();
Log.d("TAG", "city is " + weatherInfo.getCity());
Log.d("TAG", "temp is " + weatherInfo.getTemp());
Log.d("TAG", "time is " + weatherInfo.getTime());
}
}, new Response.ErrorListener() {

@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});

queue.add(gsonRequest);

onResponse() 方法回调中返回了一个 Weather 对象,通过它可以得到 WeatherInfo 对象,接着选择性地取出所要的 JSON 数据。运行程序:

1
2
3
05-05 03:09:10.538 1632-1632/com.iamasoldier6.gsonrequestdemo D/TAG: city is 北京
05-05 03:09:10.538 1632-1632/com.iamasoldier6.gsonrequestdemo D/TAG: temp is 18
05-05 03:09:10.538 1632-1632/com.iamasoldier6.gsonrequestdemo D/TAG: time is 17:05

显示的北京在 Android Studio 上是乱码的,这里手动改正了下。

本篇深刻地理解了自定义 Request 的的方法,至此,关于 Android Volley 研究综述(三)到此结束,(四)未完待续。

本着知其然,知其所以然的理念,(四)会是关于 Volley 源码的研究综述,待未来某天完善。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流

参考文献

1.http://developer.android.com/training/volley/request-custom.html

2.http://blog.csdn.net/guolin_blog/article/details/17612763