寫在前面
本文按照一次網絡請求的代碼實例的順序來進行源碼分析,如果有錯誤的地方麻煩各位大佬指正~
目錄
實例
/**
* 同步請求
*/
public void synRequest() {
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.get()
.build();
Call call = client.newCall(request);
try {
Response response = call.execute();
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 異步請求
*/
public void asyncRequest() {
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
Request request = new Request.Builder()
.url("http://www.baidu.com")
.get()
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.out.println(e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
});
}
public static void main(String[] args) {
OkHttpTest test = new OkHttpTest();
test.synRequest();
test.asyncRequest();
}
OkHttpClient
Call的工廠類,用於發送HTTP請求和讀取其響應。:
OkHttpClients 應該共享
當創建一個OkHttpClient實例並將其用於所有HTTP調用時,OkHttp的性能最佳。這是因爲每個客戶端都擁有自己的連接池和線程池,重用連接和線程可減少延遲並節省內存;相反,爲每個請求創建客戶端都會浪費空閒池上的資源。
使用newBuilder()自定義客戶端
您可以使用{@link #newBuilder()}自定義共享的OkHttpClient實例。這將構建共享相同連接池,線程池和配置的客戶端。使用構建器方法爲特定目的配置派生的客戶機。
不需要手動關閉連接
保留的線程和連接如果保持空閒狀態,將自動釋放。但是,如果編寫的應用程序需要主動釋放未使用的資源,則可以這樣做:
-
1.使用{@link ExecutorService#shutdown()}關閉調度程序的執行程序服務。
這也將導致將來打給客戶的電話被拒絕。
client.dispatcher().executorService().shutdown(); -
2.使用{@link ConnectionPool#evictAll()}清除連接池。請注意,連接池的守護程序線程可能不會立即退出。client.connectionPool().evictAll();
-
3.如果您的客戶端具有緩存,調用{@link Cache#close()}。請注意,針對關閉的緩存創建調用是錯誤的,這樣做會導致調用崩潰。
client.cache().close();
OkHttp還使用守護程序線程進行HTTP/2
連接。如果它們保持空閒狀態,它們將自動退出。
總的來說,OkHttpClient將我們配置的參數和線程調度部分封裝在一起進行管理,提供Call的創建方法,爲網絡請求提供必要的組件。
Request
Request類中保存了url、method、headers、body和tags,當創建RealCall時作爲參數傳遞這些數據。
Call & RealCall
首先,我們先來看一下代碼上的流程:
當你看到後面覺得有些亂時,返回來看一下這張圖,就會感覺很舒服了~
接下來我們看這句:
Call call = client.newCall(request);
# OKHttpClient
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
# RealCall
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
可以看到,我們創建的是一個RealCall的實例對象,而RealCall的結構如下:
同步和異步請求調用了RealCall的execute()、enqueue()方法:
// Guarded by this.
// 通過該變量來標記RealCall是否被執行過了
private boolean executed;
/**
* 同步請求調用
*/
@Override
public Response execute() throws IOException {
// 每個Call只能被執行一次
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
// 將RealCall傳遞給dispatcher統一管理
client.dispatcher().executed(this);
// 進入攔截器鏈(各種操作及網絡請求)
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 {
// release
client.dispatcher().finished(this);
}
}
/**
* 異步請求調用
*/
@Override
public void enqueue(Callback responseCallback) {
// 每個Call只能被執行一次
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
// 將RealCall傳遞給dispatcher統一管理
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
// dispatcher的具體調度後文再講;異步請求被執行時會調用下面的RealCall #execute()
@Override
protected void execute() {
boolean signalledCallback = false;
timeout.enter();
try {
// 進入攔截器鏈(各種操作及網絡請求)
Response response = getResponseWithInterceptorChain();
// 1.responseCallback是發送異步請求時傳進來的CallBack
// 2.因爲是異步請求,在返回結果時會先判斷用戶是否已經取消了這次請求
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
e = timeoutExit(e);
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
// release
client.dispatcher().finished(this);
}
}
注意這裏的兩個execute方法不是同一個:
同步請求調用的是聲明爲public的方法,而異步的調用比較複雜一些:enqueue將Call實例保存在dispatcher中,通過dispatcher來進行統一調度;當dispatcher決定執行Call時,會通過executeService(線程池)來調用Call,即實現Call接口的execute方法。dispatcher的調度在下一節中詳細的講。
這裏我們看到了同步請求和異步請求都調用了dispatcher的方法,這個並不是真正的網絡請求階段,而是通過dispatcher對我們想要執行的請求進行管理,詳細的代碼流程在下文中會講到,這裏只是總結一下;而enqueue操作,通過dispatcher中的executeService調度後,切換到工作線程執行網絡請求,調用到RealCall #execute()方法,最後也調用到了RealCall #getResponseWithInterceptorChain()方法。這個方法通過添加多個攔截器,對網絡請求進行補全、選擇協議、編解碼、複用緩存/連接、網絡請求等操作,以及添加自定義攔截器,對請求和返回的結果進行加工,最後返回數據。這就是整體的流程,關於攔截器的詳細講解也在下文中。先附上getResponseWithInterceptorChain()的代碼:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
// 自定義攔截器
interceptors.addAll(client.interceptors());
// 從故障中恢復,並根據需要進行重定向
interceptors.add(retryAndFollowUpInterceptor);
// 構建網絡請求、根據網絡響應構建response
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// 提供緩存的請求、將response寫入緩存
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, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
Dispatcher
首先我們看一下dispatcher的成員變量:
maxRequest & maxRequestPerHost
maxRequest和maxRequestPerHost用來控制當前可同時進行的網絡請求的數量。用這兩個變量去控制的原因在於,方便與對其進行修改和管理。因爲同步請求是直接進行請求的,而異步請求是通過線程池來進行請求的,我們來看一下線程池的獲取:
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;
}
這裏可以看到,線程池是直接創建了數量爲MAX_VALUE的,而後續的最大數量判斷都是使用了前面的兩個變量,這樣的好處是在對maxRequest進行修改時,excuteService本身並不需要重新實例化。
readyAsyncCalls & runningAsyncCalls & runningSyncCalls
這三個Deque的作用就是保存當前準備運行和正在運行的異步請求、正在運行的同步請求這三種請求的引用,用於統計數量、管理運行以及取消請求等一系列操作。
我們接着之前講到的,異步請求調用了enqueue之後,到執行Call接口的execute方法之間的過程:
void enqueue(AsyncCall call) {
synchronized (this) {
// 加入ready隊列
readyAsyncCalls.add(call);
}
// 執行異步請求
promoteAndExecute();
}
/**
* Promotes eligible calls from {@link #readyAsyncCalls} to {@link #runningAsyncCalls} and runs
* them on the executor service. Must not be called with synchronization because executing calls
* can call into user code.
* 將合格的請求從{@link #readyAsyncCalls}轉移到{@link #runningAsyncCalls},並在executor service(線程池)上執行。
* 不能以同步方式調用,因爲執行調用可以調用用戶代碼。
*
* @return true if the dispatcher is currently running calls.
* 如果dispatcher當前正在執行請求,返回true。
*/
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
// 遍歷readyAsyncCalls中的Call
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
// 判斷是否超過最大限制
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
// 從readyAsyncCalls轉移到runningAsyncCalls
i.remove();
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
// 執行這些請求
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
/**
* Attempt to enqueue this async call on {@code executorService}. This will attempt to clean up
* if the executor has been shut down by reporting the call as failed.
* 嘗試在{@code executorService}上使此異步調用入隊。
* 如果執行程序已通過將調用報告爲失敗而被關閉,則這將嘗試進行清理。
*/
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
// 調用Call接口的execute
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
// 被拒絕執行後的清理操作
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
Interceptor
首先,我們先來看一下OKHttp添加的必須的攔截器;然後再介紹一下自定義攔截器。我們先來回顧一下:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
// 自定義攔截器
interceptors.addAll(client.interceptors());
// 從故障中恢復,並根據需要進行重定向
interceptors.add(retryAndFollowUpInterceptor);
// 構建網絡請求、根據網絡響應構建response
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// 提供緩存的請求、將response寫入緩存
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, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
那麼攔截器是怎麼進行操作的呢?如下面這張圖:每個攔截器按照其順序可以對Request進行處理,又按照反序對Response進行處理。而攔截器的核心函數就是public Response intercept(Chain chain)
。
RetryAndFollowUpInterceptor
重定向攔截器,負責從故障中恢復,並根據需要進行重定向。下文的代碼中部分需要大篇幅解釋的地方添加了註釋標誌:
@Override
public Response intercept(Chain chain) throws IOException {
// get request
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
// 註釋1
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");
}
Response response;
boolean releaseConnection = true;
try {
// 進入鏈中的下一個攔截器,並catch RouteException
response = realChain.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.
// 嘗試通過路由連接失敗,請求未被髮送
// 註釋2
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
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, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp;
try {
// 找出響應收到{@code userResponse}而發出的HTTP請求
// 並添加身份驗證標頭,遵循重定向或處理客戶端請求超時
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}
if (followUp == null) {
streamAllocation.release();
return response;
}
closeQuietly(response.body());
// 判斷是否超過次數限制
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
// 若重定向後不是同一連接,則重新創建StreamAllocation
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
註釋1:
此類協調三個實體之間的關係:
Connections:與遠程服務器的物理套接字連接。這些建立起來可能很慢,因此必須能夠取消當前正在連接的連接。
Streams:位於連接上的邏輯HTTP請求/響應對。每個連接都有自己的分配限制,該限制定義該連接可以承載的併發流數。 HTTP / 1.x連接一次只能傳送1個流,HTTP / 2通常會傳送多個流。
Calls:流的邏輯順序,通常是初始請求及其後續請求。我們希望將單個呼叫的所有流都保留在同一連接上,以實現更好的行爲和局部性。
此類的實例代表調用、使用一個或多個連接上的一個或多個流。此類具有用於釋放上述每個資源的API:
{@ link #noNewStreams()}防止將來將連接用於新的流。在{@code Connection:close}標頭之後,或在連接可能不一致時使用此命令。
{@ link #streamFinished streamFinished()}從此分配中釋放活動流。請注意,在給定時間可能只有一個流處於活動狀態,因此在使用{@link #newStream newStream()}創建後續流之前,必須調用{@link #streamFinished streamFinished()}。
{@ link #release()}刪除呼叫對連接的保留。 請注意,如果仍然有流,這將不會立即釋放連接。 當呼叫完成但其響應主體尚未完全消耗時,就會發生這種情況。
此類支持{@linkplain #cancel異步取消},目的是使影響範圍降到最小。 如果HTTP / 2流處於活動狀態,則取消操作將取消該流,但不會取消共享其連接的其他流。 但是,如果TLS握手仍在進行中,則取消操作可能會中斷整個連接。
註釋2:
/**
* Report and attempt to recover from a failure to communicate with a server. Returns true if
* {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
* be recovered if the body is buffered or if the failure occurred before the request has been
* sent.
* 報告並嘗試從與服務器通信的故障中恢復。
* 如果{@code e}是可恢復的,則返回true;
* 如果失敗是永久的,則返回false。
* 僅在緩衝正文或在發送請求之前發生故障時,才能恢復帶有正文的請求。
*/
private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {
// 關閉並釋放連接
streamAllocation.streamFailed(e);
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
// We can't send the request body again.
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false;
// No more routes to attempt.
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
BridgeInterceptor
構建網絡請求、根據網絡響應構建response
@Override
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
// 補全頭部信息
// 補全Content-Type、Content-Length、Transfer-Encoding
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");
}
}
// 補全Host
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
// 補全Connection
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.
// 如果增加了gzip壓縮的字段,還需要負責將返回的response解碼
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
// 補全Cookie
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());
}
// 分界線----上面是對Request的修改,下面是對Response的修改
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// 如果添加了gzip,需要進行解碼
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
提供緩存的請求、將response寫入緩存
@Override
public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 給定一個請求和緩存的響應,判斷是使用網絡,緩存還是同時使用兩者
// 選擇緩存策略可能會向請求中添加條件(如GET的“ If-Modified-Since”)
// 或向緩存的響應發出警告(如果緩存的數據可能已過時)
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
// 對{@code cacheStrategy}滿意的HTTP響應進行跟蹤
// 註釋1
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.
// 沒有網絡策略和緩存策略,返回504
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.
// 如果已經有一個緩存的response,那麼我們正在進行一個有條件的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()).
// 在合併headers之後、去掉Content-Encoding字段之前,更新緩存
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;
}
註釋1:
synchronized void trackResponse(CacheStrategy cacheStrategy) {
++this.requestCount;
if (cacheStrategy.networkRequest != null) {
++this.networkCount;
} else if (cacheStrategy.cacheResponse != null) {
++this.hitCount;
}
}
ConnectInterceptor
打開與目標服務器的連接,然後進入下一個攔截器
@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對象,用來編碼request、解碼response
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
// 用於進行實際的網絡IO傳輸
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
這裏的關鍵在於HttpCodec的創建,我們來看streamAllocation.newStream方法:
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
···
try {
// co co da yo ~
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
/**
* 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, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
// co co da yo ~
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, 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;
}
}
最後這裏就不貼代碼了。StreamAllocation中有一個ConnectionPool變量,負責管理Http連接。而這部分代碼的目的就是,在創建新的連接之前,需要先查看是否有可以複用的連接,如果有則複用,沒有或連接已經失效則重新創建連接。
CallServerInterceptor
進行網絡請求
@Override
public Response intercept(Chain chain) throws IOException {
···
// 寫入headers
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
··· // 100-continue
if (responseBuilder == null) {
···
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
// 寫入body信息
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} 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) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
// build response
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
··· // 100-continue
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
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();
}
// prevents the connection from being used for new streams in the future
// 禁止該連接被複用
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;
}
自定義Interceptor
用戶可以自行定義攔截器,來進行對網絡請求的統一處理。這裏附上兩個攔截器:
PostParamsConvertInterceptor:將FormBody轉化爲json
使用方式也很簡單,在OkHttpClient創建時加入:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(new PostParamsConvertInterceptor());
if (Utils.isAppDebug()) {
builder.addInterceptor(new LogInterceptor());
}
寫在最後
如果文章中有錯誤的地方,希望各位大佬們批評指正~
If you like this article, it is written by Johnny Deng.
If not, I don’t know who wrote it.