Volley源码解析及对Volley的扩展(三)

Volley 源码解析及对 Volley 的扩展系列的第二篇文章中对 Volley 的部分源码进行了分析,也知道了 Volley 具有很强的扩展性。这边文章主要就是对 Volley 进行了一定的扩展,包括以下两部分内容,具体的代码可以参照我写的练手 Demo VolleyPractice

  1. 自定义 Request
  2. 使用 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,添加以下依赖:

1
compile 'com.google.code.gson:gson:2.8.0'

抽象类 Request

通过上篇博客知道如果想要实现自定义的 Request,需要实现以下两个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 子类必须实现此方法,用于解析网络请求的响应,并返回合适的类型对象。这个方法会在一个
* 工作线程中被调用(即不会在 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 的源码如下所示:

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
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 返回的数据格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"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 类,如下所示:

1
2
3
4
5
6
7
8
9
10
11
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;
}
}

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
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

话不多说,直接上代码:

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
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);
}
}
}

  1. 使用泛型将需要得到的数据类型传入 GsonRequest 对象中,构造方法中的 Class<T> aClass 是使用 Gson 解析数据时需要使用的
  2. 通过 Response.Listener<T> 对象 mListener 回调接收到的响应结果

如何使用

定义好 GsonRequest 之后,就可以尝试使用一下了,使用的方式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 对象的时候,源码如下:

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 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 接口的类。照猫画虎,先分析一下 HurlStackHttpClientStack 的源码。

HurlStack 源码解析

HttpStack 接口只有一个方法 performRequest(Request<?>, Map<String, String>),所以我们重点分析 HurlStack 中的 performRequest(Request<?>, Map<String, String>) 方法,源码如下:

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
@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;
}

对上面的源码分析如下:

  1. 首先得到 String 类型的 url
  2. 创建 Map<String, String> 类型的对象 map,并向其添加请求头。请求头来自两部分,一部分来自 request.getHeaders(); 另一部分来自从该方法传入的 additionalHeaders
  3. UrlRewriter 接口类型的的 mUrlRewriter 对象不为空,则通过 UrlRewriter.rewriteUrl(String) 重写 url 对象,并得到重写后的 urlUrlRewriter 源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 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);
    }
  4. 接着通过 openConnection(URL, Request<?>) 方法新建一个 HttpURLConnection 对象,该方法主要是打开一个连接,设置超时响应时间;如果 SSLSocketFactory 类型的对象 mSslSocketFactory 不为空,则设置 HTTPS;设置是否使用缓存等。

    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
    /**
    * 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;
    }
  5. 然后通过一个循环,向 HttpURLConnection 类的连接对象 connection 设置请求头

  6. 使用 setConnectionParametersForRequest(HttpURLConnection, Request<?>) 方法设置请求方法和请求体,如下所示:

    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
    @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.");
    }
    }
  7. 接着创建 ProtocolVersion 对象和 StatusLine 对象,并生产 BasicHttpResponse 对象 response

  8. 通过 hasResponseBody(int requestMethod, int responseCode) 方法判断响应中是否有响应体,若有响应体则通过 entityFromConnection(HttpURLConnection) 创建响应体 HttpEntity 对象,并设置给 response

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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;
    }
  9. 通过如下代码为 response 设置响应头,并返回响应对象

    1
    2
    3
    4
    5
    6
    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>) 方法,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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);
}

分析如下:

  1. 通过 createHttpRequest(Request<?>, Map<String, String>) 方法生成 HttpUriRequest 类型的对象 httpRequest,在生成的 httpRequest 对象中已经设置了请求的url、请求方式和请求体

    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
    @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.");
    }
    }
  2. 通过 addHeaders(HttpUriRequest, Map<String, String>)httpRequest 对象中添加请求头,请求头来自两部分,和 HurlStack 是一样的

    1
    2
    3
    4
    5
    private static void addHeaders(HttpUriRequest httpRequest, Map<String, String> headers) {
    for (String key : headers.keySet()) {
    httpRequest.setHeader(key, headers.get(key));
    }
    }
  3. 设置超时连接

  4. 然后调用 HttpClient 对象的 execute 方法,获得 HttpResponse 对象并返回

自定义 OkHttpStack

通过分析 HurlStackHttpClientStack 源码可知,在其中的 performRequest(Request<?>, Map<String, String>) 方法中大致有以下几个行为:

  1. 设置请求头,请求头来自两个地方:一个来自 request.getHeaders() 方法,另一个来自从方法传入的参数 additionalHeaders
  2. 设置请求方法和请求体
  3. 设置超时时间
  4. 生成响应对象并向响应对象中添加参数,包括:响应头和响应体

有了如上的流程,OkHttpStack 也是这样的流程。
要使用 OKHttp3 库需要在 gradle 中添加对 OKHttp3 的依赖,如下所示:

1
compile 'com.squareup.okhttp3:okhttp:3.8.0'

OkHttpStack 源码如下:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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>) 方法为线索,进行分析:

  1. 通过下面代码生成一个 OkHttpClient 对象,并设置超时时间

    1
    2
    3
    4
    5
    6
    7
    int timeoutMs = request.getTimeoutMs();
    OkHttpClient okHttpClient = mOkHttpClient
    .newBuilder()
    .connectTimeout(timeoutMs, TimeUnit.MILLISECONDS)
    .readTimeout(timeoutMs, TimeUnit.MILLISECONDS)
    .writeTimeout(timeoutMs, TimeUnit.MILLISECONDS)
    .build();
  2. 通过下面代码设置请求的 url 和请求头

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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));
    }
  3. 通过 setConnectionParametersForRequest(okhttp3.Request.Builder, Request<?>) 方法设置请求方式和请求体

    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
    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.");
    }
    }
  4. 通过如下代码得到 OKHttp3 的响应,注意:下面得到的响应是 Response 类型的,并不是 HttpResponse 类型的,需要通过它生成对应的 HttpResponse 对象才可以返回

    1
    2
    3
    okhttp3.Request okRequest = builder.build();
    Call call = okHttpClient.newCall(okRequest);
    Response okResponse = call.execute();
  5. 通过下面的代码和 entityFromOkHttpResponse(Response) 方法生成 BasicHttpResponse 类型的对象 response,并设置 response 的响应体。

    1
    2
    3
    4
    5
    6
    7
    BasicStatusLine responseStatus = new BasicStatusLine(
    parseProtocol(okResponse.protocol()),
    okResponse.code(),
    okResponse.message()
    );
    BasicHttpResponse response = new BasicHttpResponse(responseStatus);
    response.setEntity(entityFromOkHttpResponse(okResponse));
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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;
    }
  6. 通过如下方法设置响应头

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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


参考资料:

Volley 源码解析grumoon

Android Volley 之自定义Request鸿洋_

Android 使用OkHttp扩展Volley_区长