前言:爲什麼有些人寧願喫生活的苦也不願喫學習的苦,大概是因爲懶惰吧,學習的苦是需要自己主動去喫的,而生活的苦,你躺着不動它就會來找你了。
一、概述
OKHttp是一個非常優秀的網絡請求框架,已經被谷歌加入到Android源碼中,目前比較流行的Retrofit底層也是使用OKHttp的,OKHttp的使用是要掌握的,有不懂得可以參考我的博客OKHttp3的使用和詳解
在早期版本中,OKHttp支持http1.0,1.1,SPDY協議,但是http2的協議問世也導致了OKHttp做出了改變,OKHttp鼓勵開發者使用http2,不再對SPDY協議給予支持,另外,新版的okhttp還支持WebScoket,這樣就可以非常方便地建立長連接了。
作爲非常優秀的網絡請求框架,okhttp也支持網絡緩存,okhttp的緩存基於DiskLruCache,DiskLruCache雖然沒有被加入到Android源碼中,但是也是一個非常優秀的緩存框架,有時間可以學習一下。在安全方面,okhttp支持如上圖所示的TLS版本,以確保一個安全的Scoket鏈接,此外還支持網絡鏈接失敗的重試以及重定向。
二、源碼分析
2.1 okhttp的使用
OkHttpClient okHttpClient = new OkHttpClient();
//通過Builder輔助類構建請求對象
Request request = new Request.Builder()
.get()//get請求
.url("https://www.baidu.com")//請求地址
.build();//構建
//通過mOkHttpClient調用請求得到Call
final Call call = mOkHttpClient.newCall(request);
//執行同步請求,獲取Response對象
Response response = call.execute();
String string = response.body().string();
Log.e(TAG, "get同步請求success==" + string);
//異步執行
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
Log.e(TAG, "get異步請求failure==" + e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
String string = response.body().string();
Log.e(TAG, "get異步請求success==" + string);
}
});
2.2 同步Request的請求流程
在開始流程講解前先來了解三個概念(註釋來自於源碼):
Connections:鏈接遠程服務器的無理鏈接
Streams:基於Connection的邏輯Http請求/響應對。一個鏈接可以承載多個Stream都是有限制的,http1.0、1.1只支持承載一個Stream,http2支持承載多個Stream(支持併發請求,併發請求共用一個Connection)
Calls:邏輯Stream序列,典型的例子是一個初始請求以及後續的請求。對於同步和異步請求,唯一的區別就是異步請求會放到線程池(ThreadPoolExecutor)中執行,而同步請求會在當前線程執行,會阻塞當前線程。
call:最終的請求對象
Interceptors:這個是okhttp最核心的部分,一個請求會經過okhttp若干個攔截器的處理,每一個攔截器都完成一個功能模塊,一個Request經過攔截器的處理後,最終會得到Response。攔截器裏面包含的東西很多,這裏將重點講解。
2.3 OkHttpClient
首先,我們構造OkHttpClient對象實例,OkHttpClient創建實例的方式有兩種,第一種是使用默認構造函數直接new一個實例,另一種就是,通過建造者(Builder)模式new OkHttpClient().Builder().build,這兩種方式有什麼區別呢?其實第二種默認的設置和第一種相同,但是我們可以利用建造者模式來設置每一種屬性。
我們先來看第一種方式:OkHttpClient okHttpClient = new OkHttpClient();
public OkHttpClient() {
this(new Builder());
}
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
可以看到簡單的一句new OkHttpClient(),Okhttp就爲我們做了很多工作,很多需要使用到的參數在這裏獲得默認值,我們來分析一下他們的含義:
- dispatcher :調度器的意識,主要作用通過雙端隊列保存Calls(同步/異步call),同時在線程池中執行異步請求;
- protocols:默認支持的Http協議版本,Protocol.HTTP_2/Protocol.HTTP_1_1;
- connectionSpecs :Okhttp的鏈接(Connection)配置,ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT,一個針對TLS鏈接的配置,一個針對普通的http鏈接配置;
- eventListenerFactory:一個Call狀態的監聽器,這個是OKHttp新添加的功能,這個還不是最終版本,最後還會改變;
- proxySelector:使用默認的代理選擇器;
- cookieJar:默認是沒有Cookie的;
- socketFactory:使用默認的Socket工廠生產Socket;
- hostnameVerifier、certificatePinner、proxyAuthenticator、authenticator:安全相關的配置;
- connectionPool:連接池;
- dns:域名解析系統,domain name -> ip address;
- followSslRedirects、followRedirects、retryOnConnectionFailure:起始狀態;
- connectTimeout、readTimeout 、writeTimeout:分別爲:鏈接超時時間、讀取超時時間、寫入超時時間;
- pingInterval:這個就和WebSocket有關了,爲了保證長連接,必須每隔一段時間發送一個ping指令進行保活。
注意事項:OkHttpClient強烈建議全局單例使用,因爲每一個OkHttpClient都有自己單獨的線程池和連接池,複用連接池和線程池能夠減少延遲和節省內存。
2.4 RealCall(生成一個Call)
在我們定義了請求對象Request之後,通過okHttpClient.newCall()生成一個Call,該對象代表一個執行的請求,Call是可以被取消的,Call對象代表一個request/response 對(Stream),一個Call只能被執行一次,執行同步請求(call.execute()):
/**
* 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 */);
}
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;
}
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
若果executed = true說明已經執行了,如果再次調用就會拋出異常,這裏說明一個Call只能被執行一次,注意同步執行請求和異步執行請求的區別:異步執行請求(call.equeue(CallBack callback))
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
可以看到同步執行請求生成的是RealCall,而異步執行請求生成的是AsyncCall,AsyncCall其實就是Runnable的子類,如果可以執行則對當前添加監聽操作,然後將Call對象放入調度器(dispatcher)中,最後由攔截器中的各個攔截器對該請求進行處理,最終返回Response。
2.5 調度器(dispatcher)
在上面同步執行請求中看到client.dispatcher().executed(this)後由Response result = getResponseWithInterceptorChain()返回結果,最後執行finished()方法,我們先來看看client.dispatcher().executed(this)。
調度器(dispatcher)是保存同步和異步Call的地方,並負責執行異步AsyncCall。我們來看看維護的變量含義:
- int maxRequests = 64:最大併發請求數爲64
- int maxRequestsPerHost = 5:每個主機的最大請求數爲5
- Runnable idleCallback:Runnable對象,在刪除請求時執行
- ExecutorService executorService:線程池
- Deque<AsyncCall> readyAsyncCalls:緩存異步調用準備的任務
- Deque<AsyncCall> runningAsyncCalls:緩存正在運行的異步任務,包括取消尚未完成的任務
- Deque<RealCall> runningSyncCalls:緩存正在運行的同步任務,包括取消尚未完成的任務
如上圖,針對同步請求Dispatcher使用一個Deque保存了同步請求任務;針對異步Dispacher使用了兩個Deque保存任務,一個保存準備執行的請求,一個保存正在執行的請求,爲什麼要兩個呢?因爲Dispatch默認支持併發請求是64個,單個Host最多執行5個併發請求,如果超過,則call會被放入readyAsyncCalls中,當出現空閒線程時,再講readyAsyncCalls中的線程移到runningAsyncCalls中,執行請求。
那麼client.dispatcher().executed(this)裏面做了什麼呢?
@Override public Response execute() throws IOException {
······
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
return result;
} catch (IOException e) {
·····
} finally {
client.dispatcher().finished(this);
}
}
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
裏面只是將同步任務加入到runningSyncCalls中,由Response result = getResponseWithInterceptorChain()攔截器攔截後返回Response 結果,最後執行finished()方法。
/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
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();
}
}
對於同步請求,只是簡單地在runningSyncCalls集合中移除請求,promoteCalls爲false,因此不會執行promoteCalls()中的方法,promoteCalls()裏面主要是遍歷並執行異步請求待執行合集中的請求。下面來看看異步執行中的方法:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
可以看到當“正在執行的請求總數<64 && 單個Host真正執行的請求<5”則將請求加入到runningAsyncCalls執行中的集合中,然後利用線程池執行該請求,否則就直接加入到readyAsyncCalls準備執行的集合中。因爲AsyncCall 是Runnable的子類,在線程池中最終會調用AsyncCall.execute()方法執行異步請求。
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();//攔截器
if (retryAndFollowUpInterceptor.isCanceled()) {//如果攔截失敗回調onFailure方法
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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);//結束
}
}
}
此處的執行邏輯跟同步的大致相同,只是client.dispatcher().finished(this)不一樣,這是一個異步任務,會回調另外一個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) {
·····
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");//移除出請求集合
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
·····
}
可以看到promoteCalls爲true,所以會執行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.
}
}
這個方法主要是遍歷readyAsyncCalls集合中待執行請求,如果runningAsyncCalls的任務數<64並且readyAsyncCalls不爲空,將readyAsyncCalls準備執行的集合請求加入到runningAsyncCalls中並且執行請求。如果runningAsyncCalls的任務數>=64,則說明正在執行的任務池已經滿了,暫時無法加入;如果readyAsyncCalls集合爲空,說明請求都已經執行了,沒有還沒執行的請求。放入readyAsyncCalls集合的請求會繼續走上述的流程,直至到所有的請求被執行。
2.6 攔截器Intercepoter
我們回到最重要的地方攔截器部分:
Response response = getResponseWithInterceptorChain();
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, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
這裏先介紹一個比較重要的類:RealInterceptorChain,攔截器鏈,所有攔截器的合集,網絡的核心也是最後的調用者。
可以看到上面依次添加interceptors,retryAndFollowUpInterceptor,BridgeInterceptor,CacheInterceptor,ConnectInterceptor到RealInterceptorChain中,攔截器之所以可以依次調用,並最終從後先前返回Response,都是依賴chain.proceed(originalRequest)這個方法,
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
·····
// 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);
······
return response;
}
執行當前的攔截器intercept(RealInterceptorChain next)方法,並且調用下一個(index+1)攔截器,下一個攔截器的調用依賴於當前攔截器的intercept()方法中,對RealInterceptorChain的proceed()方法的調用:
response = realChain.proceed(request, streamAllocation, null, null);
可以看到當前攔截器的Response依賴於一下一個攔截器的Response,因此沿着這條攔截器鏈會依次調用下一個攔截器,執行到最後一哥攔截器的時候,就會沿着相反方向依次返回Response,得到最終的Response。
2.7 RetryAndFollowUpInterceptor:重試及重定向攔截器
攔截器類都是繼承Interceptor類,並重寫intercept(Chain chain)方法:
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();//獲取Request對象
RealInterceptorChain realChain = (RealInterceptorChain) chain;//獲取攔截器對象,用於調用下一個proceed()
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
call, eventListener, callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {//循環
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
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.
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();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {//前一個重試得到的response
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//主要爲了新的重試Request添加驗證頭等內容
Request followUp = followUpRequest(response);
if (followUp == null) {//如果一個響應的到的code是200,那麼followUp==null
if (!forWebSocket) {
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());
}
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;//得到處理後的Request,沿着攔截鏈繼續請求
priorResponse = response;
}
}
該攔截器的作用就是重試以及重定向,當一個請求由於各種原因失敗,或者路由異常,則嘗試恢復,否則根據響應碼ResponseCode在followUpRequest(response)方法中對Request進行再處理得到行的Request,然後沿着攔截器繼續新的請求,如果ResponseCode==200,那麼這些過程就結束了。
2.8 BridgeInterceptor:橋攔截器
BridgeInterceptor的主要作用是爲請求Request添加請求頭,爲響應Response添加響應頭:
@Override
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
//----------------Request---------------------
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {//添加Content-Type請求頭
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());
//----------------Response---------------------
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());//保存cookie
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")//Content-Encoding和Content-Length不能用於gzip解壓
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
這個攔截器相對比較簡單。
2.9 CacheInterceptor:緩存攔截器
我們先來看看緩存的響應頭:
Cache-control:標明緩存最大的存活時常;
Date:服務器告訴客戶端,該資源發送的時間;
Expires:表示過期時間(該字段是1.0的東西,與cache-control同時存在時,cache-control的優先級更高);
Last-Modified:服務器告訴客戶端,資源最後的修改時間;
E-Tag:當前資源在服務器的唯一標識,用於判斷資源是否被修改。
還有If-Modified-since和If-none-Match兩個字段,這兩個字段是配合Last-Modified和E-Tag使用的,大致流程如下:服務器收到請求時,會在200 OK中返回該資源的Last-Modified和ETag頭(服務器支持緩存的時候纔有這兩個頭),客戶端將該資源保存在cache中,並且記錄這兩個屬性,當客戶端需要發送相同的請求時,根據Date + Cache-control來判斷緩存是否過期,如果過期了則會在請求中攜帶If-Modified-Since和If-None-Match兩個頭,這兩個頭的值分別是Last-Modified和ETag頭的值,服務器通過這兩個頭判斷資源未發生變化,客戶端重新加載,返回304響應。
先來看看CacheInterceptor幾個比較重要的類:
CacheStrategy:緩存策略類,告訴CacheInterceptor是使用緩存還是使用網絡請求;
Cache:封裝了實際的緩存操作;
DiskLruCache:Cache基於DiskLruCache。
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())//以請求的URL作爲可以來獲取緩存
: null;
long now = System.currentTimeMillis();
//緩存策略類,該類決定了是使用緩存還是網絡請求
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;//網絡請求,如果爲null則代表不適用網絡請求
Response cacheResponse = strategy.cacheResponse;//緩存響應,如果爲null則表示不適用緩存
if (cache != null) {//根據緩存策略,更新統計指標:請求次數,使用網絡請求次數,緩存次數
cache.trackResponse(strategy);
}
//緩存不可用,關閉
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
//如果既無網絡可用,也無緩存可用,則返回504錯誤
// 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());
}
}
//HTTP_NOT_MODIFIED緩存有效,合併網絡請求和緩存
// 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;
}
再簡單說一下流程:
1.如果網絡不可用而且無可用的緩存,則返回504錯誤;
2.繼續,如果不需要網絡請求,則直接使用緩存;
3.繼續,如果網絡請求可用,則進行網絡請求;
4.繼續,如果有緩存,並且網絡請求返回HTTP_NOT_MODIFIED,說明緩存還有效的,則合併網絡響應和緩存的結果,同時更新緩存;
5.繼續,如果沒有緩存,則寫入新的緩存。
CacheStrategy在CacheInterceptor中起到了很關鍵的作用,該類決定了是網絡請求還是緩存,該類最關鍵的代碼是getCandidate()方法:
private CacheStrategy getCandidate() {
// No cached response.
//沒有緩存,直接使用網絡請求
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
//https, 但是沒有握手,直接進行網絡請求
// Drop the cached response if it's missing a required handshake.
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// If this response shouldn't have been stored, it should never be used
// as a response source. This check should be redundant as long as the
// persistence store is well-behaved and the rules are constant.
if (!isCacheable(cacheResponse, request)) {//不可以緩存,直接進行網絡請求
return new CacheStrategy(request, null);
}
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
//請求頭nocache或者請求頭包含If-Modified-Since或者If-None-Match
//請求頭包含If-Modified-Since或者If-None-Match意味着本地緩存過期,需要服務器驗證
//本地緩存是不是還能繼續使用
return new CacheStrategy(request, null);
}
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.immutable()) {//強制使用緩存
return new CacheStrategy(null, cacheResponse);
}
long ageMillis = cacheResponseAge();
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//可緩存,並且ageMillis + minFreshMillis < freshMillis + maxStaleMillis
//意味着雖過期,但是可用,在請求頭添加“warning”
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());//使用緩存
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
//走到這裏說明緩存已經過期
//添加請求頭:If-Modified-Since或者If-None-Match
//etag與If-None-Match配合使用
//lastModified與If-Modified-Since配合使用
//前者和後者的值是相同的
//區別在於前者是響應頭,後者是請求頭。
//後者用於服務器進行資源比對,看看是資源是否改變了。
// 如果沒有,則本地的資源雖過期還是可以用的
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
大致的流程如下:(if-else的關係)
1.沒有緩存,直接使用網絡請求;
2.如果是https,但是沒有握手,直接網絡請求;
3.不可緩存,直接網絡請求;
4.請求頭nocache,或者請求頭包含If-Modified-Since或者If-None-Match,則需要服務器驗證本地緩存是否繼續使用,直接網絡請求;
5.可緩存,並且ageMillis + minFreshMillis < freshMillis + maxStaleMillis(意味着過期,但是可用,會在請求頭添加warning),則使用緩存;
6.緩存過期,則添加請求頭,If-Modified-Since或者If-None-Match,進行網絡請求。
緩存攔截器流程圖:
2.10 ConnectInterceptor(連接池攔截器)
ConnectInterceptor是一個鏈接相關的攔截器,這個攔截器的代碼最少,但並不是最簡單的,先來看看ConnectInterceptor中比較重要的相關類:
RouteDatabase:這是一個關於路由器白名單和黑名單類,處於黑名單的信息會避免不必要的嘗試;
RealConnection:Connect子類,主要 實現鏈接的建立等工作;
ConnectionPool:連接池,實現鏈接的複用;
Connection和Stream的關係:Http1是1:1的關係,Http2是1對多的關係;就是說一個http1.x的鏈接只能被一個請求使用,而一個http2.x鏈接對應多個Stream,多個Stream的意思是Http2連接支持併發請求,即一個鏈接可以被多個請求使用。還有Http1.x的keep-alive機制的作用是保證鏈接使用完不關閉,當下一次請求與鏈接的Host相同的時候,連接可以直接使用,不用創建(節省資源,提高性能)。
StreamAllocation:流分配,流是什麼呢?我們知道Connection是一個連接遠程服務器的Socket連接,而Stream是基於Connection邏輯Http 請求/響應對,StreamAllocation會通過ConnectionPool獲取或者新生成一個RealConnection來得到一個連接到server的Connection連接,同時會生成一個HttpCodec用於下一個CallServerInterceptor,以完成最終的請求。
HttpCodec:Encodes HTTP requests and decodes HTTP responses(源碼註釋),大意爲編碼Http請求,解碼Http響應。針對不同的版本OKHttp爲我們提供了HttpCodec1(Http1.x)和HttpCodec2(Http2.x)。
一句話概括就是:分配一個Connection和HttpCodec,爲最終的請求做準備。
/** Opens a connection to the target server and proceeds to the next interceptor. */
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.
//我們需要網絡來滿足這個請求,可能是爲了驗證一個條件GET請求(緩存驗證等)
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
代碼量表面看起來很少,但是大部分都已經封裝好的了,這裏僅僅是 調用,爲了可讀性和維護性,該封裝的還是要封裝。這裏的核心代碼就兩行:
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
可以看出,主要的工作由streamAllocation完成,我們來看看newStream()和connection()做了什麼工作:
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
//找到一個可用鏈接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
可以看到最關鍵的一步是findHealthyConnection(),這個方法的主要作用是找到可用的鏈接,如果連接不可用,這個過程會一直持續哦。
/**
* 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;
}
}
我們來看看你noNewStreams()做了什麼操作:
/** Forbid new streams from being created on the connection that hosts this allocation. */
public void noNewStreams() {
Socket socket;
Connection releasedConnection;
synchronized (connectionPool) {
releasedConnection = connection;
socket = deallocate(true, false, false);//noNewStreams,released,streamFinished核心方法
if (connection != null) releasedConnection = null;
}
closeQuietly(socket);//關閉Socket
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);//監聽回調
}
}
上面的關鍵代碼是deallocate():
private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
assert (Thread.holdsLock(connectionPool));
//這裏以noNewStreams爲true,released爲false,streamFinished爲false爲例
if (streamFinished) {
this.codec = null;
}
if (released) {
this.released = true;
}
Socket socket = null;
if (connection != null) {
if (noNewStreams) {
//noNewStreams是RealConnection的屬性,如果爲true則這個鏈接不會創建新的Stream,一但設置爲true就一直爲true
//搜索整個源碼,這個該屬性設置地方如下:
//evitAll:關閉和移除連接池中的所有鏈接,(如果連接空閒,即連接上的Stream數爲0,則noNewStreams爲true);
//pruneAndGetAllocationCount:移除內存泄漏的連接,以及獲取連接Stream的分配數;
//streamFailed:Stream分配失敗
//綜上所述,這個屬性的作用是禁止無效連接創建新的Stream的
connection.noNewStreams = true;
}
if (this.codec == null && (this.released || connection.noNewStreams)) {
release(connection);//釋放Connection承載的StreamAllocations資源(connection.allocations)
if (connection.allocations.isEmpty()) {
connection.idleAtNanos = System.nanoTime();
//connectionBecameIdle:通知線程池該連接是空閒連接,可以作爲移除或者待移除對象
if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
socket = connection.socket();
}
}
connection = null;//
}
}
return socket;//返回待關閉的Socket對象
}
我們來看看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 {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
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. We need to be careful here because our
// already-allocated connection may have been restricted from creating new streams.
//這個方法與deallocate()方法作用一致
//如果連接不能創建Stream,則釋放資源,返回待關閉的close Socket
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
//經過releaseIfNoNewStreams(),如果connection不爲null,則連接可用
if (this.connection != null) {
// We had an already-allocated connection and it's good.
//存在可使用的已分配連接,
result = this.connection;
//爲null值,說明這個連接是有效的
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, don't report it as released!
releasedConnection = null;
}
//沒有可用連接,去連接池中找
if (result == null) {
// Attempt to get a connection from the pool.
//通過ConnectionPool,Address,StreamAllocation從連接池中獲取連接
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
//找到一個已分配或者連接池中的連接,此過程結束,返回
return result;
}
//否則我們需要一個路由信心,這是一個阻塞操作
// If we need a route selection, make one. This is a blocking operation.
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
//提供更加全面的路由信息,再次從連接池中獲取連接
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
}
//實在是沒有找到,只能生成新的連接
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// 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, false);//添加Connection的StreamAllocation添加到connection.allocations集合中
}
}
// If we found a pooled connection on the 2nd time around, we're done.
//如果連接是從連接池中找到的,那麼說明連接是可複用的,不是新生的,新生成的連接是需要連接服務器才能可用的
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// Do TCP + TLS handshakes. This is a blocking operation.連接server
result.connect(
connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());//將路由信息添加到routeDatabase中
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// 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.
//如果是HTTP2連接,由於HTTP2連接具有多路複用特性
//因此,我們需要確保http2的多路複用特性
if (result.isMultiplexed()) {
//確保http2的多路複用特性,重複的連接將 被踢除
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
上面的源碼比較長,加了註釋。我們來看看流程圖:
a)排除連接不可用的情況;
private Socket releaseIfNoNewStreams() {
assert (Thread.holdsLock(connectionPool));
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && allocatedConnection.noNewStreams) {
return deallocate(false, false, true);
}
return null;
}
這個方法是說如果狀態處於releaseIfNoNewStreams狀態,釋放該連接,否則,該連接可用
b)判斷連接是否可用
//經過releaseIfNoNewStreams(),如果connection不爲null,則連接可用
if (this.connection != null) {
// We had an already-allocated connection and it's good.
//存在可使用的已分配連接,
result = this.connection;
//爲null值,說明這個連接是有效的
releasedConnection = null;
}
經過releaseIfNoNewStreams()檢查後,Connection不爲空則連接可用
c)第一次連接池查找,沒有提供路由信息
//通過ConnectionPool,Address,StreamAllocation從連接池中獲取連接
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
}
如果找到,則將連接賦值給result
d)遍歷路由器進行二次查找
//提供更加全面的路由信息,再次從連接池中獲取連接
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
e)如果還是沒有找到,則只能創建新的連接了
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);//添加Connection的StreamAllocation添加到connection.allocations集合中
f)新的連接,連接服務器
// Do TCP + TLS handshakes. This is a blocking operation.連接server
result.connect(
connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());//將路由信息添加到routeDatabase中
g)新的連接放入線程池
// Pool the connection.
Internal.instance.put(connectionPool, result);//將新生成的連接放入連接池中
h)如果連接是一個HTTP2連接,則需要確保多路複用的特性
//如果是HTTP2連接,由於HTTP2連接具有多路複用特性
//因此,我們需要確保http2的多路複用特性
if (result.isMultiplexed()) {
//確保http2的多路複用特性,重複的連接將 被踢除
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
在Connectinterceptor中起到關鍵作用的就是ConnectionPool,我們來看看ConnectionPool連接池的源碼:
在目前版本,連接池是默認保持5個空閒連接的,這些空閒連接如果超過五分鐘不被使用,則會被連接池移除,這個兩個數值以後可能會改變,同時也是可以自定義修改的
- RouteDatabase:路由記錄表,這是一個關於路由信息的白名單和黑名單類,處於黑名單的路由信息會被避免不必要的嘗試;
- Deque:隊列,存放待複用的連接
- ThreadPoolExecutor:線程池,用於支持線程池的clearup任務,清除idel線程
對於連接池我們聯想到的是,存、去、清除:
1)存
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
可以看到,在存入連接池connections.add(connection)之前,可能需要執行連接池的清潔任務,連接存入連接池的操作很簡單,主要看一下clearup做了什麼:
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
// Find either a connection to evict, or the time that the next eviction is due.
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
// If the connection is in use, keep searching.
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;//連接池中處於使用狀態的連接數
continue;
}
idleConnectionCount++;//處於空閒狀態的連接數
// If the connection is ready to be evicted, we're done.
long idleDurationNs = now - connection.idleAtNanos;
//尋找空閒最久的那個連接
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
//空閒最久的那個連接
//如果空閒連接大於keepAliveDurationNs默認五分鐘
//如果空閒連接數大於maxIdleConnections默認五個
//則執行移除操作
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
return keepAliveDurationNs;
} else {
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
}
closeQuietly(longestIdleConnection.socket());//關閉socket
// Cleanup again immediately.
return 0;
}
這個方法根據兩個指標判斷是否移除空閒時間最長的連接,大於空閒值或者連接數超過最大值,則移除空閒時間最長的空閒連接,clearup方法的執行也依賴與另一個比較重要的方法:pruneAndGetAllocationCount(connection, now)該方法的作用是移除發生泄漏的StreamAllocation,統計連接中正在使用的StreamAllocation個數。
2)取
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
//isEligible是判斷一個連接是否還能攜帶一個StreamAllocation,如果能則說明這個連接可用
if (connection.isEligible(address, route)) {
//將StreamAllocation添加到connection.allocations中
streamAllocation.acquire(connection, true);
return connection;
}
}
return null;
}
首先,判斷Address對應的Connection是否還能承載一個新的StreamAllocation,可以的話我們將這個StreamAllocation添加到connection.allocations中,最後返回這個Connection。
3)移除
public void evictAll() {
List<RealConnection> evictedConnections = new ArrayList<>();
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
if (connection.allocations.isEmpty()) {
connection.noNewStreams = true;
evictedConnections.add(connection);
i.remove();
}
}
}
for (RealConnection connection : evictedConnections) {
closeQuietly(connection.socket());
}
}
關閉並移除連接池中的空閒連接。
(11)CallServerInterceptor
攔截器鏈最後的攔截器,使用HttpCodec完成最後的請求發送。
三、總結
okhttp是一個http+http2的客戶端,使用android+java的應用,整體分析完之後,再來看下面這個框架架構圖就清晰很多了
至此,本文結束!
請尊重原創者版權,轉載請標明出處:https://blog.csdn.net/m0_37796683/article/details/101306070 謝謝!
相關文章:
- Retrofit2的介紹和簡單使用
- OKHttp3的用法介紹和解析
- 從源碼角度解釋OKHttp3的關鍵流程和重要操作
- 詳細介紹了RxJava的使用(基本創建、快速創建、延遲創建等操作符)
- RxJava轉換、組合、合併等操作符的使用
- RxJava延遲、do相關、錯誤處理等操作符的使用
- RxJava過濾、其他操作符的使用
上述幾篇都是android開發必須掌握的,後續會完善其他部分!