簡介
目前在HTTP協(xié)議請求庫中,OKHttp應(yīng)當(dāng)是非常火的,使用也非常的簡單。網(wǎng)上有很多文章寫了關(guān)于OkHttp的特點(diǎn)以及使用,而本章主要帶領(lǐng)大家閱讀OkHttp的源碼,讓大家對OKhttp的工作原理有所了解,當(dāng)然源碼的代碼量是非常大的,這里我只是抓住主線和重點(diǎn)部分,至于細(xì)節(jié)部分,大家隨著我拋出的線來跟進(jìn)基本是沒什么問題的。這篇文章要干嘛,引用一句話:
read the fucking source code
目錄:
- OkHttp介紹
- 粗繪請求流程
- RealCall方法execute
- getResponseWithInterceptorChain調(diào)用鏈
- RetryAndFollowUpInterceptor
- ConnectInterceptor獲取連接
- CallServerInterceptor網(wǎng)絡(luò)請求
- RealConnection
- StreamAllocation
- HttpCodec(Http1Codec)
- 同步/異步請求
OkHttp介紹:
特點(diǎn):
- 支持連接同一地址的連接共享同一個socket(前提服務(wù)端支持)
- 支持Socket連接池,減少請求延遲
- 使用攔截器模式,將流程拆分
- 透明的GZIP壓縮
粗繪請求流程
注意:這里我選擇OkHttp源碼版本是 3.8.0。為了方便大家能夠和文章同步,最好保持版本一致,我看過老版本和新的版本還是有點(diǎn)不同的。
官網(wǎng)給出的示例
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
我們就從這里入口,來一步一步的跟進(jìn)。
- 首先是創(chuàng)建一個OkHttpClient,Http請求工廠,也就是只要需要發(fā)Http請求,那都得找他。內(nèi)部當(dāng)然后很多的成員變量和方法,這里我們先不做介紹,等用到時再解釋。
- 我們繼續(xù)看
client.newCall(request)
。找到源碼
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
很簡單,創(chuàng)建了一個RealCall,這里我就稱為一個請求。Request不說大家能理解,里面封裝了各種請求的信息。創(chuàng)建過程也很簡單,做一些成員變量賦值和初始化。
RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
final EventListener.Factory eventListenerFactory = client.eventListenerFactory();
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
// TODO(jwilson): this is unsafe publication and not threadsafe.
this.eventListener = eventListenerFactory.create(this);
}
這里注意retryAndFollowUpInterceptor;
變量,后面會用到。
- 調(diào)用了RealCall的
execute()
方法并返回Response結(jié)果。
RealCall方法execute
前面我們知道了大致的請求流程,下面我們重點(diǎn)看
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
- 首先我們發(fā)現(xiàn)try的前后調(diào)用了Dispatcher的方法:
client.dispatcher().executed(this);
client.dispatcher().finished(this);
分別是將Call加入到Dispatcher中的同步隊(duì)列中,結(jié)束后,移除隊(duì)列。
- 調(diào)用getResponseWithInterceptorChain獲取Response。
接下來我們就重點(diǎn)看getResponseWithInterceptorChain方法
getResponseWithInterceptorChain調(diào)用鏈
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
這里的代碼就很關(guān)鍵了,設(shè)計(jì)的很巧妙。可能有點(diǎn)繞,這里我講一下幾個關(guān)鍵的類。
Chain
- 獲取Request
- 執(zhí)行proceed
RealInterceptorChain
Chain的實(shí)現(xiàn)
- 包含了完成請求需要的類,包括StreamAllocation、HttpCodec、RealConnection、Request等。這里必要重要的就是可以實(shí)現(xiàn)了Chain的
request()
來獲取Request。 - 控制Interceptor的調(diào)用,調(diào)用Interceptor的攔截方法intercept后,就封裝下一個RealInterceptorChain并指定index。聲明下一個將要被調(diào)用的Interceptor。這部分邏輯主要在proceed方法中。我們看核心代碼
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
首先會獲取當(dāng)前index的Interceptor。然后執(zhí)行對應(yīng)的intercept
方法。同時出入的參數(shù)是新創(chuàng)建的RealInterceptorChain。而新創(chuàng)建的RealInterceptorChain對應(yīng)的index+1。如果執(zhí)行新創(chuàng)建的RealInterceptorChain的proceed方法,那么interceptors的第index+1個Interceptor的intercept會被執(zhí)行。依次循環(huán)下去。
總結(jié): RealInterceptorChain就是對請求中個中重要對象的封裝,執(zhí)行Interceptor的intercept
的調(diào)用,確定下一個RealInterceptorChain。保證所有的Interceptor依次執(zhí)行intercept
。
Interceptor
前面講到了RealInterceptorChain會執(zhí)行Interceptor的intercept方法,同時傳入下一個RealInterceptorChain。那么intercept方法究竟做了什么事呢,因?yàn)镮nterceptor的實(shí)現(xiàn)很多,這里我們挑一個系統(tǒng)的實(shí)現(xiàn)類看看,比如:BridgeInterceptor,這個代碼雖然長,但邏輯想對簡單
@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);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
里面邏輯我們現(xiàn)在可能還看不懂,我們看中間最核心的一句話,。Response networkResponse = chain.proceed(requestBuilder.build());
有沒有覺得頓時豁然開朗。realChain是傳入的參數(shù),而執(zhí)行proceed方法,就又回到了前面我們講RealInterceptorChain
的流程。那前后RealInterceptorChain有什么區(qū)別呢?那就是index在不斷的增加,同時對應(yīng)的Interceptor也就不同。
那么Interceptor有什么用呢?
我們剛才只關(guān)注了中間的chain.proceed(requestBuilder.build());
。而在此前后我們可以做很多的邏輯操作了,比如:
對Request進(jìn)行一些請求頭的判斷,處理和完善。對Response進(jìn)行一些處理,如在有g(shù)zip的情況下數(shù)據(jù)的處理等。
總結(jié):Interceptor這里我稱之為攔截器。Okhttp將請求的流程,從封裝請求頭,獲取連接,發(fā)請求數(shù)據(jù),讀請求數(shù)據(jù)等等。拆分成一個個Interceptor。每一個Interceptor有著自己單一的功能,而下層的Interceptor為上層的Interceptor服務(wù),有沒有覺得有點(diǎn)像我們的網(wǎng)絡(luò)TCP/IP的模型,哈哈。其實(shí)這種思想讓我們的請求變的更加清晰,并且擴(kuò)展性很好。每一層也就是Interceptor可以有自己的實(shí)現(xiàn)。同時我們可以定義自己的Interceptor。 而Interceptor的順序執(zhí)行就由RealInterceptorChain
完成。
到這里我們就講了整個請求的大體執(zhí)行框架和模式。這部分一定要好好的理解,方便后面的學(xué)習(xí)。
RetryAndFollowUpInterceptor
這個攔截器用來做重連接和重定向的。其中邏輯有以下:
創(chuàng)建StreamAllocation
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
...省略剩余代碼
看到了吧new StreamAllocation了吧。這里第一個疑惑解決,StreamAllocation的創(chuàng)建地方。這里還要多講一個地方就是構(gòu)造參數(shù)ConnectionPool。我們看到是從OkHttpClient傳了的。而在OkHttpclient創(chuàng)建時候創(chuàng)建了ConnectionPool。
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
...省略
public static final class Builder {
public Builder() {
...省略
connectionPool = new ConnectionPool();
...省略
后面用到ConnectionPool大家就別再疑惑了。
創(chuàng)建StreamAllocation在這里,那當(dāng)然釋放也是在這類里:
失敗重連接
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
我們看到response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
是在一個無限循環(huán)中,如果出現(xiàn)異常,并且滿足重連接,就會再次調(diào)用。
重定向
Request followUp = followUpRequest(response);
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
ConnectInterceptor獲取連接
前面我們講了Interceptor的執(zhí)行流程。而getResponseWithInterceptorChain方法中添加了一些給定的Interceptor。如:RetryAndFollowUpInterceptor(這個是在創(chuàng)建RealCall時候創(chuàng)建的,前面有提醒大家注意)、BridgeInterceptor(上一節(jié)有講到,主要做一些請求頭和響應(yīng)數(shù)據(jù)的處理)、CacheInterceptor(看名稱知道,處理緩存)、ConnectInterceptor、CallServerInterceptor。按上一節(jié)的流程,這些Interceptor會依次被調(diào)用。這里我們要重點(diǎn)看最后兩個,首先是ConnectInterceptor。
通過名稱我們知道主要做連接處理。我們看下源碼:
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
估計(jì)大家都喜歡看這種源碼,代碼量很少,簡潔。我們主要看中間三行代碼:
獲取StreamAllocation
StreamAllocation streamAllocation = realChain.streamAllocation();
而streamAllocation是RealInterceptorChain的成員變量,在構(gòu)造方法中賦值。這里我們就往前找,往直前的Interceport找,看誰構(gòu)造RealInterceptorChain傳遞了StreamAllocation。經(jīng)過我們的一個個查找,在RetryAndFollowUpInterceptor的intercept方法中找到:
獲取HttpCodec
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
獲取RealConnection
RealConnection connection = streamAllocation.connection();
HttpCodec和RealConnection這兩玩意干啥的,就現(xiàn)在這幾行代碼我們也看不出來,那就先不管。繼續(xù)看CallServerInterceptor
CallServerInterceptor網(wǎng)絡(luò)請求
這個就厲害了。看名稱,請求服務(wù)。那就是最核心的地方了。擼上代碼:
/** This is the last interceptor in the chain. It makes a network call to the server. */
public final class CallServerInterceptor implements Interceptor {
private final boolean forWebSocket;
public CallServerInterceptor(boolean forWebSocket) {
this.forWebSocket = forWebSocket;
}
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return what
// we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
// being reused. Otherwise we're still obligated to transmit the request body to leave the
// connection in a consistent state.
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
}
1. 從RealInterceptorChain獲取相關(guān)對象
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
這些變量在上一節(jié)中已經(jīng)介紹,這里只是獲取一下。
2. 發(fā)送請求頭數(shù)據(jù)
httpCodec.writeRequestHeaders(request);
3. 發(fā)送請求體
這里會先判斷請求方法以及是否有請求體數(shù)據(jù)。如果有則發(fā)送。
4. 讀取響應(yīng)頭
responseBuilder = httpCodec.readResponseHeaders(false);
5. 封裝相應(yīng)內(nèi)容
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
6. 判斷Connection頭部信息是否是close
如果請求頭或響應(yīng)頭的Connection值為close。則標(biāo)識改Connection為noNewStreams。標(biāo)識不會有新的流。
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
至于noNewStreams是用來控制,當(dāng)前的連接是否能再次被使用,我們后面會講到。
前面我們講了CallServerInterceptor的請求流程,但里面有很多的類我們還不清楚是怎么來的,以及干啥用的。接下來我們就講核心類的用法以及創(chuàng)建和使用流程。
RealConnection
這個類主要負(fù)責(zé)進(jìn)行Socket的操作(連接),獲取Socket的輸入輸出流并封裝。
建立Socket連接(connect)
while (true) {
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout);
} else {
connectSocket(connectTimeout, readTimeout);
}
establishProtocol(connectionSpecSelector);
break;
} catch (IOException e) {
closeQuietly(socket);
closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
http2Connection = null;
if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}
我們先簡單的看,直接連接socket的方式,大家可以看到是一個無限循環(huán),知道連接成功,或者指定的相關(guān)異常拋出則跳出循環(huán)。具體哪些異常可以看connectionSpecSelector.connectionFailed(e)
內(nèi)部的實(shí)現(xiàn)。
接下來我們就具體看連接socket的方法connectSocket(connectTimeout, readTimeout)
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
rawSocket.setSoTimeout(readTimeout);
try {
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
ce.initCause(e);
throw ce;
}
// The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
// More details:
// https://github.com/square/okhttp/issues/3245
// https://android-review.googlesource.com/#/c/271775/
try {
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
throw new IOException(npe);
}
}
}
邏輯很清晰,就是建立socket連接,然后封裝輸入輸出流。
- 建立socket連接
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
進(jìn)行連接,我們查看實(shí)現(xiàn):
public void connectSocket(Socket socket, InetSocketAddress address,
int connectTimeout) throws IOException {
socket.connect(address, connectTimeout);
}
這就到了我們熟悉的Socket連接了。
- 封裝輸入輸出流
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
獲取并封裝Socket輸入輸出流
我們看一下Okio.source方法:
/**
* Returns a source that reads from {@code socket}. Prefer this over {@link
* #source(InputStream)} because this method honors timeouts. When the socket
* read times out, the socket is asynchronously closed by a watchdog thread.
*/
public static Source source(Socket socket) throws IOException {
if (socket == null) throw new IllegalArgumentException("socket == null");
AsyncTimeout timeout = timeout(socket);
Source source = source(socket.getInputStream(), timeout);
return timeout.source(source);
}
還得繼續(xù)扒:看source的重載方法:
private static Source source(final InputStream in, final Timeout timeout) {
if (in == null) throw new IllegalArgumentException("in == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");
return new Source() {
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (byteCount == 0) return 0;
try {
timeout.throwIfReached();
Segment tail = sink.writableSegment(1);
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
sink.size += bytesRead;
return bytesRead;
} catch (AssertionError e) {
if (isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
}
}
@Override public void close() throws IOException {
in.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "source(" + in + ")";
}
};
}
來了,這下清晰了,對輸入流做了包裝。既然是輸入,就只有read方法而么有write方法。而讀的邏輯就是講InputStream的數(shù)據(jù)存放到Buffer中。
到這里Okio.source(rawSocket)
我們清楚了,把輸入流封裝成Source。read方法將數(shù)據(jù)讀入到Buffer中。我們接下來繼續(xù)看Okio.buffer(Okio.source(rawSocket))
外面這個方法:
public static BufferedSource buffer(Source source) {
return new RealBufferedSource(source);
}
有點(diǎn)粗暴,就是new了個RealBufferedSource。而它又是啥玩意呢,它實(shí)現(xiàn)了BufferedSource。這分明就是個裝飾模式嘛。在原有Source的基礎(chǔ)上,多了一些方法。如:readInt、skip等等。那還有啥用呢,我們看read方法:
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (sink == null) throw new IllegalArgumentException("sink == null");
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
if (buffer.size == 0) {
long read = source.read(buffer, Segment.SIZE);
if (read == -1) return -1;
}
long toRead = Math.min(byteCount, buffer.size);
return buffer.read(sink, toRead);
}
這里進(jìn)行了方法的覆寫,數(shù)據(jù)先讀到Buffer里,然后再寫到sink里。
至于sink = Okio.buffer(Okio.sink(rawSocket));
我就不講了,模式一樣。只是一個負(fù)責(zé)讀,一個負(fù)責(zé)寫。
StreamAllocation
協(xié)調(diào)Connections、Streams、Calls之間的關(guān)系。包括控制RealConnection的創(chuàng)建,釋放,狀態(tài)的管理。
一個RealConnection中可以包含多個StreamAllocation,默認(rèn)為1個。
findHealthyConnection
這個方法就比較重要了。找到一個健康可用的RealConnection,通過閱讀這個類,我們可以把上面說的幾個類的關(guān)系搞清楚。先源碼:
/**
* Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
* until a healthy connection is found.
*/
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
首選進(jìn)入一個死循環(huán),直到獲取一個健康的可用的RealConnection或者有異常拋出。
findConnection》》》》》》》》》》》》》》》》Start
第一步調(diào)用到findConnection方法,我們查看該方法
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
*/
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// Attempt to use an already-allocated connection.
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
// If we need a route, make one. This is a blocking operation.
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
}
RealConnection result;
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
// Now that we have an IP address, make another attempt at getting a connection from the pool.
// This could match due to connection coalescing.
Internal.instance.get(connectionPool, address, this, selectedRoute);
if (connection != null) return connection;
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result);
}
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
// Pool the connection.
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
return result;
}
先判斷當(dāng)前的
RealConnection allocatedConnection = this.connection;
判斷當(dāng)前連接是否已存在,如果存在且沒有標(biāo)記noNewStreams,則直接返回該連接-
到連接池中尋找匹配的連接
Internal.instance.get(connectionPool, address, this, null);
。這里Internal.instance是一個抽象類中的靜態(tài)變量,那在哪里實(shí)現(xiàn)的呢。我們看到OkHttpClient類。類中第三個static關(guān)鍵字就是instance的實(shí)現(xiàn)static { Internal.instance = new Internal() { @Override public void addLenient(Headers.Builder builder, String line) { builder.addLenient(line); } @Override public RealConnection get(ConnectionPool pool, Address address, StreamAllocation streamAllocation, Route route) { return pool.get(address, streamAllocation, route); } ...省略代碼 }
這里我們就知道其實(shí)最終調(diào)用到了ConnectionPool的get方法。我們查看
/** * Returns a recycled connection to {@code address}, or null if no such connection exists. The * route is null if the address has not yet been routed. */ @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) { assert (Thread.holdsLock(this)); for (RealConnection connection : connections) { if (connection.isEligible(address, route)) { streamAllocation.acquire(connection); return connection; } } return null; }
內(nèi)部就是循環(huán)遍歷connections,找到匹配的Connection。至于如何判斷,大家查看方法的實(shí)現(xiàn)即可。如果找到則直接返回,否則進(jìn)入下一步
前面在調(diào)用ConnectionPool.get方法時候Route參數(shù)為空,這一步就是獲取一個Route然后再次查找。如果成功就返回
-
經(jīng)過上面三個步驟后,說明已經(jīng)沒有可用的Connection。那么就得創(chuàng)建一個,
result = new RealConnection(connectionPool, selectedRoute); acquire(result);
-
創(chuàng)建完后調(diào)用
acquire
,這個是干啥的呢public void acquire(RealConnection connection) { assert (Thread.holdsLock(connectionPool)); if (this.connection != null) throw new IllegalStateException(); this.connection = connection; connection.allocations.add(new StreamAllocationReference(this, callStackTrace)); }
把當(dāng)前的StreamAllocation添加到RealConnection。這和我們前面說到的一個RealConnection可能對應(yīng)多個StreamAllocation。
-
開始進(jìn)行Socket連接
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled); routeDatabase().connected(result.route());
-
將RealConnection添加到connectionPool中
Internal.instance.put(connectionPool, result);
到這里findHealthyConnection執(zhí)行完畢,結(jié)果獲取一個可用的RealConnection。
findConnection《《《《《《《《《《《《《《《《《《《《End
繼續(xù)findHealthyConnection的代碼:
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
這里就是檢查RealConnection是否正常可用。也就是做個體檢isHealthy
,如果發(fā)現(xiàn)返回False,那么標(biāo)記這個RealConnection的noNewStreams為true。此變量標(biāo)記為true后,代碼后面就不要從使用這個RealConnection。何以得知呢?
看到前面第2步,從ConnectionPool調(diào)用get方法尋找合適的RealConnection,有一句判斷,前面我們沒有講,這里我跟蹤一下:
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (connection.isEligible(address, route)) {
streamAllocation.acquire(connection);
return connection;
}
}
return null;
}
看到connection.isEligible(address, route)
這句話,我們進(jìn)入:
public boolean isEligible(Address address, @Nullable Route route) {
// If this connection is not accepting new streams, we're done.
if (allocations.size() >= allocationLimit || noNewStreams) return false;
// If the non-host fields of the address don't overlap, we're done.
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
.../省略代碼
看到noNewStreams了吧, 現(xiàn)在知道他的用處了吧。還有allocations.size() >= allocationLimit
,控制一個RealConnection可以被多少個StreamAllocation持有,這下都清楚了吧。
HttpCodec(Http1Codec)
Http請求和響應(yīng)的編解碼抽象HttpCodec
這是一個接口,定義了編解碼的抽象方法。
public interface HttpCodec {
/**
* The timeout to use while discarding a stream of input data. Since this is used for connection
* reuse, this timeout should be significantly less than the time it takes to establish a new
* connection.
*/
int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
/** Returns an output stream where the request body can be streamed. */
Sink createRequestBody(Request request, long contentLength);
/** This should update the HTTP engine's sentRequestMillis field. */
void writeRequestHeaders(Request request) throws IOException;
/** Flush the request to the underlying socket. */
void flushRequest() throws IOException;
/** Flush the request to the underlying socket and signal no more bytes will be transmitted. */
void finishRequest() throws IOException;
/**
* Parses bytes of a response header from an HTTP transport.
*
* @param expectContinue true to return null if this is an intermediate response with a "100"
* response code. Otherwise this method never returns null.
*/
Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;
/** Returns a stream that reads the response body. */
ResponseBody openResponseBody(Response response) throws IOException;
/**
* Cancel this stream. Resources held by this stream will be cleaned up, though not synchronously.
* That may happen later by the connection pool thread.
*/
void cancel();
}
主要就是針對Request和Response的處理。將我們傳入的請求Request編碼成Http的協(xié)議請求,將響應(yīng)解碼成Response。
針對HTTP/1.1的實(shí)現(xiàn)Http1Codec
前面講了HttpCodec的抽象方法。這里就是實(shí)現(xiàn),Http協(xié)議也有多個版本,也就對應(yīng)不同的實(shí)現(xiàn)。這里我們就看現(xiàn)在常用的Http/1.1。
而Http1Codec的創(chuàng)建在ConnectInterceptor中。
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
我們繼續(xù)跟蹤到StreamAllocation的newStream方法
HttpCodec resultCodec = resultConnection.newCodec(client, this);
繼續(xù)進(jìn)入:
public HttpCodec newCodec(
OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(client.readTimeoutMillis());
source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
兩個參數(shù)我們已經(jīng)很熟悉了。重點(diǎn)看到了new Http1Codec(client, streamAllocation, source, sink)
,在這里創(chuàng)建了Http1Codec。而傳遞的參數(shù)source、sink前面我們已經(jīng)介紹了。在連接Socket后進(jìn)行輸入輸出的封裝。
Http1Codec核心方法實(shí)現(xiàn)
我們在介紹CallServerInterceptor的intercept方法時候,只是粗略的講了下流程。這里我們將一下和Http1Codec相關(guān)的方法。
-
發(fā)送請求頭
httpCodec.writeRequestHeaders(request);
看到實(shí)現(xiàn)
/** * Prepares the HTTP headers and sends them to the server. * * <p>For streaming requests with a body, headers must be prepared <strong>before</strong> the * output stream has been written to. Otherwise the body would need to be buffered! * * <p>For non-streaming requests with a body, headers must be prepared <strong>after</strong> the * output stream has been written to and closed. This ensures that the {@code Content-Length} * header field receives the proper value. */ @Override public void writeRequestHeaders(Request request) throws IOException { String requestLine = RequestLine.get( request, streamAllocation.connection().route().proxy().type()); writeRequest(request.headers(), requestLine); }
requestLine就是HTTP的起始行,內(nèi)部大家可以自己查看。然后看到
writeRequest
方法:
/** Returns bytes of a request header for sending on an HTTP transport. */
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
sink.writeUtf8(requestLine).writeUtf8("\r\n");
for (int i = 0, size = headers.size(); i < size; i++) {
sink.writeUtf8(headers.name(i))
.writeUtf8(": ")
.writeUtf8(headers.value(i))
.writeUtf8("\r\n");
}
sink.writeUtf8("\r\n");
state = STATE_OPEN_REQUEST_BODY;
}
邏輯就很簡單了,遍歷Headers,將請求頭寫入到sink中。
- 發(fā)送請求體
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
// being reused. Otherwise we're still obligated to transmit the request body to leave the
// connection in a consistent state.
streamAllocation.noNewStreams();
}
這里會先判斷,如果后請求體的話就發(fā)送。看到createRequestBody
方法。
@Override public Sink createRequestBody(Request request, long contentLength) {
if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
// Stream a request body of unknown length.
return newChunkedSink();
}
if (contentLength != -1) {
// Stream a request body of a known length.
return newFixedLengthSink(contentLength);
}
throw new IllegalStateException(
"Cannot stream a request body without chunked encoding or a known content length!");
}
確定發(fā)送的請求數(shù)據(jù)大小是否確定,然后返回對應(yīng)的Sink實(shí)現(xiàn)。接下來看到request.body().writeTo(bufferedRequestBody);
的實(shí)現(xiàn)。我們看到request.body()返回是RequestBody,一個抽象類,定義請求體的方法。看到自帶的實(shí)現(xiàn)有FormBody、MultipartBody。我們挑一個看FormBody。看到writeTo
方法:
@Override public void writeTo(BufferedSink sink) throws IOException {
writeOrCountBytes(sink, false);
}
繼續(xù)看:
/**
* Either writes this request to {@code sink} or measures its content length. We have one method
* do double-duty to make sure the counting and content are consistent, particularly when it comes
* to awkward operations like measuring the encoded length of header strings, or the
* length-in-digits of an encoded integer.
*/
private long writeOrCountBytes(@Nullable BufferedSink sink, boolean countBytes) {
long byteCount = 0L;
Buffer buffer;
if (countBytes) {
buffer = new Buffer();
} else {
buffer = sink.buffer();
}
for (int i = 0, size = encodedNames.size(); i < size; i++) {
if (i > 0) buffer.writeByte('&');
buffer.writeUtf8(encodedNames.get(i));
buffer.writeByte('=');
buffer.writeUtf8(encodedValues.get(i));
}
if (countBytes) {
byteCount = buffer.size();
buffer.clear();
}
return byteCount;
}
這里就清晰了,將請求體(Form表單)遍歷的寫出。
- 讀取響應(yīng)頭
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
進(jìn)入到實(shí)現(xiàn):
@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
throw new IllegalStateException("state: " + state);
}
try {
StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
Response.Builder responseBuilder = new Response.Builder()
.protocol(statusLine.protocol)
.code(statusLine.code)
.message(statusLine.message)
.headers(readHeaders());
if (expectContinue && statusLine.code == HTTP_CONTINUE) {
return null;
}
state = STATE_OPEN_RESPONSE_BODY;
return responseBuilder;
} catch (EOFException e) {
// Provide more context if the server ends the stream before sending a response.
IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
exception.initCause(e);
throw exception;
}
}
首先讀取起始行statusLine
。我們進(jìn)入到實(shí)現(xiàn)source.readUtf8LineStrict()
看怎么讀取的
long newline = indexOf((byte) '\n', 0, scanLength);
if (scanLength < Long.MAX_VALUE
&& request(scanLength) && buffer.getByte(scanLength - 1) == '\r'
&& request(scanLength + 1) && buffer.getByte(scanLength) == '\n') {
return buffer.readUtf8Line(scanLength); // The line was 'limit' UTF-8 bytes followed by \r\n.
}
這里先時候去到\n
的位置。然后看到,起始行的結(jié)束是否是\r\n
。最后讀取并返回。StatusLine.parse就是解析得到Http的Method、Code、Message。下面讀取響應(yīng)頭:
Response.Builder responseBuilder = new Response.Builder()
.protocol(statusLine.protocol)
.code(statusLine.code)
.message(statusLine.message)
.headers(readHeaders());
先將起始行封裝到responseBuilder中,然后readHeaders()讀取響應(yīng)頭。
/** Reads headers or trailers. */
public Headers readHeaders() throws IOException {
Headers.Builder headers = new Headers.Builder();
// parse the result headers until the first blank line
for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) {
Internal.instance.addLenient(headers, line);
}
return headers.build();
}
這里就是一行行讀取響應(yīng)頭,然后添加到Headers中。細(xì)節(jié)大家跟蹤到方法內(nèi)部查看即可。
- 封裝響應(yīng)體
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
先判斷響應(yīng)頭,101啥意思呢:服務(wù)器將遵從客戶的請求轉(zhuǎn)換到另外一種協(xié)議(HTTP 1.1新)。所以就不用管響應(yīng)體了。我們重點(diǎn)看到httpCodec.openResponseBody(response)
@Override public ResponseBody openResponseBody(Response response) throws IOException {
Source source = getTransferStream(response);
return new RealResponseBody(response.headers(), Okio.buffer(source));
}
兩個步驟:
- 第一步getTransferStream(response)
private Source getTransferStream(Response response) throws IOException {
if (!HttpHeaders.hasBody(response)) {
return newFixedLengthSource(0);
}
if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
return newChunkedSource(response.request().url());
}
long contentLength = HttpHeaders.contentLength(response);
if (contentLength != -1) {
return newFixedLengthSource(contentLength);
}
// Wrap the input stream from the connection (rather than just returning
// "socketIn" directly here), so that we can control its use after the
// reference escapes.
return newUnknownLengthSource();
}
主要判斷響應(yīng)頭Transfer-Encoding
來確定響應(yīng)體的數(shù)據(jù)大小是否確定,如果是chunked
則是分塊傳輸,則沒有Content-Length。否則可以確定響應(yīng)體大小。然后返回不同的Source實(shí)現(xiàn)。
- 第二步
new RealResponseBody(response.headers(), Okio.buffer(source)
public final class RealResponseBody extends ResponseBody {
private final Headers headers;
private final BufferedSource source;
public RealResponseBody(Headers headers, BufferedSource source) {
this.headers = headers;
this.source = source;
}
@Override public MediaType contentType() {
String contentType = headers.get("Content-Type");
return contentType != null ? MediaType.parse(contentType) : null;
}
@Override public long contentLength() {
return HttpHeaders.contentLength(headers);
}
@Override public BufferedSource source() {
return source;
}
}
這個就是做了一個封裝,沒什么別的邏輯。
到此為止整個Http的發(fā)送和響應(yīng)就介紹完畢了。
同步/異步請求
先來一張流程圖:
文章的開始我們發(fā)送http請求直接使用的同步請求
Response response = client.newCall(request).execute();
這樣比較粗暴,我們還需要開啟線程。如此OkHttp當(dāng)然也就提供了異步調(diào)用方法。
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
調(diào)用也是非常的方便的。整個請求OkHttp會幫我們開啟線程,并完成Http請求。接下來我們就分析這塊的流程。
newCall方法就不介紹了,我們看到enqueue方法。
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
- 先判斷是否已經(jīng)executed。防止多次調(diào)用
- 將任務(wù)加入到隊(duì)里中
client.dispatcher().enqueue(new AsyncCall(responseCallback));
我們進(jìn)入到enqueue方法。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
- 先判斷當(dāng)前正在請求的數(shù)量是否大于最大請求數(shù),同一個主機(jī)的請求是否超過限制
- 如果超過限制,則將AsyncCall任務(wù)放到readyAsyncCalls(準(zhǔn)備任務(wù))隊(duì)列中。
- 如果沒有超過限制,加入到runningAsyncCalls(運(yùn)行)隊(duì)列中,并直接調(diào)度執(zhí)行。
這里我們先看幾個變量runningAsyncCalls、readyAsyncCalls:
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
這個寫的很清楚,異步就緒隊(duì)列、異步運(yùn)行隊(duì)列,同步運(yùn)行隊(duì)列。runningSyncCalls這個在前面同步調(diào)用的時候有涉及。剩下兩個變量就在這里體現(xiàn)。整體的思路就是,異步調(diào)用先判斷請求數(shù)量是否超限,如果沒有直接交給線程池執(zhí)行;超限就先放到準(zhǔn)備隊(duì)列中。
我們在看到executorService().execute(call);
進(jìn)入到executorService()方法:
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
這里很明顯了一個單例的實(shí)現(xiàn),方法上加上了synchronized關(guān)鍵字。ThreadPoolExecutor是創(chuàng)建一個線程池。
這里有朋友要問了,當(dāng)請求數(shù)量超限制我們只看到了把任務(wù)放到準(zhǔn)備隊(duì)列中,那啥時候被調(diào)用呢?這里大家先別著急,后面會講到。
到此我們已經(jīng)把client.dispatcher().enqueue(new AsyncCall(responseCallback));
enqueue這個方法干了什么事講清楚了。這里還設(shè)計(jì)一個類AsyncCall。我們看看:
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
光看這里看不出啥門道,我們得看看父類NamedRunnable
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
嘿!就是個Runnable,run里就做了兩個事
- 設(shè)置線程名稱
- 進(jìn)行execute()調(diào)用
然后我們回到AsyncCall看到execute的實(shí)現(xiàn):
Response response = getResponseWithInterceptorChain();
有沒有覺得很眼熟,這個不就是我們前面講同步調(diào)用的時候,通過這個方法完成的請求。
請求成功進(jìn)行CallBack回調(diào)
responseCallback.onResponse(RealCall.this, response)
失敗或者發(fā)生異常也回調(diào)
responseCallback.onFailure(RealCall.this, e);
上面我們就把異步調(diào)用的發(fā)起和回調(diào)講清楚了,前面我們還有個問題就是準(zhǔn)備隊(duì)列的任務(wù)啥時候被執(zhí)行。
準(zhǔn)備就緒隊(duì)列任務(wù)的調(diào)度
我們還是看到AsyncCall的execute方法,真正的執(zhí)行調(diào)用時在這個方法中,我們看到最后的finally塊
finally {
client.dispatcher().finished(this);
}
跟蹤到Dispatcher的finish方法
/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
看到重載的方法:
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
這里先把call從隊(duì)列中移除。然后判斷promoteCalls,這里我們知道是true,所以重點(diǎn)看到if (promoteCalls) promoteCalls();
這段代碼:
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
- 還是先判斷正在請求的數(shù)量是否超限制
- 遍歷readyAsyncCalls,找到符合條件的請求(同一個主機(jī)的請求數(shù)量是否超限制)。如果找到就從readyAsyncCalls中移除,然后加入到runningAsyncCalls。然后通過線程池獲進(jìn)行調(diào)度執(zhí)行。
前面我們將到同步調(diào)用的時候,RealCall的execute()方法的開始有client.dispatcher().executed(this);
方法的結(jié)束finally調(diào)用了client.dispatcher().finished(this);
。然后調(diào)用了Dispatcher的finished(runningSyncCalls, call, false);
方法。這里和異步調(diào)用的區(qū)別就是最后一個參數(shù)為false。
到這里整個同步調(diào)用和異步調(diào)用我們就串聯(lián)起來了。