OkHttpClient源码分析(五)—— ConnectInterceptor和CallServerInterceptor
上一篇我們介紹了緩存攔截器CacheInterceptor,本篇將介紹剩下的兩個(gè)攔截器: ConnectInterceptor和CallServerInterceptor
ConnectInterceptor
該攔截器主要是負(fù)責(zé)建立可用的鏈接,主要作用是打開(kāi)了與服務(wù)器的鏈接,正式開(kāi)啟了網(wǎng)絡(luò)請(qǐng)求。 查看其intercept()方法:
@Override public Response intercept(Chain chain) throws IOException {RealInterceptorChain realChain = (RealInterceptorChain) chain;//從攔截器鏈中獲取StreamAllocation對(duì)象Request request = realChain.request();StreamAllocation streamAllocation = realChain.streamAllocation();//創(chuàng)建HttpCodec對(duì)象HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);//獲取realConnetionRealConnection connection = streamAllocation.connection();//執(zhí)行下一個(gè)攔截器,返回responsereturn realChain.proceed(request, streamAllocation, httpCodec, connection);} 復(fù)制代碼可以看到intercept中的處理很簡(jiǎn)單,主要有以下幾步操作:
從攔截器鏈中獲取StreamAllocation對(duì)象,在講解第一個(gè)攔截器RetryAndFollowUpInterceptor的時(shí)候,我們已經(jīng)初步了解了StreamAllocation對(duì)象,在RetryAndFollowUpInterceptor中僅僅只是創(chuàng)建了StreamAllocation對(duì)象,并沒(méi)有進(jìn)行使用,到了ConnectInterceptor中,StreamAllocation才被真正使用到,該攔截器的主要功能都交給了StreamAllocation處理;
執(zhí)行StreamAllocation對(duì)象的 newStream() 方法創(chuàng)建HttpCodec,用于處理編碼Request和解碼Response;
接著通過(guò)調(diào)用StreamAllocation對(duì)象的 connection() 方法獲取到RealConnection對(duì)象,這個(gè)RealConnection對(duì)象是用來(lái)進(jìn)行實(shí)際的網(wǎng)絡(luò)IO傳輸?shù)摹?/p>
調(diào)用攔截器鏈的**proceed()**方法,執(zhí)行下一個(gè)攔截器返回response對(duì)象。
上面我們已經(jīng)了解了ConnectInterceptor攔截器的intercept()方法的整體流程,主要的邏輯是在StreamAllocation對(duì)象中,我們先看下它的 newStream() 方法:
public HttpCodec newStream(OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {...try {//創(chuàng)建RealConnection對(duì)象RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);//創(chuàng)建HttpCodec對(duì)象HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);synchronized (connectionPool) {codec = resultCodec;//返回HttpCodec對(duì)象return resultCodec;}} catch (IOException e) {throw new RouteException(e);}} 復(fù)制代碼newStream()方法中,主要是創(chuàng)建了RealConnection對(duì)象(用于進(jìn)行實(shí)際的網(wǎng)絡(luò)IO傳輸)和HttpCodec對(duì)象(用于處理編碼Request和解碼Response),并將HttpCodec對(duì)象返回。
findHealthyConnection()方法用于創(chuàng)建RealConnection對(duì)象:
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)throws IOException {while (true) {//while循環(huán)//獲取RealConnection對(duì)象RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,connectionRetryEnabled);//同步代碼塊判斷RealConnection對(duì)象的successCount是否為0synchronized (connectionPool) {if (candidate.successCount == 0) {//如果為0則返回return candidate;}}//對(duì)鏈接池中不健康的鏈接做銷毀處理if (!candidate.isHealthy(doExtensiveHealthChecks)) {noNewStreams();continue;}return candidate;}} 復(fù)制代碼以上代碼主要做的事情有:
我們看下findConnection()方法做了哪些操作:
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,boolean connectionRetryEnabled) throws IOException {...RealConnection result = null;...synchronized (connectionPool) {...releasedConnection = this.connection;toClose = releaseIfNoNewStreams();if (this.connection != null) {//如果不為 null,則復(fù)用,賦值給 resultresult = this.connection;releasedConnection = null;}...//如果result為 null,說(shuō)明上面找不到可以復(fù)用的if (result == null) {//從連接池中獲取,調(diào)用其get()方法Internal.instance.get(connectionPool, address, this, null);if (connection != null) {//找到對(duì)應(yīng)的 RealConnection對(duì)象//更改標(biāo)志位,賦值給 resultfoundPooledConnection = true;result = connection;} else {selectedRoute = route;}}}...if (result != null) {//已經(jīng)找到 RealConnection對(duì)象,直接返回return result;}...//連接池中找不到,new一個(gè)result = new RealConnection(connectionPool, selectedRoute);......//發(fā)起請(qǐng)求result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);...//存進(jìn)連接池中,調(diào)用其put()方法Internal.instance.put(connectionPool, result);...return result;} 復(fù)制代碼以上代碼主要做的事情有:
ConnectionPool 連接池介紹
剛才我們說(shuō)到從連接池中取出RealConnection對(duì)象時(shí)調(diào)用了Internal的get()方法,存進(jìn)去的時(shí)候調(diào)用了其put()方法。其中Internal是一個(gè)抽象類,里面定義了一個(gè)靜態(tài)變量instance:
public abstract class Internal {...public static Internal instance;... } 復(fù)制代碼instance的實(shí)例化是在OkHttpClient的靜態(tài)代碼塊中:
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {...static {Internal.instance = new Internal() {...@Override public RealConnection get(ConnectionPool pool, Address address,StreamAllocation streamAllocation, Route route) {return pool.get(address, streamAllocation, route);}...@Override public void put(ConnectionPool pool, RealConnection connection) {pool.put(connection);}};}... } 復(fù)制代碼這里我們可以看到實(shí)際上 Internal 的 get()方法和put()方法是調(diào)用了 ConnectionPool 的get()方法和put()方法,這里我們簡(jiǎn)單看下ConnectionPool的這兩個(gè)方法:
private final Deque<RealConnection> connections = new ArrayDeque<>();@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, true);return connection;}}return null;} 復(fù)制代碼在get()方法中,通過(guò)遍歷connections(用于存放RealConnection的ArrayDeque隊(duì)列),調(diào)用RealConnection的isEligible()方法判斷其是否可用,如果可用就會(huì)調(diào)用streamAllocation的acquire()方法,并返回connection。
我們看下調(diào)用StreamAllocation的acquire()方法到底做了什么操作:
public void acquire(RealConnection connection, boolean reportedAcquired) {assert (Thread.holdsLock(connectionPool));if (this.connection != null) throw new IllegalStateException();//賦值給全局變量this.connection = connection;this.reportedAcquired = reportedAcquired;//創(chuàng)建StreamAllocationReference對(duì)象并添加到allocations集合中connection.allocations.add(new StreamAllocationReference(this, callStackTrace));} 復(fù)制代碼先是從連接池中獲取的RealConnection對(duì)象賦值給StreamAllocation的成員變量connection;
創(chuàng)建StreamAllocationReference對(duì)象(StreamAllocation對(duì)象的弱引用), 并添加到RealConnection的allocations集合中,到時(shí)可以通過(guò)allocations集合的大小來(lái)判斷網(wǎng)絡(luò)連接次數(shù)是否超過(guò)OkHttp指定的連接次數(shù)。
接著我們查看ConnectionPool 的put()方法:
void put(RealConnection connection) {assert (Thread.holdsLock(this));if (!cleanupRunning) {cleanupRunning = true;executor.execute(cleanupRunnable);}connections.add(connection);} 復(fù)制代碼put()方法在將連接添加到連接池之前,會(huì)先執(zhí)行清理任務(wù),通過(guò)判斷cleanupRunning是否在執(zhí)行,如果當(dāng)前清理任務(wù)沒(méi)有執(zhí)行,則更改cleanupRunning標(biāo)識(shí),并執(zhí)行清理任務(wù)cleanupRunnable。
我們看下清理任務(wù)cleanupRunnable中到底做了哪些操作:
private final Runnable cleanupRunnable = new Runnable() {@Override public void run() {while (true) {//對(duì)連接池進(jìn)行清理,返回進(jìn)行下次清理的間隔時(shí)間。long waitNanos = cleanup(System.nanoTime());if (waitNanos == -1) return;if (waitNanos > 0) {long waitMillis = waitNanos / 1000000L;waitNanos -= (waitMillis * 1000000L);synchronized (ConnectionPool.this) {try {//進(jìn)行等待ConnectionPool.this.wait(waitMillis, (int) waitNanos);} catch (InterruptedException ignored) {}}}}}}; 復(fù)制代碼可以看到run()方法里面是一個(gè)while死循環(huán),其中調(diào)用了cleanup()方法進(jìn)行清理操作,同時(shí)會(huì)返回進(jìn)行下次清理的間隔時(shí)間,如果返回的時(shí)間間隔為-1,則會(huì)結(jié)束循環(huán),如果不是-1,則會(huì)調(diào)用wait()方法進(jìn)行等待,等待完成后又會(huì)繼續(xù)循環(huán)執(zhí)行,具體的清理操作在cleanup()方法中:
long cleanup(long now) {//正在使用的連接數(shù)int inUseConnectionCount = 0;//空閑的連接數(shù)int idleConnectionCount = 0;//空閑時(shí)間最長(zhǎng)的連接RealConnection longestIdleConnection = null;//最大的空閑時(shí)間,初始化為 Long 的最小值,用于記錄所有空閑連接中空閑最久的時(shí)間long longestIdleDurationNs = Long.MIN_VALUE;synchronized (this) {//for循環(huán)遍歷connections隊(duì)列for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {RealConnection connection = i.next();//如果遍歷到的連接正在使用,則跳過(guò),continue繼續(xù)遍歷下一個(gè)if (pruneAndGetAllocationCount(connection, now) > 0) {inUseConnectionCount++;continue;}//當(dāng)前連接處于空閑,空閑連接數(shù)++idleConnectionCount++;//計(jì)算空閑時(shí)間long idleDurationNs = now - connection.idleAtNanos;//空閑時(shí)間如果超過(guò)最大空閑時(shí)間if (idleDurationNs > longestIdleDurationNs) {//重新賦值最大空閑時(shí)間longestIdleDurationNs = idleDurationNs;//賦值空閑最久的連接longestIdleConnection = connection;}}if (longestIdleDurationNs >= this.keepAliveDurationNs|| idleConnectionCount > this.maxIdleConnections) {//如果最大空閑時(shí)間超過(guò)空閑保活時(shí)間或空閑連接數(shù)超過(guò)最大空閑連接數(shù)限制//則移除該連接connections.remove(longestIdleConnection);} else if (idleConnectionCount > 0) {//如果存在空閑連接//計(jì)算出線程清理的時(shí)間即(保活時(shí)間-最大空閑時(shí)間),并返回return keepAliveDurationNs - longestIdleDurationNs;} else if (inUseConnectionCount > 0) {//沒(méi)有空閑連接,返回keepAliveDurationNsreturn keepAliveDurationNs;} else {//連接池中沒(méi)有連接存在,返回-1cleanupRunning = false;return -1;}}//關(guān)閉空閑時(shí)間最長(zhǎng)的連接closeQuietly(longestIdleConnection.socket());return 0;} 復(fù)制代碼cleanup()方法通過(guò)for循環(huán)遍歷connections隊(duì)列,記錄最大空閑時(shí)間和空閑時(shí)間最長(zhǎng)的連接;如果存在超過(guò)空閑保活時(shí)間或空閑連接數(shù)超過(guò)最大空閑連接數(shù)限制的連接,則從connections中移除,最后執(zhí)行關(guān)閉該連接的操作。
主要是通過(guò)pruneAndGetAllocationCount()方法判斷連接是否處于空閑狀態(tài):
private int pruneAndGetAllocationCount(RealConnection connection, long now) {List<Reference<StreamAllocation>> references = connection.allocations;for (int i = 0; i < references.size(); ) {Reference<StreamAllocation> reference = references.get(i);if (reference.get() != null) {i++;continue;}...references.remove(i);connection.noNewStreams = true;...if (references.isEmpty()) {connection.idleAtNanos = now - keepAliveDurationNs;return 0;}}return references.size();} 復(fù)制代碼該方法通過(guò)for循環(huán)遍歷RealConnection的allocations集合,如果當(dāng)前遍歷到的StreamAllocation被使用就遍歷下一個(gè),否則就將其移除,如果移除后列表為空,則返回0,所以如果方法的返回值為0則說(shuō)明當(dāng)前連接處于空閑狀態(tài),如果返回值大于0則說(shuō)明連接正在使用。
CallServerInterceptor
接下來(lái)講解最后一個(gè)攔截器CallServerInterceptor了,查看intercept()方法:
@Override public Response intercept(Chain chain) throws IOException {RealInterceptorChain realChain = (RealInterceptorChain) chain;//相關(guān)對(duì)象的獲取 HttpCodec httpCodec = realChain.httpStream();StreamAllocation streamAllocation = realChain.streamAllocation();RealConnection connection = (RealConnection) realChain.connection();Request request = realChain.request();...//寫(xiě)入請(qǐng)求頭httpCodec.writeRequestHeaders(request);Response.Builder responseBuilder = null;if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {//判斷是否有請(qǐng)求體if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {//詢問(wèn)服務(wù)器是否愿意接收請(qǐng)求體httpCodec.flushRequest();//刷新請(qǐng)求realChain.eventListener().responseHeadersStart(realChain.call());responseBuilder = httpCodec.readResponseHeaders(true);}if (responseBuilder == null) {//服務(wù)器愿意接收請(qǐng)求體//寫(xiě)入請(qǐng)求體...} else if (!connection.isMultiplexed()) {streamAllocation.noNewStreams();}}//結(jié)束請(qǐng)求httpCodec.finishRequest();if (responseBuilder == null) {realChain.eventListener().responseHeadersStart(realChain.call());//根據(jù)服務(wù)器返回的數(shù)據(jù)構(gòu)建 responseBuilder對(duì)象responseBuilder = httpCodec.readResponseHeaders(false);}//構(gòu)建 response對(duì)象Response response = responseBuilder.request(request).handshake(streamAllocation.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();...//設(shè)置 response的 bodyresponse = response.newBuilder().body(httpCodec.openResponseBody(response)).build();//如果請(qǐng)求頭中 Connection對(duì)應(yīng)的值為 close,則關(guān)閉連接if ("close".equalsIgnoreCase(response.request().header("Connection"))|| "close".equalsIgnoreCase(response.header("Connection"))) {streamAllocation.noNewStreams();}...return response;} 復(fù)制代碼以上代碼具體的流程:
??好了,到這里OkHttpClient源碼分析就結(jié)束了,相信看完本套源碼解析會(huì)加深你對(duì)OkHttpClient的認(rèn)識(shí),同時(shí)也學(xué)到了其巧妙的代碼設(shè)計(jì)思路,在閱讀源碼的過(guò)程中,我們的編碼能力也逐步提升,如果想要寫(xiě)更加優(yōu)質(zhì)的代碼,閱讀源碼是一件很有幫助的事。
總結(jié)
以上是生活随笔為你收集整理的OkHttpClient源码分析(五)—— ConnectInterceptor和CallServerInterceptor的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: easyui datagrid 表头固定
- 下一篇: laravel Collection m