Android网络请求的好搭档-Volley和Gson

最近做了一个模块需要在Android客户端做网络请求,调用服务端提供的REST API。由于开发时间紧,网络请求次数和接口个数都比较多,如果使用自带的HttpURLConnection和HttpClient加上Java自身提供的Json库开发效率会很低,而网上的开源库例如okhttp或Retrofit我都缺乏了解。为了求稳,便使用了Google官方开发的网络库Volley和Json解析库Gson,使用起来发现非常的顺手,跟服务端提供的API几乎无缝衔接.

Volley

首先Volley库的使用可以参考Android开发的官方文档Training中的Transmitting Network Data Using Volley这一章的内容。最开始只需了解如何send a request,cancel a request,custom request这些基本的操作。接下来文档中的Use a Singleton Pattern将RequestQueue封装成单例的思想以及文档中非常重要的一张图:

都是值得好好学习的,可以了解到对于Volley最基本的封装以及在使用Volley库的过程中哪些代码运行在哪些线程的问题。

Gson

Gson是Google开发的将Json串和JavaBean相互转换的非常方便易用的库。Github地址在https://github.com/google/gson。具体的使用方法只要阅读README中的user guide就能掌握了。具体的功能有Json串和JavaBean的相互转换,包括数组,嵌套类,并且可以自定义一些内容,例如控制哪些字段需要操作哪些不需要,空值的操作等等。Gson库我认为比较难的一点在于如何定义JavaBean中字段的类型,尤其是在有泛型和容器类的情况下。

实际用法

我在使用Volley + Gson的组合的时候,首先参考了官方文档Implementing a Custom Request 中的一个Example:GsonRequest
稍做修改使其支持传入JSON格式的参数,并且规定字符集为utf-8

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class GsonRequest<T> extends Request<T> {

/** Default charset for JSON request. */
protected static final String PROTOCOL_CHARSET = "utf-8";

/** Content type for request. */
private static final String PROTOCOL_CONTENT_TYPE =
String.format("application/json; charset=%s", PROTOCOL_CHARSET);

private final Listener<T> mListener;
private final Gson mGson;
private final Class<T> mClass;
private String mRequestBody;

public GsonRequest(int method, String url, Class<T> clazz, Listener<T> listener,
ErrorListener errorListener, String requestBody) {
super(method, url, errorListener);
mGson = new Gson();
mClass = clazz;
mListener = listener;
mRequestBody = requestBody;
}

@Override
public String getBodyContentType() {
return PROTOCOL_CONTENT_TYPE;
}

@Override
public byte[] getBody() {
try {
return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
} catch (UnsupportedEncodingException uee) {
VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",
mRequestBody, PROTOCOL_CHARSET);
return null;
}
}

@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
jsonString = handleResponse(jsonString);
return Response.success(mGson.fromJson(jsonString, mClass),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JSONException e) {
return Response.error(new ParseError(e));
}
}

private String handleResponse(String response) throws JSONException {
// Handle response JSON
}

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

如果有上传文件的需求,可以将请求的Content-Type改为MultiPart类型的请求。新建一个MultipartRequest类继承GsonRequest类,参数存放在Part数组中,如果参数要增加文件就像数组中添加相应FilePart类,增加字符串就添加StringPart类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MultipartRequest<T> extends GsonRequest<T> {
private Part[] parts;

public MultipartRequest(String url, Class<T> clazz, Response.Listener<T> listener, Response.ErrorListener errorListener, Part[] parts) {
super(Method.POST, url, clazz, listener, errorListener, null);
this.parts = parts;
}

@Override
public String getBodyContentType() {
return "multipart/form-data; boundary=" + Part.getBoundary();
}

@Override
public byte[] getBody() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
Part.sendParts(baos, parts);
} catch (IOException e) {
VolleyLog.e(e, "error when sending parts to output!");
}
return baos.toByteArray();
}
}

接着对请求进行一层简单的封装(POST请求和GET请求的参数类型应与服务端API保持一致):

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
public class NetworkInterfaces {
private static final String URL = "REST API URL";
private RequestQueue mRequestQueue;
private static final String TAG = "MY_REQUEST";

public NetworkInterfaces(Context context) {
mRequestQueue = Volley.newRequestQueue(context.getApplicationContext());
}

private <T> void requestMultipartPost(String url, Class<T> claszz, Listener<T> listener, ErrorListener errorListener, Part[] parts) {
MultipartRequest<T> multipartRequest = new MultipartRequest(url, clazz, listener, errorListener, parts);
multipartRequest.setRetryPolicy(new DefaultRetryPolicy(0, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
multipartRequest.setTag(TAG);
mRequestQueue.add(multipartRequest);
}

private <T> void requestPost(String url, Class<T> clazz, Listener<T> listener, ErrorListener errorListener, JSONObject params) {
GsonRequest<T> gsonRequest = new GsonRequest<>(Method.POST, url, clazz, listener, errorListener, params == null ? null : params.toString());
gsonRequest.setRetryPolicy(new DefaultRetryPolicy(0, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
gsonRequest.setTag(TAG);
mRequestQueue.add(gsonRequest);
}

private <T> void requestGet(String url, Class<T> clazz, Listener<T> listener, ErrorListener errorListener, String params) {
url = url + "?" + params;
GsonRequest<T> gsonRequest = new GsonRequest<>(Method.GET, url, clazz, listener, errorListener, params);
gsonRequest.setRetryPolicy(new DefaultRetryPolicy(0, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
gsonRequest.setTag(TAG);
mRequestQueue.add(gsonRequest);
}

public void requestAPI1(Listener<API1ResponseBean> listener, ErrorListener errorListener, JSONObject params) {
requestPost(URL + "/api1", API1ResponseBean.class, listener, errorListener, params);
}

public void requestAPI2(Listener<API2ResponseBean> listener, ErrorListener errorListener, String params) {
requestGet(URL + "/api2", API2ResponseBean.class, listener, errorListener, params);
}

public void requestAPI3(Listener<API3ResponseBean> listener, ErrorListener errorListener, Part[] parts) {
requestMultipartPost(URL + "/api3", API3ResponseBean.class, listener, errorListener, parts);
}

public void onStop() {
if (mRequestQueue != null) {
mRequestQueue.cancelAll(TAG);
}
}
}

最后再根据Response的Json串格式定义相应的ResponseBean即可完成一个简单的网络请求层。

注意事项

  1. 在GsonResponse的parseNetworkResponse方法可以对返回的Json串进行处理使其与我们定义的JavaBean兼容(handleResponse方法),便于Gson库的转换。
  2. Request的setRetryPolicy可以设置请求的超时时长和重试次数。
  3. 在网络请求结束后可以调用mRequestQueue.cancelAll()来清空队列中剩余的请求,可以定义TAG来清除特定的网络请求。

最后

优秀的库往往包含了优秀的代码,在使用Volley和Gson之后大概阅读一下他们的源码,会发现层次结构非常清晰,针对接口编程,大量使用组合而不是继承,对外暴露的接口往往十分简单容易理解。其次扩展性很强,用户通常能根据实际的业务需求自定义相关的类,在原有功能的基础上实现自己需要的功能。这些都是非常值得学习的设计思想。