okhttp初探

使用依赖最新的版本 implementation("com.squareup.okhttp3:okhttp:3.12.0")

网站:https://square.github.io/okhttp/

get 请求

1
2
3
4
5
6
7
8
9
10
11
public String getRequests() throws IOException {
OkHttpClient client = new OkHttpClient();

String url = "http://www.baidu.com";
Request request = new Request.Builder()
.url(url)
.build();

Response response = client.newCall(request).execute();
return response.body().string();
}

put 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

String json = "http://www.roundsapp.com/post";
String url = bowlingJson("Jesse","Jake");//json字符串

OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(JSON,json);

Request request = new Request.Builder()
.url(url)
.post(body)
.build();

Response response = client.newCall(request).execute();
return response.body().string();

总结

1.创建 OkHttpClient 对象

2.创建 Request

3.同步或者异步发出请求

4.得到 Response

同步执行:

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
@Override public Response execute() throws IOException {
//1.判断是否执行过,每个Call请求只能请求一次,如果需要重复执行可以使用clone 方法
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
//2.获取分发器 dispatcher, 执行 execute
client.dispatcher().executed(this);
//3.获取网络请求的结果
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
//4.最后通知 dispatcher 执行完毕
client.dispatcher().finished(this);
}
}

clone 方法:

1
2
3
@Override public RealCall clone() {
return RealCall.newRealCall(client, originalRequest, forWebSocket);
}

Dispatcher.java

1
2
//当异步线程执行时,会依照不同的策略执行
* Policy on when async requests are executed.

拦截器

OkHttpClinet 拿到返回结果,是通过 getResponseWithInterceptorChain() 方法,从名字上我们可以大概猜测方法的语义,通过一系列的拦截器链得到结果,我们可以看下这个方法:

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
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.一个集合存储所有的拦截器啊
List<Interceptor> interceptors = new ArrayList<>();
//用户自定义的拦截器,能拦截所有的请求
interceptors.addAll(client.interceptors());
//猜测是重试机制
interceptors.add(retryAndFollowUpInterceptor);
//请求的头信息,cookie,gzip
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//猜测是缓存
interceptors.add(new CacheInterceptor(client.internalCache()));
//开始与目标服务器建立连接,获得RealConnection
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//用户自定义的 interceptor, 仅在生产网络请求时生效
interceptors.addAll(client.networkInterceptors());//猜测是网络请求
}
//向服务器发出一次网络请求的地方
interceptors.add(new CallServerInterceptor(forWebSocket));

Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());

return chain.proceed(originalRequest);
}

InterceptorOkHttp 最核心的一个东西,采用了责任链的设计模式,除了日常负责拦截请求进行一些额外处理,例如 cookie, 实际上它把网络请求,缓存,透明压缩等功能都统一在一起,每个功能都是一个 Interceptor , 它们再连接称为 Intercepteor.Chain 拦截链条,环环相扣,最终完成一次网络请求。

RealInterceptorChain.java:

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
/**
* A concrete interceptor chain that carries the entire interceptor chain: all application
* interceptors, the OkHttp core, all network interceptors, and finally the network caller.
*/
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;//所有的拦截器集合
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;

public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}

@Override public Connection connection() {
return connection;
}

@Override public int connectTimeoutMillis() {
return connectTimeout;
}

@Override public Interceptor.Chain withConnectTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, millis, readTimeout, writeTimeout);
}

@Override public int readTimeoutMillis() {
return readTimeout;
}

@Override public Interceptor.Chain withReadTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, connectTimeout, millis, writeTimeout);
}

@Override public int writeTimeoutMillis() {
return writeTimeout;
}

@Override public Interceptor.Chain withWriteTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, connectTimeout, readTimeout, millis);
}

public StreamAllocation streamAllocation() {
return streamAllocation;
}

public HttpCodec httpStream() {
return httpCodec;
}

@Override public Call call() {
return call;
}

public EventListener eventListener() {
return eventListener;
}

@Override public Request request() {
return request;
}

@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();

calls++;

// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}

// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}

// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);

// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}

// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}

if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}

return response;
}
}

这个方法我没看懂,网上说是传递参数和执行拦截器,并且设定条件,每个拦截器都会执行 proceed 方法。

