在 Volley 源码解析及对 Volley 的扩展
系列的第二篇文章中对 Volley 的部分源码进行了分析,也知道了 Volley 具有很强的扩展性。这边文章主要就是对 Volley 进行了一定的扩展,包括以下两部分内容,具体的代码可以参照我写的练手 Demo VolleyPractice。
- 自定义
Request
- 使用
OKHttp3
自定义OkHttpStack
自定义 Request
在项目的开发中,相信大多数的网络请求接口返回的数据都是 Json
格式的,接收到该 Json
对象以后,还要解析 Json
格式的结果并生成对应的 Model 类对象才能做进一步的操作。如果能稍微封装一下,使返回的结果就是想要的 Model 类对象,这样是不就很方便了呢?下面以 http://gank.io/api/data/Android/10/1
(该接口出自http://gank.io/api
) 为例,自定义 Request
,实现上述需求。
添加 Gson
为了方便进行 Json 结果的解析,使用 Google 官方的 Json 解析库 --- Gson,添加以下依赖:
compile 'com.google.code.gson:gson:2.8.0'
抽象类 Request
通过上篇博客知道如果想要实现自定义的 Request
,需要实现以下两个方法:
/**
* 子类必须实现此方法,用于解析网络请求的响应,并返回合适的类型对象。这个方法会在一个
* 工作线程中被调用(即不会在 UI 线程中调用此方法),如果此方法返回 null,则结果并不会被发送。
*
* @param response 来自于网络请求的响应
* @return 解析的结果,如果发生错误则返回 null
*/
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
/**
* 子类必须实现这个方法,把解析的结果发送给监听器。其中 T 类型的参数 response 要保证
* 不可以为 null,如果解析失败,则解析的结果不会通过此方法发送。
*
* @param response 通过 {@link #parseNetworkResponse(NetworkResponse)} 方法解析的结果
*/
abstract protected void deliverResponse(T response);
看一下 Volley 中默认的 Request
实现类 StringReqeust
的源码如下所示:
public class StringRequest extends Request<String> {
private final Listener<String> mListener;
/**
* 构造方法,创建一个 SringRequest 类的请求对象
*
* @param method 请求方法,参见 {@link Method}
* @param url 请求的 url 地址
* @param listener 接收请求结果的监听器
* @param errorListener 异常监听器,如果忽略异常可传入 null
*/
public StringRequest(int method, String url, Listener<String> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
/**
* 重载的构造方法,调用 {@ling StringRequest(int, String, Listener<String>, ErrorListener)} 构造方法实现
*/
public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
/**
* 通过构造方法中传入的 mListener 接口,回调接收到的请求响应结果
*/
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
/**
* 解析传入的网络请求响应 NetworkResponse 对象,并生成具体的结果
*/
@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));
}
}
定义 Model 对象
因为 http://gank.io/api/data/Android/10/1
返回的数据格式如下:
{
"error": false,
"results":
[
{
"_id": "593f2091421aa92c769a8c6a",
"createdAt": "2017-06-13T07:15:29.423Z",
"desc": "Android之自定义View:侧滑删除",
"publishedAt": "2017-06-15T13:55:57.947Z",
"source": "web",
"type": "Android",
"url": "https://mp.weixin.qq.com/s?__biz=MzIwMzYwMTk1NA==&mid=2247484934&idx=1&sn=f2a40261efe8ebee45804e9df93c1cce&chksm=96cda74ba1ba2e5dbbac15a9e57b5329176d1fe43478e5c63f7bc502a6ca50e4dfa6c0a9041e#rd",
"used": true,
"who": "陈宇明"
},
......
]
}
所以定义对应的 Model 类,如下所示:
public class AndroidModel {
public final boolean error;
public final List<Android> results;
public AndroidModel(boolean error, List<Android> results) {
this.error = error;
this.results = results;
}
}
public class Android {
public final String _id;
public final String createdAt;
public final String desc;
public final String publishedAt;
public final String source;
public final String type;
public final String url;
public final boolean used;
public final String who;
public Android(String _id, String createdAt, String desc,
String publishedAt, String source, String type,
String url, boolean used, String who) {
this._id = _id;
this.createdAt = createdAt;
this.desc = desc;
this.publishedAt = publishedAt;
this.source = source;
this.type = type;
this.url = url;
this.used = used;
this.who = who;
}
}
自定义GsonRequest
话不多说,直接上代码:
public class GsonRequest<T> extends Request<T> {
private Gson mGson = new Gson();
private Response.Listener<T> mListener = null;
private Class<T> mClass = null;
public GsonRequest(String url, Response.Listener<T> listener, Response.ErrorListener errorListener, Class<T> aClass) {
this(Method.GET, url, listener, errorListener, aClass);
}
public GsonRequest(int method, String url, Response.Listener<T> listener, Response.ErrorListener errorListener, Class<T> aClass) {
super(method, url, errorListener);
mListener = listener;
mClass = aClass;
}
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
String res = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
return Response.success(mGson.fromJson(res, mClass), HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(T response) {
if (mListener != null) {
mListener.onResponse(response);
}
}
}
- 使用泛型将需要得到的数据类型传入
GsonRequest
对象中,构造方法中的Class<T> aClass
是使用Gson
解析数据时需要使用的 - 通过
Response.Listener<T>
对象mListener
回调接收到的响应结果
如何使用
定义好 GsonRequest
之后,就可以尝试使用一下了,使用的方式如下所示:
GsonRequest<AndroidModel> request = new GsonRequest<AndroidModel>(mUrl,
new Response.Listener<AndroidModel>() {
@Override
public void onResponse(AndroidModel response) {
L.i("onResponse " + response.error);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
L.i("onErrorResponse " + error);
}
}, AndroidModel.class);
RequestQueue queue = Volley.newRequestQueue(MainActivity.this.getApplicationContext());
queue.add(request);
具体的用法和默认的 ***Request
的用法基本一样,只不过需要需要向 GsonRequest
中传入具体的 Model 类的 Class 类型。这样一看,使用 GsonRequest
进行网络请求是不是方便了很多,就不需要在自己进行 Json 数据解析,可以直接得到需要类的对象。
使用 OKHttp3 自定义 OkHttpStack
在第二篇博客分析 Volley
类的源码的时候,使用 Volley
的静态方法创建 RequestQueue
对象的时候,源码如下:
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
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;
}
一般我们使用一个参数的方法 newRequestQueue(Context)
即可创建 RequestQueue
对象,在此方法内部又是使用了 newRequestQueue(Context, HttpStack)
方法构建 RequestQueue
对象的,第二个参数 HttpStack
传入的是 null
,分析 newRequestQueue(Context, HttpStack)
方法的源码可见,如果传入的 HttpStack
对象为 null
,则创建一个 HttpStack
的对象,在 Android SDK 9(不包括9)之前使用 HttpClientStack
;在 Android SDK 9(包括9)之后使用 HurlStack
这两个类都实现了 HttpStack
接口,用于执行具体的网络请求。HurlStack
内部是使用 HttpURLConnection
类实现的,HttpClientStack
内部是使用 HttpClient
实现的。这两个类的区别如下所示:
在
Froyo(2.2)
之前,HttpURLConnection
有个重大 Bug,调用close()
函数会影响连接池,导致连接复用失效,所以在Froyo
之前使用HttpURLConnection
需要关闭keepAlive
。
另外在Gingerbread(2.3) HttpURLConnection
默认开启了gzip
压缩,提高了HTTPS
的性能,Ice Cream Sandwich(4.0) HttpURLConnection
支持了请求结果缓存。
再加上HttpURLConnection
本身 API 相对简单,所以对 Android 来说,在 2.3 之后建议使用HttpURLConnection
,之前建议使用AndroidHttpClient
。 --- Volley 源码解析
由此可见,我们完全可以自定义一个实现了 HttpStack
接口的类,使用两个参数的方法 newRequestQueue(Context, HttpStack)
创建一个 RequestQueue
对象。
现在 OkHttp3 使用的很广泛,而且优点很多,我就不多说了,那我们可以使用 OkHttp3 定义一个实现了 HttpStack
接口的类。照猫画虎,先分析一下 HurlStack
和 HttpClientStack
的源码。
HurlStack 源码解析
HttpStack
接口只有一个方法 performRequest(Request<?>, Map<String, String>)
,所以我们重点分析 HurlStack
中的 performRequest(Request<?>, Map<String, String>)
方法,源码如下:
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
// 请求的 url
String url = request.getUrl();
// 添加请求头
HashMap<String, String> map = new HashMap<String, String>();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
// 若 mUrlRewriter 对象不为空,则通过 `mUrlRewriter.rewriteUrl(url)` 重新改写 url
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
// 生成 URL 对象
URL parsedUrl = new URL(url);
// 生成 HttpURLConnection 对象 connection
HttpURLConnection connection = openConnection(parsedUrl, request);
// 向 connection 中添加请求头
for (String headerName : map.keySet()) {
connection.addRequestProperty(headerName, map.get(headerName));
}
// 根据不同的请求方法向 connection 对象中添加请求的参数
setConnectionParametersForRequest(connection, request);
// Initialize HttpResponse with data from the HttpURLConnection.
// 通过 connection 请求服务器,得到响应,并初始化 HttpResponse 对象
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
// -1 is returned by getResponseCode() if the response code could not be retrieved.
// Signal to the caller that something was wrong with the connection.
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
StatusLine responseStatus = new BasicStatusLine(protocolVersion,
connection.getResponseCode(), connection.getResponseMessage());
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
response.setEntity(entityFromConnection(connection));
}
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
response.addHeader(h);
}
}
return response;
}
对上面的源码分析如下:
- 首先得到
String
类型的url
- 创建
Map<String, String>
类型的对象map
,并向其添加请求头。请求头来自两部分,一部分来自request.getHeaders()
; 另一部分来自从该方法传入的additionalHeaders
- 若
UrlRewriter
接口类型的的mUrlRewriter
对象不为空,则通过UrlRewriter.rewriteUrl(String)
重写url
对象,并得到重写后的url
。UrlRewriter
源码如下:
/**
* An interface for transforming URLs before use.
*/
public interface UrlRewriter {
/**
* Returns a URL to use instead of the provided one, or null to indicate
* this URL should not be used at all.
*/
public String rewriteUrl(String originalUrl);
}
- 接着通过
openConnection(URL, Request<?>)
方法新建一个HttpURLConnection
对象,该方法主要是打开一个连接,设置超时响应时间;如果SSLSocketFactory
类型的对象mSslSocketFactory
不为空,则设置HTTPS
;设置是否使用缓存等。
/**
* Opens an {@link HttpURLConnection} with parameters.
* @param url
* @return an open connection
* @throws IOException
*/
private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
HttpURLConnection connection = createConnection(url);
int timeoutMs = request.getTimeoutMs();
connection.setConnectTimeout(timeoutMs);
connection.setReadTimeout(timeoutMs);
connection.setUseCaches(false);
connection.setDoInput(true);
// use caller-provided custom SslSocketFactory, if any, for HTTPS
if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
}
return connection;
}
/**
* Create an {@link HttpURLConnection} for the specified {@code url}.
*/
protected HttpURLConnection createConnection(URL url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// Workaround for the M release HttpURLConnection not observing the
// HttpURLConnection.setFollowRedirects() property.
// https://code.google.com/p/android/issues/detail?id=194495
connection.setInstanceFollowRedirects(HttpURLConnection.getFollowRedirects());
return connection;
}
- 然后通过一个循环,向
HttpURLConnection
类的连接对象connection
设置请求头 - 使用
setConnectionParametersForRequest(HttpURLConnection, Request<?>)
方法设置请求方法和请求体,如下所示:
@SuppressWarnings("deprecation")
/* package */ static void setConnectionParametersForRequest(HttpURLConnection connection,
Request<?> request) throws IOException, AuthFailureError {
switch (request.getMethod()) {
case Method.DEPRECATED_GET_OR_POST:
// This is the deprecated way that needs to be handled for backwards compatibility.
// If the request's post body is null, then the assumption is that the request is
// GET. Otherwise, it is assumed that the request is a POST.
byte[] postBody = request.getPostBody();
if (postBody != null) {
// Prepare output. There is no need to set Content-Length explicitly,
// since this is handled by HttpURLConnection using the size of the prepared
// output stream.
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.addRequestProperty(HEADER_CONTENT_TYPE,
request.getPostBodyContentType());
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(postBody);
out.close();
}
break;
case Method.GET:
// Not necessary to set the request method because connection defaults to GET but
// being explicit here.
connection.setRequestMethod("GET");
break;
case Method.DELETE:
connection.setRequestMethod("DELETE");
break;
case Method.POST:
connection.setRequestMethod("POST");
addBodyIfExists(connection, request);
break;
case Method.PUT:
connection.setRequestMethod("PUT");
addBodyIfExists(connection, request);
break;
case Method.HEAD:
connection.setRequestMethod("HEAD");
break;
case Method.OPTIONS:
connection.setRequestMethod("OPTIONS");
break;
case Method.TRACE:
connection.setRequestMethod("TRACE");
break;
case Method.PATCH:
connection.setRequestMethod("PATCH");
addBodyIfExists(connection, request);
break;
default:
throw new IllegalStateException("Unknown method type.");
}
}
- 接着创建
ProtocolVersion
对象和StatusLine
对象,并生产BasicHttpResponse
对象response
- 通过
hasResponseBody(int requestMethod, int responseCode)
方法判断响应中是否有响应体,若有响应体则通过entityFromConnection(HttpURLConnection)
创建响应体HttpEntity
对象,并设置给response
private static boolean hasResponseBody(int requestMethod, int responseCode) {
return requestMethod != Request.Method.HEAD
&& !(HttpStatus.SC_CONTINUE <= responseCode && responseCode < HttpStatus.SC_OK)
&& responseCode != HttpStatus.SC_NO_CONTENT
&& responseCode != HttpStatus.SC_NOT_MODIFIED;
}
private static HttpEntity entityFromConnection(HttpURLConnection connection) {
BasicHttpEntity entity = new BasicHttpEntity();
InputStream inputStream;
try {
inputStream = connection.getInputStream();
} catch (IOException ioe) {
inputStream = connection.getErrorStream();
}
entity.setContent(inputStream);
entity.setContentLength(connection.getContentLength());
entity.setContentEncoding(connection.getContentEncoding());
entity.setContentType(connection.getContentType());
return entity;
}
- 通过如下代码为
response
设置响应头,并返回响应对象
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
response.addHeader(h);
}
}
HttpClientStack 源码解析
和分析 HurlStack
源码类似,直接分析其中的 performRequest(Request<?>, Map<String, String>)
方法,源码如下:
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
addHeaders(httpRequest, additionalHeaders);
addHeaders(httpRequest, request.getHeaders());
onPrepareRequest(httpRequest);
HttpParams httpParams = httpRequest.getParams();
int timeoutMs = request.getTimeoutMs();
// TODO: Reevaluate this connection timeout based on more wide-scale
// data collection and possibly different for wifi vs. 3G.
HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
return mClient.execute(httpRequest);
}
分析如下:
- 通过
createHttpRequest(Request<?>, Map<String, String>)
方法生成HttpUriRequest
类型的对象httpRequest
,在生成的httpRequest
对象中已经设置了请求的url、请求方式和请求体
@SuppressWarnings("deprecation")
/* protected */ static HttpUriRequest createHttpRequest(Request<?> request,
Map<String, String> additionalHeaders) throws AuthFailureError {
switch (request.getMethod()) {
case Method.DEPRECATED_GET_OR_POST: {
// This is the deprecated way that needs to be handled for backwards compatibility.
// If the request's post body is null, then the assumption is that the request is
// GET. Otherwise, it is assumed that the request is a POST.
byte[] postBody = request.getPostBody();
if (postBody != null) {
HttpPost postRequest = new HttpPost(request.getUrl());
postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType());
HttpEntity entity;
entity = new ByteArrayEntity(postBody);
postRequest.setEntity(entity);
return postRequest;
} else {
return new HttpGet(request.getUrl());
}
}
case Method.GET:
return new HttpGet(request.getUrl());
case Method.DELETE:
return new HttpDelete(request.getUrl());
case Method.POST: {
HttpPost postRequest = new HttpPost(request.getUrl());
postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
setEntityIfNonEmptyBody(postRequest, request);
return postRequest;
}
case Method.PUT: {
HttpPut putRequest = new HttpPut(request.getUrl());
putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
setEntityIfNonEmptyBody(putRequest, request);
return putRequest;
}
case Method.HEAD:
return new HttpHead(request.getUrl());
case Method.OPTIONS:
return new HttpOptions(request.getUrl());
case Method.TRACE:
return new HttpTrace(request.getUrl());
case Method.PATCH: {
HttpPatch patchRequest = new HttpPatch(request.getUrl());
patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType());
setEntityIfNonEmptyBody(patchRequest, request);
return patchRequest;
}
default:
throw new IllegalStateException("Unknown request method.");
}
}
- 通过
addHeaders(HttpUriRequest, Map<String, String>)
向httpRequest
对象中添加请求头,请求头来自两部分,和HurlStack
是一样的
private static void addHeaders(HttpUriRequest httpRequest, Map<String, String> headers) {
for (String key : headers.keySet()) {
httpRequest.setHeader(key, headers.get(key));
}
}
- 设置超时连接
- 然后调用
HttpClient
对象的execute
方法,获得HttpResponse
对象并返回
自定义 OkHttpStack
通过分析 HurlStack
和 HttpClientStack
源码可知,在其中的 performRequest(Request<?>, Map<String, String>)
方法中大致有以下几个行为:
- 设置请求头,请求头来自两个地方:一个来自
request.getHeaders()
方法,另一个来自从方法传入的参数additionalHeaders
- 设置请求方法和请求体
- 设置超时时间
- 生成响应对象并向响应对象中添加参数,包括:响应头和响应体
有了如上的流程,OkHttpStack
也是这样的流程。
要使用 OKHttp3 库需要在 gradle
中添加对 OKHttp3 的依赖,如下所示:
compile 'com.squareup.okhttp3:okhttp:3.8.0'
OkHttpStack
源码如下:
public class OkHttpStack implements HttpStack {
private final OkHttpClient mOkHttpClient;
public OkHttpStack(OkHttpClient okHttpClient) {
if (okHttpClient == null) {
throw new IllegalArgumentException("OkHttpClient can't be null");
}
mOkHttpClient = okHttpClient;
}
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
int timeoutMs = request.getTimeoutMs();
OkHttpClient okHttpClient = mOkHttpClient
.newBuilder()
.connectTimeout(timeoutMs, TimeUnit.MILLISECONDS)
.readTimeout(timeoutMs, TimeUnit.MILLISECONDS)
.writeTimeout(timeoutMs, TimeUnit.MILLISECONDS)
.build();
HashMap<String, String> headers = new HashMap<String, String>();
headers.putAll(request.getHeaders());
headers.putAll(additionalHeaders);
okhttp3.Request.Builder builder = new okhttp3.Request
.Builder()
.url(request.getUrl());
for (String key : headers.keySet()) {
builder.header(key, headers.get(key));
}
setConnectionParametersForRequest(builder, request);
okhttp3.Request okRequest = builder.build();
Call call = okHttpClient.newCall(okRequest);
Response okResponse = call.execute();
BasicStatusLine responseStatus = new BasicStatusLine(
parseProtocol(okResponse.protocol()),
okResponse.code(),
okResponse.message()
);
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
response.setEntity(entityFromOkHttpResponse(okResponse));
Headers responseHeaders = okResponse.headers();
int size = responseHeaders.size();
String name;
String value;
for (int i = 0; i < size; i++) {
name = responseHeaders.name(i);
value = responseHeaders.value(i);
if (value != null) {
response.addHeader(new BasicHeader(name, value));
}
}
return response;
}
private void setConnectionParametersForRequest(okhttp3.Request.Builder builder, Request<?> request)
throws 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<?> r) throws AuthFailureError {
final byte[] body = r.getBody();
if (body == null) return null;
return RequestBody.create(MediaType.parse(r.getBodyContentType()), body);
}
private static ProtocolVersion parseProtocol(final Protocol p) {
switch (p) {
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");
}
private static HttpEntity entityFromOkHttpResponse(Response r) throws IOException {
BasicHttpEntity entity = new BasicHttpEntity();
ResponseBody body = r.body();
entity.setContent(body.byteStream());
entity.setContentLength(body.contentLength());
entity.setContentEncoding(r.header("Content-Encoding"));
if (body.contentType() != null) {
entity.setContentType(body.contentType().type());
}
return entity;
}
}
以分析 performRequest(Request<?>, Map<String, String>)
方法为线索,进行分析:
- 通过下面代码生成一个
OkHttpClient
对象,并设置超时时间
int timeoutMs = request.getTimeoutMs();
OkHttpClient okHttpClient = mOkHttpClient
.newBuilder()
.connectTimeout(timeoutMs, TimeUnit.MILLISECONDS)
.readTimeout(timeoutMs, TimeUnit.MILLISECONDS)
.writeTimeout(timeoutMs, TimeUnit.MILLISECONDS)
.build();
- 通过下面代码设置请求的
url
和请求头
HashMap<String, String> headers = new HashMap<String, String>();
headers.putAll(request.getHeaders());
headers.putAll(additionalHeaders);
okhttp3.Request.Builder builder = new okhttp3.Request
.Builder()
.url(request.getUrl());
for (String key : headers.keySet()) {
builder.header(key, headers.get(key));
}
- 通过
setConnectionParametersForRequest(okhttp3.Request.Builder, Request<?>)
方法设置请求方式和请求体
private void setConnectionParametersForRequest(okhttp3.Request.Builder builder, Request<?> request)
throws 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.");
}
}
- 通过如下代码得到 OKHttp3 的响应,注意:下面得到的响应是
Response
类型的,并不是HttpResponse
类型的,需要通过它生成对应的HttpResponse
对象才可以返回
okhttp3.Request okRequest = builder.build();
Call call = okHttpClient.newCall(okRequest);
Response okResponse = call.execute();
- 通过下面的代码和
entityFromOkHttpResponse(Response)
方法生成BasicHttpResponse
类型的对象response
,并设置response
的响应体。
BasicStatusLine responseStatus = new BasicStatusLine(
parseProtocol(okResponse.protocol()),
okResponse.code(),
okResponse.message()
);
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
response.setEntity(entityFromOkHttpResponse(okResponse));
``` Java
private static HttpEntity entityFromOkHttpResponse(Response r) throws IOException {
BasicHttpEntity entity = new BasicHttpEntity();
ResponseBody body = r.body();
entity.setContent(body.byteStream());
entity.setContentLength(body.contentLength());
entity.setContentEncoding(r.header("Content-Encoding"));
if (body.contentType() != null) {
entity.setContentType(body.contentType().type());
}
return entity;
}
```
- 通过如下方法设置响应头
Headers responseHeaders = okResponse.headers();
int size = responseHeaders.size();
String name;
String value;
for (int i = 0; i < size; i++) {
name = responseHeaders.name(i);
value = responseHeaders.value(i);
if (value != null) {
response.addHeader(new BasicHeader(name, value));
}
}
通过上面六步便大功告成了。
OkHttpStack 的使用
可以通过如下方式使用 OkHttpStack
RequestQueue queue = Volley.newRequestQueue(MainActivity.this,
new OkHttpStack(OkHttpManager.getInstance().getHttpClient()));
StringRequest request = new StringRequest(mUrl, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
L.i("onResponse " + response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
L.i("onErrorResponse " + error);
}
});
queue.add(request);
除了在生成 RequestQueue
对象的时候,和普通的使用 Volley 不同,其他都是一样的。其中 OkHttpManager.getInstance().getHttpClient()
是我对 OKHttp3 进行的一个简单的封装,返回的是一个 OKHttpClient
的对象
至此,关于Volley 源码解析及对 Volley 的扩展
系列的第三篇文章也结束了,主要介绍了自定义 Request
类,并使用 GsonReqeust
举例;并使用 OkHttp
自定义了实现 HttpStack
接口的 OkHttpStack
类,这样通过 OkHttpStack
创建的 RequestQueue
对象处理的请求使用的都是 OKHttp3 了。
至此 Volley 源码解析及对 Volley 的扩展
系列的文章全部结束了,其中的代码都在 GitHub 上的 VolleyPractice 工程里,
如果有什么问题欢迎指出。我的工作邮箱:jiankunli24@gmail.com
参考资料:
网友评论