在项目中使用OkHttp

What

对于OkHttp的简介就直接复制官网了:

HTTP is the way modern applications network. It’s how we exchange data & media. Doing HTTP efficiently makes your stuff load faster and saves bandwidth.

OkHttp is an HTTP client that’s efficient by default:

  • HTTP/2 support allows all requests to the same host to share a socket.
  • Connection pooling reduces request latency (if HTTP/2 isn’t available).
  • Transparent GZIP shrinks download sizes.
  • Response caching avoids the network completely for repeat requests.
  • Supports both synchronous blocking calls and async calls with callbacks.

OkHttp perseveres when the network is troublesome: it will silently recover from common connection problems. If your service has multiple IP addresses OkHttp will attempt alternate addresses if the first connect fails. This is necessary for IPv4+IPv6 and for services hosted in redundant data centers. OkHttp initiates new connections with modern TLS features (SNI, ALPN), and falls back to TLS 1.0 if the handshake fails.

Using OkHttp is easy. Its request/response API is designed with fluent builders and immutability. It supports both synchronous blocking calls and async calls with callbacks.

OkHttp supports Android 2.3 and above. For Java, the minimum requirement is 1.7.

Why

Android为我们提供了两种HTTP交互的方式:HttpURLConnectionApache-HTTPClient,虽然两者都支持HTTPS,流的上传和下载,配置超时,IPv6和连接池,已足够满足我们各种HTTP请求的需求。但更高效的使用HTTP可以让应用运行更快、更节省流量。而OkHttp就是为此而生。

那么除了更cool之外,为什么要用OkHttp替换掉项目里用了很久的HttpClientHttpURLConnection

首先,从实际使用的角度看,OkHttp除了具备HttpClientHttpURLConnection具有的功能以外,

  • 本身支持同步和异步请求,OkHttpRequestReponse现在都用了建造者模式,写起来更清晰,即使不封装代码量也并不大

一个典型的OkHttp异步请求:

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

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();

client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}

@Override public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}

System.out.println(response.body().string());
}
});
}

  • 共享同一个Socket来处理同一个服务器的所有请求,利用连接池技术减少请求延迟,缓存响应数据来减少重复的网络请求
  • 支持GZIP,不需要我们手动处理GZIP
  • 自带更强到的IO操作框架Okio
  • 从很多常用的连接问题中自动恢复
  • 如果服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP
  • 提供了强大的Interceptors(拦截器),我们可以通过注册自己的Interceptors来对每次请求进行监控和改写,比如最基础的打印每次请求的基本信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14

class LogInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();

Log.d("request url:",request.url());

Response response = chain.proceed(request);

Log.d("reponse url:",response.request().url());

return response;
}
}

另外从外部因素来说,Google官方从Android4.4的时候已经开始使用OkHttp,sdk23彻底抛弃了HttpClient而采用OkHttp。许多新的强大的开源库也默认支持OkHttp作为网络传输层,例如比Volley更快的Retrofit

OkHttp目前的这些优势应该已经足够有理由替换掉HttpClientHttpURLConnection了(单说网络请求传输速度,OkHttp并没有更快,因为HttpClient已经很强大了,它的优势主要还是在于配置简单,易用以及官方和开源社区的支持)。

How

比较典型的是替换Volley中的HttpStack

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
135
136
137
138
139
140
141
142
143
144
145
public class OkHttpStack implements HttpStack {

private OkHttpClient mOkHttpClient;

private final UrlRewriter mUrlRewriter;

public interface UrlRewriter {
public String rewriteUrl(String originalUrl);
}


public OkHttpStack(OkHttpClient okHttpClient) {
this(null, okHttpClient);
}

public OkHttpStack(UrlRewriter urlRewriter, OkHttpClient okHttpClient) {
this.mOkHttpClient = okHttpClient;
this.mUrlRewriter = urlRewriter;
}

@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
//加工url,需要UrlRewriter
String url = request.getUrl();
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}

//设置header
okhttp3.Request.Builder okHttpRequestBuilder = new okhttp3.Request.Builder();
Map<String, String> headers = new HashMap<String, String>();
headers.putAll(request.getHeaders());
headers.putAll(additionalHeaders);
for (String headerName : headers.keySet()) {
okHttpRequestBuilder.header(headerName, headers.get(headerName));
}

//设置connectionParameters
setConnectionParametersForRequest(okHttpRequestBuilder, request);

//发起请求
okhttp3.Request okHttpRequest = okHttpRequestBuilder.url(url).build();
Response okHttpResponse = mOkHttpClient.newCall(okHttpRequest).execute();

//转换Response
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 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;
}

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

private void setConnectionParametersForRequest(okhttp3.Request.Builder builder, Request<?> request) throws IOException, AuthFailureError {
byte[] postBody = null;
switch (request.getMethod()) {
case Request.Method.DEPRECATED_GET_OR_POST:
postBody = request.getBody();
if (postBody != null) {
builder.post(RequestBody.create(MediaType.parse(request.getBodyContentType()), postBody));
} else {
builder.get();
}
break;
case Request.Method.GET:
builder.get();
break;
case Request.Method.DELETE:
builder.delete();
break;
case Request.Method.POST:
postBody = request.getBody();
if (postBody == null) {
builder.post(RequestBody.create(MediaType.parse(request.getBodyContentType()), ""));
} else {
builder.post(RequestBody.create(MediaType.parse(request.getBodyContentType()), postBody));
}
break;
case Request.Method.PUT:
postBody = request.getBody();
if (postBody == null) {
builder.put(RequestBody.create(MediaType.parse(request.getBodyContentType()), ""));
} else {
builder.put(RequestBody.create(MediaType.parse(request.getBodyContentType()), postBody));
}
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:
postBody = request.getBody();
if (postBody == null) {
builder.patch(RequestBody.create(MediaType.parse(request.getBodyContentType()), ""));
} else {
builder.patch(RequestBody.create(MediaType.parse(request.getBodyContentType()), postBody));
}
break;
default:
throw new IllegalStateException("Unknown method type.");
}
}
}