拦截器:RetryAndFollowUpInterceptor.java

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
/**
* This interceptor recovers from failures and follows redirects as necessary. It may throw an
* {@link IOException} if the call was canceled.
这个拦截器必要时会重试和重定向,可能会抛出异常在call被取消的时候
*/
public final class RetryAndFollowUpInterceptor implements Interceptor {
....

@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();

StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;

int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {//被取消
streamAllocation.release();
throw new IOException("Canceled");
}
}
  • BridgeInterceptor.java

    基本上请求的头信息,Cookie 压缩: gzip

    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
    /**
    * Bridges from application code to network code. First it builds a network request from a user
    * request. Then it proceeds to call the network. Finally it builds a user response from the network
    * response.
    */
    public final class BridgeInterceptor implements Interceptor {
    private final CookieJar cookieJar;
    @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    if (body != null) {
    MediaType contentType = body.contentType();
    if (contentType != null) {
    requestBuilder.header("Content-Type", contentType.toString());
    }

    long contentLength = body.contentLength();
    if (contentLength != -1) {
    requestBuilder.header("Content-Length", Long.toString(contentLength));
    requestBuilder.removeHeader("Transfer-Encoding");
    } else {
    requestBuilder.header("Transfer-Encoding", "chunked");
    requestBuilder.removeHeader("Content-Length");
    }
    }

    if (userRequest.header("Host") == null) {
    requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
    requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
    transparentGzip = true;
    requestBuilder.header("Accept-Encoding", "gzip");
    }

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
    requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
    requestBuilder.header("User-Agent", Version.userAgent());
    }

    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
    .request(userRequest);

    if (transparentGzip
    && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
    && HttpHeaders.hasBody(networkResponse)) {
    GzipSource responseBody = new GzipSource(networkResponse.body().source());
    Headers strippedHeaders = networkResponse.headers().newBuilder()
    .removeAll("Content-Encoding")
    .removeAll("Content-Length")
    .build();
    responseBuilder.headers(strippedHeaders);
    String contentType = networkResponse.header("Content-Type");
    responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
    }

    CacheInterceptor.java

    缓存信息

    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
    /** Serves requests from the cache and writes responses to the cache. */
    public final class CacheInterceptor implements Interceptor {
    final InternalCache cache;

    public CacheInterceptor(InternalCache cache) {
    this.cache = cache;
    }

    @Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
    ? cache.get(chain.request())
    : null;

    long now = System.currentTimeMillis();

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    //从缓存中获取 response
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
    cache.trackResponse(strategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
    return new Response.Builder()
    .request(chain.request())
    .protocol(Protocol.HTTP_1_1)
    .code(504)
    .message("Unsatisfiable Request (only-if-cached)")
    .body(Util.EMPTY_RESPONSE)
    .sentRequestAtMillis(-1L)
    .receivedResponseAtMillis(System.currentTimeMillis())
    .build();
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
    return cacheResponse.newBuilder()
    .cacheResponse(stripBody(cacheResponse))
    .build();
    }

    Response networkResponse = null;
    try {
    networkResponse = chain.proceed(networkRequest);
    } finally {
    // If we're crashing on I/O or otherwise, don't leak the cache body.
    if (networkResponse == null && cacheCandidate != null) {
    closeQuietly(cacheCandidate.body());
    }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
    if (networkResponse.code() == HTTP_NOT_MODIFIED) {
    Response response = cacheResponse.newBuilder()
    .headers(combine(cacheResponse.headers(), networkResponse.headers()))
    .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
    .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
    .cacheResponse(stripBody(cacheResponse))
    .networkResponse(stripBody(networkResponse))
    .build();
    networkResponse.body().close();

    // Update the cache after combining headers but before stripping the
    // Content-Encoding header (as performed by initContentStream()).
    cache.trackConditionalCacheHit();
    cache.update(cacheResponse, response);
    return response;
    } else {
    closeQuietly(cacheResponse.body());
    }
    }

    Response response = networkResponse.newBuilder()
    .cacheResponse(stripBody(cacheResponse))
    .networkResponse(stripBody(networkResponse))
    .build();

    if (cache != null) {
    if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
    // Offer this request to the cache.
    CacheRequest cacheRequest = cache.put(response);
    return cacheWritingResponse(cacheRequest, response);
    }

    if (HttpMethod.invalidatesCache(networkRequest.method())) {
    try {
    cache.remove(networkRequest);
    } catch (IOException ignored) {
    // The cache cannot be written.
    }
    }
    }

    return response;
    }
    .....

    连接池

okHttp 利用连接池来复用连接,避免反复握手建立连接,并且具备在合适的时候挥手连接的能力。

-------------本文结束感谢您的阅读-------------