上QQ阅读APP看书,第一时间看更新
6.5 Volley网络库简介
6.5.1 Volley网络库
Volley库是Google官方提供的开源网络库,在Android系统中也使用了这个网络库。
Volley库对网络功能进行了封装,默认根据Android系统的不同版本使用不同的HTTP协议栈。在Android 2.3及以上版本使用HurlStack协议栈,在Android 2.3以下版本使用HttpClientStack协议栈。使用者也可以自己设置其中使用的HTTP协议栈,使用比较灵活。
Volley库支持字符串、图片和JSON格式数据的处理,但因为在解析服务器端的响应消息时,Volley库是把响应消息存储在内存中,所以Volley库不适合大数据量的网络请求,如下载大文件等。
在Volley库的Volley类中,提供了设置请求HTTP协议栈的方法。
public class Volley { /** Default on-disk cache directory. */ private static final String DEFAULT_CACHE_DIR = "volley"; /** * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. * * @param context A {@link Context} to use for creating the cache dir. * @param stack An {@link HttpStack} to use for the network, or null for default. * @return A started {@link RequestQueue} instance. */ public static RequestQueue newRequestQueue(Context context, HttpStack stack){ File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); String userAgent = "volley/0"; try { String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo (packageName, 0); userAgent = packageName + "/" + info.versionCode; } catch (NameNotFoundException e) { } if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { // Prior to Gingerbread, HttpUrlConnection was unreliable. // See: http://android-developers.blogspot.com/2011/09/androids-http- clients.html stack = new HttpClientStack(AndroidHttpClient.newInstance (userAgent)); } } Network network = new BasicNetwork(stack); RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); queue.start(); return queue; } /** * Creates a default instance of the worker pool and calls {@link RequestQueue #start()} on it. * * @param context A {@link Context} to use for creating the cache dir. * @return A started {@link RequestQueue} instance. */ public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, null); } }
在Volley库的Request类中,定义了Volley库支持的HTTP方法。
public abstract class Request<T> implements Comparable<Request<T>> { … /** * Supported request methods. */ public interface Method { int DEPRECATED_GET_OR_POST = -1; int GET = 0; int POST = 1; int PUT = 2; int DELETE = 3; int HEAD = 4; int OPTIONS = 5; int TRACE = 6; int PATCH = 7; } … public Request(int method, String url, Response.ErrorListener listener) { mMethod = method; mUrl = url; mIdentifier = createIdentifier(method, url); mErrorListener = listener; setRetryPolicy(new DefaultRetryPolicy()); mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url); } … }
在Volley库中,HttpHeaderParser类用于处理从服务器获得的头字段数据。
public class HttpHeaderParser { public static Cache.Entry parseCacheHeaders(NetworkResponse response) { long now = System.currentTimeMillis(); Map<String, String> headers = response.headers; long serverDate = 0; long lastModified = 0; long serverExpires = 0; long softExpire = 0; long finalExpire = 0; long maxAge = 0; long staleWhileRevalidate = 0; boolean hasCacheControl = false; boolean mustRevalidate = false; String serverEtag = null; String headerValue; headerValue = headers.get("Date"); if (headerValue ! = null) { serverDate = parseDateAsEpoch(headerValue); } headerValue = headers.get("Cache-Control"); if (headerValue ! = null) { hasCacheControl = true; String[] tokens = headerValue.split(", "); for (int i = 0; i < tokens.length; i++) { String token = tokens[i].trim(); if (token.equals("no-cache") || token.equals("no-store")) { return null; } else if (token.startsWith("max-age=")) { try { maxAge = Long.parseLong(token.substring(8)); } catch (Exception e) { } } else if (token.startsWith("stale-while-revalidate=")) { try { staleWhileRevalidate = Long.parseLong(token.substring(23)); } catch (Exception e) { } } else if(token.equals("must-revalidate")||token.equals ("proxy-revalidate")) { mustRevalidate = true; } } } headerValue = headers.get("Expires"); if (headerValue ! = null) { serverExpires = parseDateAsEpoch(headerValue); } headerValue = headers.get("Last-Modified"); if (headerValue ! = null) { lastModified = parseDateAsEpoch(headerValue); } serverEtag = headers.get("ETag"); // Cache-Control takes precedence over an Expires header, even if both exist and Expires // is more restrictive. if (hasCacheControl) { softExpire = now + maxAge * 1000; finalExpire = mustRevalidate ? softExpire : softExpire + staleWhileRevalidate * 1000; } else if (serverDate > 0 && serverExpires >= serverDate) { // Default semantic for Expire header in HTTP specification is softExpire. softExpire = now + (serverExpires - serverDate); finalExpire = softExpire; } Cache.Entry entry = new Cache.Entry(); entry.data = response.data; entry.etag = serverEtag; entry.softTtl = softExpire; entry.ttl = finalExpire; entry.serverDate = serverDate; entry.lastModified = lastModified; entry.responseHeaders = headers; return entry; } … }
目前大多数APP与服务器间传输数据时,都是使用JSON格式,Volley库中提供了使用JSON格式传递数据的类。
public abstract class JsonRequest<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 String mRequestBody; /** * Deprecated constructor for a JsonRequest which defaults to GET unless {@link #getPostBody()} * or {@link #getPostParams()} is overridden (which defaults to POST). * * @deprecated Use {@link #JsonRequest(int, String, String, Listener, ErrorListener)}. */ public JsonRequest(String url, String requestBody, Listener<T> listener, ErrorListener errorListener) { this(Method.DEPRECATED_GET_OR_POST, url, requestBody, listener, errorListener); } public JsonRequest(int method, String url, String requestBody, Listener<T> listener, ErrorListener errorListener) { super(method, url, errorListener); mListener = listener; mRequestBody = requestBody; } @Override protected void deliverResponse(T response) { mListener.onResponse(response); } @Override abstract protected Response<T> parseNetworkResponse(NetworkResponse response); /** * @deprecated Use {@link #getBodyContentType()}. */ @Override public String getPostBodyContentType() { return getBodyContentType(); } /** * @deprecated Use {@link #getBody()}. */ @Override public byte[] getPostBody() { return getBody(); } @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; } } } 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 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)); } } }
6.5.2 Volley网络库的使用
为了使用方便,可以对Volley库提供的类和方法进行二次封装,代码如下所示。
public class JsonRequest extends JsonObjectRequest { String cookieString; public JsonRequest(String url, JSONObject jsonRequest, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) { super(url, jsonRequest, listener, errorListener); } //自定义向服务器发送的头字段数据 @Override public Map<String, String> getHeaders() throws AuthFailureError { HashMap<String, String> headers = new HashMap<String, String>(); //标示是Android APP向服务器发起请求 headers.put("AppKey", "Android"); //设置User-Agent的内容为APP的包名和版本信息,标示是哪个APP向服务器发起请求 String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); userAgent = packageName + "/" + info.versionCode; headers.put("User-Agent", userAgent ); //把服务器返回给APP的Cookie信息,添加到APP发给服务器的头信息中,标示访问服务器的客户身份 headers.put("Cookie", cookieString); return headers; } @Override protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) { try { String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET)); //获取服务器返回给APP的Cookie信息 Map<String, String> headers = response.headers; cookieString = headers.get("Set-Cookie"); //解析服务器返回的cookie信息,从中获取SessionID String sessionId = parseVooleyCookie(cookies); 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)); } } //解析cookie数据 public String parseVooleyCookie(String cookie) { StringBuilder sb = new StringBuilder(); String[] cookievalues = cookie.split("; "); for (int j = 0; j < cookievalues.length; j++) { String[] keyPair = cookievalues[j].split("/"); for (int i = 0; i < keyPair.length; i++) { if (keyPair[0].contains("session_id")) { sb.append(keyPair[0]); sb.append("; "); break; } } } return sb.toString(); } }
使用OkHttp3作为Volley库的HTTP协议栈,需要实现HttpStack里定义的接口方法。协议栈的具体代码如下:
public class OkHttp3Stack implements HttpStack { private final OkHttpClient mClient; public OkHttp3Stack(OkHttpClient client) { this.mClient = client; } @Override public HttpResponse performRequest(Request<? > request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { int timeoutMs = request.getTimeoutMs(); OkHttpClient client = mClient.newBuilder() .readTimeout(timeoutMs, TimeUnit.MILLISECONDS) .connectTimeout(timeoutMs, TimeUnit.MILLISECONDS) .writeTimeout(timeoutMs, TimeUnit.MILLISECONDS) .build(); okhttp3.Request.Builder okHttpRequestBuilder = new okhttp3.Request.Builder(); Map<String, String> headers = request.getHeaders(); for (final String name : headers.keySet()) { okHttpRequestBuilder.addHeader(name, headers.get(name)); } for (final String name : additionalHeaders.keySet()) { okHttpRequestBuilder.addHeader(name, additionalHeaders.get(name)); } setConnectionParametersForRequest(okHttpRequestBuilder, request); okhttp3.Request okhttp3Request = okHttpRequestBuilder.url(request.getUrl()).build(); Response okHttpResponse = client.newCall(okhttp3Request).execute(); StatusLine responseStatus = new BasicStatusLine ( parseProtocol(okHttpResponse.protocol()), okHttpResponse.code(), okHttpResponse.message() ); BasicHttpResponse response = new BasicHttpResponse(responseStatus); response.setEntity(entityFromOkHttpResponse(okHttpResponse)); Headers responseHeaders = okHttpResponse.headers(); for (int i = 0, len = responseHeaders.size(); i < len; i++) { final String name = responseHeaders.name(i), value = responseHeaders.value(i); if (name ! = null) { response.addHeader(new BasicHeader(name, value)); } } return response; } private static HttpEntity entityFromOkHttpResponse(Response response) throws IOException { BasicHttpEntity entity = new BasicHttpEntity(); ResponseBody body = response.body(); entity.setContent(body.byteStream()); entity.setContentLength(body.contentLength()); entity.setContentEncoding(response.header("Content-Encoding")); if (body.contentType() ! = null) { entity.setContentType(body.contentType().type()); } return entity; } @SuppressWarnings("deprecation") private static void setConnectionParametersForRequest (okhttp3.Request.Builder builder, Request<? > request) throws IOException, AuthFailureError { switch (request.getMethod()) { case Request.Method.DEPRECATED_GET_OR_POST: byte[] postBody = request.getPostBody(); if (postBody ! = null) { builder.post(RequestBody.create (MediaType.parse(request.getPostBodyContentType()), postBody)); } break; case Request.Method.GET: builder.get(); break; case Request.Method.DELETE: builder.delete(); break; case Request.Method.POST: builder.post(createRequestBody(request)); break; case Request.Method.PUT: builder.put(createRequestBody(request)); break; case Request.Method.HEAD: builder.head(); break; case Request.Method.OPTIONS: builder.method("OPTIONS", null); break; case Request.Method.TRACE: builder.method("TRACE", null); break; case Request.Method.PATCH: builder.patch(createRequestBody(request)); break; default: throw new IllegalStateException("Unknown method type."); } } private static RequestBody createRequestBody(Request request) throws AuthFailureError { final byte[] body = request.getBody(); if (body == null) return null; return RequestBody.create(MediaType.parse(request.getBodyContentType()), body); } private static ProtocolVersion parseProtocol(final Protocol protocol) { switch (protocol) { case HTTP_1_0: return new ProtocolVersion("HTTP", 1, 0); case HTTP_1_1: return new ProtocolVersion("HTTP", 1, 1); case SPDY_3: return new ProtocolVersion("SPDY", 3, 1); case HTTP_2: return new ProtocolVersion("HTTP", 2, 0); } throw new IllegalAccessError("Unkwown protocol"); } }
还需增加一个类,提供其他模块调用Volley库的接口方法。
public class NetworkManager { private static NetworkManager mInstance; private RequestQueue mRequestQueue; private static Context mCtx; private NetworkManager(Context context) { mCtx = context; mRequestQueue = getRequestQueue(); } //使用单列模式创建类的实例 public static synchronized NetworkManager getInstance(Context context) { if (mInstance == null) { mInstance = new NetworkManager(context); } return mInstance; } public RequestQueue getRequestQueue() { if (mRequestQueue == null) { //设置使用OkHttp3作为HTTP协议栈 mRequestQueue = Volley.newRequestQueue(mCtx, new OkHttp3Stack(new OkHttpClient())); } return mRequestQueue; } private <T> Request<T> add(Request<T> request) { return mRequestQueue.add(request); //添加请求到队列 } /** * 创建JSON格式的请求数据 * */ public void JsonRequest(Object tag, String url, JSONObject jsonObject, Response.Listener<JSONObject> listener, Response.ErrorListener errorListener) { JsonRequest jsonRequest; jsonRequest = new JsonRequest(url, jsonObject, listener, errorListener); jsonRequest.setTag(tag); add(jsonRequest); } /** * 取消请求 * */ public void cancel(Object tag) { mRequestQueue.cancelAll(tag); } }
函数调用方式如下:
NetworkManager.getInstance(EamApplication.getContext()).JsonRequest(TAG, baseUrl +
url, jsonObject,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject jsonObject) {
Log.v(TAG, "response json对象: " + jsonObject.toString());
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, error.getMessage(), error);
}
});
在创建网络对象实例时,使用Application级别的Context,保证实例的生命周期与APP的生命周期一样。