OkHttp 源碼深入分析(一)

一、概述

OkHttp 對(duì)于 Android 開(kāi)發(fā)人員來(lái)說(shuō)想必是人盡皆知的一個(gè)網(wǎng)絡(luò)請(qǐng)求框架了次舌,問(wèn)世之初就火爆了 Android 開(kāi)發(fā)圈,其優(yōu)異的設(shè)計(jì)更是讓不少技術(shù)大佬贊不絕口荆责,由其衍生的各種基于 OkHttp 的網(wǎng)絡(luò)框架也是層出不窮睹逃,同時(shí)各種對(duì)于 OkHttp 源碼分析文章也是數(shù)不勝數(shù),更是成為了面試常問(wèn)的問(wèn)題早直,可以說(shuō)市面上大多 App 都在使用著這套框架寥假。而且 Android 自 6.0 開(kāi)始也將內(nèi)部默認(rèn)的 HttpUrlConnection 換為了 OkHttp。更是確立了 OkHttp 在 Android 開(kāi)發(fā)生態(tài)中的地位霞扬。

對(duì)于每天都在使用的框架糕韧,如果只停留在使用而不了解它基本的工作原理,顯然是只見(jiàn)樹(shù)木不見(jiàn)森林的做法喻圃,也不利于自我提高萤彩,而互聯(lián)網(wǎng)的包容和開(kāi)放,更是讓我們可以輕松的看到這種優(yōu)秀的源碼斧拍,了解大神的思維以及設(shè)計(jì)一個(gè)優(yōu)秀框架的思想雀扶。畢竟站在巨人的肩膀上才能看到更遠(yuǎn)處的風(fēng)景。

本文將會(huì)從使用著手饮焦,不會(huì)太糾纏于細(xì)枝末節(jié)怕吴,著重分析 OkHttp 從發(fā)起請(qǐng)求到獲取響應(yīng)的總體脈絡(luò)流程,代碼會(huì)進(jìn)行適當(dāng)精簡(jiǎn)县踢,只展示核心邏輯

二转绷、一個(gè)簡(jiǎn)單的請(qǐng)求

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
                .url("https://raw.github.com/square/okhttp/master/README.md")
                .build();
                
Response response = client.newCall(request).execute()

System.out.println("OKHTTP : " + response.body().string());

上述代碼是簡(jiǎn)化后的官方 GET 同步請(qǐng)求的例子,也是我們?nèi)粘J褂玫姆绞脚鹌。煌氖钱惒秸{(diào)用的是 enqueue 而已议经,這么幾行代碼就發(fā)起了一個(gè)網(wǎng)絡(luò)請(qǐng)求,我是著實(shí)有點(diǎn)好奇 OkHttp 內(nèi)部到底做了什么處理谴返,老規(guī)矩煞肾,讓我們先看下 OkHttp 的流程圖


在這里插入圖片描述

通過(guò)上圖我們可以看到整個(gè)流程的關(guān)鍵節(jié)點(diǎn)由以下幾個(gè)核心類(lèi)組成

OkHttpClient

顧名思義 OkHttpClient 為我們創(chuàng)建一個(gè)請(qǐng)求的客戶(hù)端,內(nèi)部對(duì) OkHttp 各項(xiàng)參數(shù)進(jìn)行了默認(rèn)初始化嗓袱,同時(shí)對(duì) OkHttp 的處理邏輯進(jìn)行統(tǒng)一管理籍救,它通過(guò) Builder 構(gòu)造器生成,而根據(jù) OkHttp 的官方建議渠抹,對(duì)于使用來(lái)說(shuō) OkHttpClient 在全局的實(shí)例一個(gè)就夠了蝙昙,無(wú)須創(chuàng)造多個(gè)造成資源浪費(fèi)

Request 和 Response

Request 對(duì)象是我們要發(fā)送的具體請(qǐng)求闪萄,通過(guò)源碼我們可以發(fā)現(xiàn),它也是通過(guò) Builder 進(jìn)行構(gòu)建奇颠,內(nèi)部包含了符合 Http 協(xié)議的必要配置參數(shù)

// Request 的構(gòu)造函數(shù)
Request(Builder builder) {
    this.url = builder.url; // 請(qǐng)求的 url 地址
    this.method = builder.method; // 請(qǐng)求方法  get/pos
    this.headers = builder.headers.build(); //請(qǐng)求頭
    this.body = builder.body; //請(qǐng)求體
    this.tags = Util.immutableMap(builder.tags); //請(qǐng)求的 tag 標(biāo)記用于取消請(qǐng)求
}

Response 則是請(qǐng)求的結(jié)果信息败去,通過(guò)源碼我們可以看到與 Request 如出一轍,也是通過(guò) Builder 進(jìn)行構(gòu)建

//Response 構(gòu)造函數(shù)烈拒,省略部分代碼
Response(Builder builder) {
    this.request = builder.request;//發(fā)起的請(qǐng)求
    this.protocol = builder.protocol;//協(xié)議
    this.code = builder.code;// 響應(yīng)碼
    this.message = builder.message; //響應(yīng)信息
    this.headers = builder.headers.build(); //響應(yīng)頭
    this.body = builder.body //響應(yīng)body
}

RealCall

RealCall 正如它的名字一樣圆裕,是真正發(fā)起實(shí)際請(qǐng)求并處理請(qǐng)求內(nèi)部一系列邏輯的類(lèi),所有的請(qǐng)求都會(huì)經(jīng)過(guò)它的處理和調(diào)度荆几,所以接下來(lái)通過(guò)部分代碼讓我們看看 RealCall 到底做了些什么

RealCall 的實(shí)例化過(guò)程
//這是OkHttp獲取請(qǐng)求結(jié)果的一行代碼
//這里 OkHttpCient 將構(gòu)建好的 request 放入 newCall 
//函數(shù)中并直接執(zhí)行
Response response = client.newCall(request).execute()


//OkHttpClient 的 newCall 函數(shù)主要作用是將當(dāng)前
//OkHttpClient 和 Request 的引用傳給 RealCall 
 @Override 
 public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
 }
 
 // RealCall 類(lèi)的靜態(tài) newRealCall 方法,這里是真正創(chuàng)建
 //RealCall 實(shí)例對(duì)象的例子
 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.transmitter = new Transmitter(client, call);
    return call;
  }
 
getResponseWithInterceptorChain 方法

創(chuàng)建完 RealCall 的實(shí)例后吓妆,我們就可以進(jìn)行 execute 和 enqueue 操作,也就是同步可異步的請(qǐng)求伴郁,但如果我們繼續(xù)跟進(jìn)代碼就會(huì)發(fā)現(xiàn)無(wú)論是同步還是異步最終都會(huì)調(diào)用 getResponseWithInterceptorChain() 方法來(lái)獲取 response耿战,而區(qū)別就是同步方法是直接調(diào)用,異步方法則是通過(guò)封裝了一個(gè) AsyncCall 線(xiàn)程來(lái)調(diào)用焊傅。所以我們可以基本確定整個(gè) OkHttp 的核心請(qǐng)求邏輯應(yīng)該就隱藏在這個(gè)方法當(dāng)中,讓我們來(lái)看下這方法的源碼

 Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    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, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

getResponseWithInterceptorChain 方法的內(nèi)部邏輯還是很簡(jiǎn)單的狈涮,主要是構(gòu)建了一個(gè)攔截器的集合狐胎,將所有的攔截器按照順序依次添加,然后創(chuàng)建了一個(gè) RealInterceptorChain 實(shí)例歌馍,通過(guò) proceed 方法按照順序執(zhí)行各個(gè)攔截器邏輯握巢,跟進(jìn)源碼繼續(xù)看看

 public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
            throws IOException {
        if (index >= interceptors.size()) throw new AssertionError();

        calls++;

        //省略若干代碼...

        // Call the next interceptor in the chain.
        RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
                index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
        Interceptor interceptor = interceptors.get(index);
        Response response = interceptor.intercept(next);
        
        //省略若干代碼...

        return response;
    }

proceed 方法內(nèi)部做的事情也并不復(fù)雜,總結(jié)起來(lái)就是這個(gè)方法會(huì)根據(jù) interceptors.size 來(lái)進(jìn)行一個(gè)循環(huán)的攔截器調(diào)用(具體從哪里循環(huán)后面會(huì)講到)松却,在執(zhí)行攔截器之前會(huì)創(chuàng)建一下攔截器的 RealInterceptorChain 實(shí)例暴浦,同時(shí) index 也會(huì)遞增,然后執(zhí)行當(dāng)前的攔截器鏈條并將下一個(gè)鏈條傳入晓锻,整個(gè)責(zé)任鏈就是以這種內(nèi)嵌的方式層層執(zhí)行歌焦,直到所有鏈條上的攔截器執(zhí)行完畢為止

其實(shí)從上面的代碼我們也可以清晰的看出 OkHttp 與我們以往的網(wǎng)絡(luò)請(qǐng)求框架的明顯區(qū)別,以往的網(wǎng)絡(luò)請(qǐng)求框架其請(qǐng)求過(guò)程基本都是封裝好的很難做出改變砚哆,而 OkHttp 則將請(qǐng)求的邏輯以攔截器的方式切成一個(gè)個(gè)獨(dú)立的執(zhí)行單元独撇,然后通過(guò)責(zé)任鏈的設(shè)計(jì)模式將其串聯(lián)成一個(gè)整體,最終通過(guò)層層的處理獲取到我們想要的響應(yīng)躁锁。而且攔截器對(duì)外是開(kāi)放的纷铣,這使得開(kāi)發(fā)者可以在請(qǐng)求的過(guò)程中根據(jù)需求定制屬于自己的攔截器,可以說(shuō)這也是 OkHttp 為什么備受推崇的原因之一战转。

二搜立、OkHttp 的攔截器

從上面的分析中我們可以看到 OkHttp 默認(rèn)為我們提供了五個(gè)攔截器如下

  • RetryAndFollowUpInterceptor
  • BridgeInterceptor
  • CacheInterceptor
  • ConnectInterceptor
  • CallServerInterceptor

接下來(lái)我們依次來(lái)分析一下各個(gè)攔截器的內(nèi)部邏輯

2.1、RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor 主要是用來(lái)處理連接失敗過(guò)程中的常見(jiàn)的異常和重定向問(wèn)題槐秧,讓我們看下它的核心代碼

Request request = chain.request();
//下一個(gè)待執(zhí)行的攔截器鏈條
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Transmitter transmitter = realChain.transmitter();

int followUpCount = 0;
Response priorResponse = null;
//這里開(kāi)啟了一個(gè)死循環(huán)啄踊,也是從這里不斷的層層執(zhí)行責(zé)任鏈條上的每一個(gè)
//攔截器
while (true) {
    //檢查連接的準(zhǔn)備狀態(tài)寸潦,如果是重復(fù)請(qǐng)求或正在請(qǐng)求則終止連接
    transmitter.prepareToConnect(request);
    
    //省略部分代碼...

    Response response;
    boolean success = false;
    try {
        //執(zhí)行下一個(gè)攔截器
        response = realChain.proceed(request, transmitter, null);
        success = true;
    } catch (RouteException e) {
        //當(dāng)請(qǐng)求發(fā)生異常時(shí)會(huì)嘗試重新連接.
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
            throw e.getFirstConnectException();
        }
        continue;
    } catch (IOException e) {
        // //當(dāng)請(qǐng)求發(fā)生異常時(shí)會(huì)嘗試重新連接.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, transmitter, requestSendStarted, request)) throw e;
        continue;
    } finally {
        // The network call threw an exception. Release any resources.
        if (!success) {
            //重試失敗,則釋放所有資源
            transmitter.exchangeDoneDueToException();
        }
    }
    
    //省略若干代碼...
    
    Exchange exchange = Internal.instance.exchange(response);
    Route route = exchange != null ? exchange.connection().route() : null;
    //如果沒(méi)有發(fā)生異常則對(duì)響應(yīng)的 code 進(jìn)行判斷社痛,如果是 30X 則構(gòu)建新的
    //request 進(jìn)行重定向操作见转,感興趣的可以看下源碼,不是很復(fù)雜
    Request followUp = followUpRequest(response, route);
    
    //省略若干代碼...
}
 

代碼中的注釋解釋的很清晰 蒜哀,這里在著重說(shuō)一下 recover 這個(gè)方法斩箫,也是判斷是否可以重試的重要條件

 private boolean recover(IOException e, Transmitter transmitter,
                        boolean requestSendStarted, Request userRequest) {
    // client 設(shè)置為禁止重試
    if (!client.retryOnConnectionFailure()) return false;

    // 請(qǐng)求的 body 已經(jīng)發(fā)出,則不在進(jìn)行請(qǐng)求
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;

    // 致命的異常撵儿,比如 ProtocolException乘客、SSLHandshakeException 等
    if (!isRecoverable(e, requestSendStarted)) return false;

    // 沒(méi)有更多的 Route 嘗試
    if (!transmitter.canRetry()) return false;

    // For failure recovery, use the same route selector with a new connection.
    return true;
}

可以看到重試的邏輯共有四個(gè)規(guī)則,如果都未通過(guò)那么重試就算失敗了淀歇,并且會(huì)在 finally 中釋放所有資源

2.2易核、BridgeInterceptor

這個(gè)攔截器的內(nèi)部邏輯其實(shí)很簡(jiǎn)單,它的作用就是如它的名字一般浪默,負(fù)責(zé)建立一個(gè)請(qǐng)求和響應(yīng)的橋梁牡直, 比如在請(qǐng)求的設(shè)置各種常用的 header 例如 Content-Type 、cookie纳决、User-Agent碰逸、 Gzip 壓縮等等,獲取響應(yīng)時(shí)配置響應(yīng)頭以及 Gzip 的解壓縮操作阔加,總體就是在請(qǐng)求之前和之后做的各種配置和轉(zhuǎn)換操作饵史,代碼很簡(jiǎn)單就不貼了

2.3、CacheInterceptor

OkHttp 的緩存攔截器的邏輯其實(shí)也是萬(wàn)變不離其宗胜榔,總體的思路并沒(méi)有和其它的框架有什么大的不同胳喷,也就是先從緩存中拿,緩存沒(méi)有在從網(wǎng)絡(luò)中拿夭织,只是 OkHttp 內(nèi)部對(duì)緩存的各種邏輯做了很完善的處理吭露。 但這里要注意的是 OKHttp 默認(rèn)是不支持緩存的,默認(rèn)提供的緩存功能為 Cache 類(lèi)摔癣,如果需要緩存則需要在創(chuàng)建 OkHttpClient 的時(shí)候手動(dòng)設(shè)置奴饮。

這里稍微提一下這個(gè)OkHttp提供的 Cache 類(lèi),我們知道凡是涉及到緩存功能的無(wú)非就是在設(shè)置的存儲(chǔ)范圍內(nèi)進(jìn)行增刪改查择浊,OkHttp 在這點(diǎn)上與其它的緩存策略并未有什么不同戴卜,不信我們看下 Cache 類(lèi)的大致結(jié)構(gòu)


在這里插入圖片描述

可以看到幾個(gè)很關(guān)鍵的方法 get、put琢岩、remove投剥、update、initialize担孔,前四個(gè)不多說(shuō)了 initialize 這個(gè)方法主要是做緩存之前的初始化操作江锨,比如文件創(chuàng)建吃警,刪除以及異常的處理,那么這些增刪改查的主要操作其實(shí)都是通過(guò) DiskLruCache 類(lèi)完成啄育,也可以說(shuō)整個(gè) Cache 類(lèi)其實(shí)就是對(duì)于 DiskLruCahe 操作的封裝酌心,而 DiskLruCache 則是對(duì) okio包下的 BufferSource 和 BufferedSink 做了封裝簡(jiǎn)化對(duì)文件的讀寫(xiě)操作√敉悖總體流程就是這些安券,具體的細(xì)節(jié)感興趣的讀者可以根據(jù)這幾個(gè)關(guān)鍵方法來(lái)深入了解 OkHttp 的 Cache 邏輯,這里就不贅述了氓英。

啰嗦了這么多下面我們進(jìn)入正題

@Override 
public Response intercept(Chain chain) throws IOException {
    //判斷是否配置了緩存侯勉,如果有則獲取緩存
    Response cacheCandidate = cache != null? cache.get(chain.request()) : null;

    long now = System.currentTimeMillis();
    //獲取緩存的策略,策略的判斷條件為 當(dāng)前時(shí)間铝阐,request 和 response
    //其實(shí)主要就是解析 response 和 request 各種 header 信息以此來(lái)獲取緩存策略
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;//網(wǎng)絡(luò)請(qǐng)求
    Response cacheResponse = strategy.cacheResponse;//緩存響應(yīng)

    if (cache != null) { //如果緩存不為空則直接采用上面的策略
      cache.trackResponse(strategy);
    }
    
    //省略部分代碼...

    //網(wǎng)絡(luò)請(qǐng)求和緩存都為空返回構(gòu)建的 504 response
    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();
    }

     // 上面的條件為 false 且網(wǎng)絡(luò)請(qǐng)求為 null 直接返回緩存
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
        //緩存沒(méi)有數(shù)據(jù)址貌,則執(zhí)行下一個(gè)責(zé)任鏈
      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());
      }
    }

    // 當(dāng)緩存不為空,但網(wǎng)絡(luò)返回的 response code = 304 
    // 直接返回緩存的 response 
    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());
      }
    }

    //構(gòu)建網(wǎng)絡(luò)請(qǐng)求的 response 
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    //如果緩存對(duì)象不為空則說(shuō)明 OkHttpClient 創(chuàng)建的時(shí)候
    //配置了緩存對(duì)象徘键,那么對(duì)于請(qǐng)求的 response 進(jìn)行緩存
    if (cache != null) {
        //判斷是否可以緩存,主要是判斷各種 code 是否滿(mǎn)足緩存條件等等
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        //寫(xiě)入磁盤(pán)
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
      //判斷請(qǐng)求方法是否為 GET 练对,非 GET 請(qǐng)求緩存無(wú)效并刪除
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

通過(guò)對(duì)代碼的逐行分析,我們發(fā)現(xiàn) OkHttp 在緩存的處理上是非常完善的啊鸭,基本覆蓋了常見(jiàn)的緩存問(wèn)題锹淌,整體的邏輯也不是那么復(fù)雜,但到這里你會(huì)發(fā)現(xiàn)我們依然在應(yīng)用層轉(zhuǎn)悠赠制,具體怎么進(jìn)行網(wǎng)絡(luò)請(qǐng)求的,我們依舊是一無(wú)所知挟憔,所以接下來(lái)我們來(lái)看看剩下的兩個(gè)攔截器是怎么完成這個(gè)過(guò)程的钟些。

2.4、ConnectInterceptor

ConnectInterceptor 可以說(shuō)是整個(gè) OkHttp 核心中的核心了绊谭,眾所周知當(dāng)我們通過(guò)一個(gè) url 發(fā)起一個(gè)請(qǐng)求時(shí)會(huì)先經(jīng)過(guò) DNS 進(jìn)行域名解析獲取實(shí)際的 IP 地址政恍,然后根據(jù) IP 地址和目標(biāo)服務(wù)器建立連接,在經(jīng)過(guò)三次握手后連接成功达传,我們就可以發(fā)送或接收消息了篙耗。 而 ConnectInterceptor 做的就是這些事,它將整個(gè)連接過(guò)程高度封裝宪赶,而且對(duì)連接做了復(fù)用處理宗弯,request 與 response 更是全部使用 okio 包下的 I/O 流進(jìn)行讀寫(xiě)操作,大大提升了請(qǐng)求效率搂妻。 所以廢話(huà)不多說(shuō)蒙保,上代碼~

@Override 
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    Transmitter transmitter = realChain.transmitter();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //這里開(kāi)始建立 Socket 連接,而且如果是非 GET 請(qǐng)求則要做更多的安全檢查
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
    
    //執(zhí)行下一個(gè)攔截器
    return realChain.proceed(request, transmitter, exchange);
  }

可以看到 ConnectInterceptor 的代碼并不多欲主,這里我們主要關(guān)注的就是 Transmitter 對(duì)象中的 newExchange 方法

/** Returns a new exchange to carry a new request and response. */
  Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    synchronized (connectionPool) {
      //出現(xiàn)了異常連接被釋放了
      if (noMoreExchanges) {
        throw new IllegalStateException("released");
      }
      //有別的請(qǐng)求未關(guān)閉則無(wú)法創(chuàng)建新的請(qǐng)求
      if (exchange != null) {
        throw new IllegalStateException("cannot make a new request because the previous response "
            + "is still open: please call response.close()");
      }
    }

    //建立連接
    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);

    synchronized (connectionPool) {
      this.exchange = result;
      this.exchangeRequestDone = false;
      this.exchangeResponseDone = false;
      return result;
    }
  }

代碼不多但很關(guān)鍵邓厕,首先 ExchangeCodec 是一個(gè)接口逝嚎,它的實(shí)現(xiàn)類(lèi)有兩個(gè),一個(gè)是Http1ExchangeCodec详恼,一個(gè)是Http2Exchangecodec,相信從這里你已經(jīng)明白了补君,沒(méi)錯(cuò),一個(gè)對(duì)應(yīng)的是 Http1 協(xié)議的連接昧互,一個(gè)則是 Http2 協(xié)議挽铁。而不同的 HttpCodec 代表不同的連接邏輯,雖然它倆的連接邏輯不同硅堆,但其實(shí)干的事情都一樣屿储,就是對(duì) Socket I/O 操作做了封裝,然后就是 ExchangeFinder 對(duì)象, 這個(gè)對(duì)象內(nèi)部實(shí)現(xiàn)了連接的復(fù)用機(jī)制渐逃,包括連接的重試等等够掠,不過(guò)到這里你可能會(huì)有點(diǎn)疑惑好像沒(méi)看到在哪創(chuàng)建這個(gè)類(lèi)的實(shí)例啊,提示一下茄菊,到 RetryAndFollowUpInterceptor 中找到 prepareToConnect 方法進(jìn)入后你就明白了疯潭。至于 Exchange 則是對(duì)連接的包裝類(lèi)

了解了這些類(lèi)的基本作用后那么 find 方法的做的事情我們也就基本清楚了,就是根據(jù)我們的請(qǐng)求建立 http1/http2 的的連接并返回對(duì)應(yīng)的 ExchangeCodec 對(duì)象面殖,我們進(jìn)去看看

final class ExchangeFinder {

    public ExchangeCodec find(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
        //省略若干代碼...
        
       RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
       return resultConnection.newCodec(client, chain);
        
        //省略若干代碼...
        
    }
}

可以看到內(nèi)部調(diào)用 findHealthyConnection 方法竖哩,并返回一個(gè) RealConnection 對(duì)象,并通過(guò) RealConnection 對(duì)象的 newCodec 方法創(chuàng)建對(duì)應(yīng)協(xié)議的 ExchangeCodec 對(duì)象脊僚。

這里著重說(shuō)一下 RealConnection 這個(gè)對(duì)象相叁,顧名思義這個(gè)對(duì)象是真正處理 Socket 連接的對(duì)象, RealConnection 處理的事情非常之多辽幌,包含了 socket 連接的所有細(xì)節(jié)例如 TCP + TLS 握手增淹、Http/http2 連接的處理等等,并通過(guò) RealConnectionPool 實(shí)現(xiàn)了連接的復(fù)用邏輯乌企,RealConnectionPool 內(nèi)部維護(hù)著一個(gè)線(xiàn)程池和一個(gè) RealConnection 的隊(duì)列虑润,這個(gè)線(xiàn)程池會(huì)在后臺(tái)一直運(yùn)行,負(fù)責(zé)清理過(guò)期的連接加酵、執(zhí)行新的連接或已有的連接拳喻。

繼續(xù)跟進(jìn) findHealthyConnection 方法

final class ExchangeFinder {
     /**
   * 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) {
          RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
              pingIntervalMillis, connectionRetryEnabled);
    
          //省略若干代碼...
    
          return candidate;
        }
  }
}

注釋我故意沒(méi)有刪,因?yàn)樗呀?jīng)表明了這個(gè)方法的作用猪腕,注釋的意思是說(shuō)冗澈,找到一個(gè)安全的連接,如果不安全就一直在循環(huán)中找码撰,直到找到為止渗柿,不過(guò)說(shuō)雖然是這么說(shuō),當(dāng)然不可能會(huì)無(wú)限找的,別忘了我們是有連接時(shí)超時(shí)限制的朵栖。

繼續(xù)跟進(jìn) findConnection 方法

final class ExchangeFinder {

  /**
   * 返回一個(gè)連接的流颊亮,如果這個(gè)連接在連接池中存在則返回,否則創(chuàng)建
   * 一個(gè)新的連接返回
   */
  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
      
    RealConnection result = null;
    Route selectedRoute = null;
    RealConnection releasedConnection;
    Socket toClose;
   
    synchronized (connectionPool) {

      //如果已經(jīng)建立了連接陨溅,但中間卻出現(xiàn)了異常導(dǎo)致連接出現(xiàn)問(wèn)題
      //那么釋放連接或創(chuàng)建新的連接
      releasedConnection = transmitter.connection;
      toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
          ? transmitter.releaseConnectionNoEvents()
          : null;

      if (transmitter.connection != null) {
        //如果已有連接那么直接使用
        result = transmitter.connection;
        releasedConnection = null;
      }

     //沒(méi)有可用的連接
      if (result == null) {
        //嘗試從連接池中獲取連接進(jìn)行復(fù)用
        if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
          foundPooledConnection = true;
          result = transmitter.connection;
        } else if (nextRouteToTry != null) {
         //如果連接池中也沒(méi)有那么用其它的 route 重試
          selectedRoute = nextRouteToTry;
          nextRouteToTry = null;
        } else if (retryCurrentRoute()) {
        //如果設(shè)置了重試當(dāng)前 route 那么根據(jù)重試的 route 連接
          selectedRoute = transmitter.connection.route();
        }
      }
    }


    if (result != null) {
      //如果連接池有可用的連接终惑,直接使用
      return result;
    }

    // 如果上面的條件都沒(méi)有獲取到連接,那么說(shuō)明我們要?jiǎng)?chuàng)建一個(gè)新的連接
    //并通過(guò) DNS 解析獲取要連接的 route
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      newRouteSelection = true;
      routeSelection = routeSelector.next();
    }

    List<Route> routes = null;
    synchronized (connectionPool) {
      if (transmitter.isCanceled()) throw new IOException("Canceled");

      if (newRouteSelection) {
        // 如果有新的 route 那么獲取所有 route 
        routes = routeSelection.getAll();
        //在此查看連接池中是否有可用的連接如果有直接使用
        if (connectionPool.transmitterAcquirePooledConnection(
            address, transmitter, routes, false)) {
          foundPooledConnection = true;
          result = transmitter.connection;
        }
      }
     //連接池中沒(méi)有可用連接门扇,且沒(méi)有可選擇的 route 
     //通過(guò) next 方法用 DNS 解析出新的 IP 地址用于接下來(lái)的連接
      if (!foundPooledConnection) {
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }

        // 通過(guò)上面獲取的 route 創(chuàng)建一個(gè)新的 RealConnection 對(duì)象
        result = new RealConnection(connectionPool, selectedRoute);
        connectingConnection = result;
      }
    }

   
    // 做 TCP+TLS 握手雹有,注意這是一個(gè)阻塞操作
    //到這一步執(zhí)行完整個(gè)連接算是真正的建立了
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    connectionPool.routeDatabase.connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      connectingConnection = null;
      // 這里會(huì)再次進(jìn)行一次判斷,查看連接池中是否有可用的連接
      if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
        //如果有那么關(guān)閉連接池中的連接臼寄,返回我們新創(chuàng)建的連接
        result.noNewExchanges = true;
        socket = result.socket();
        result = transmitter.connection;

        //將上面獲取的新的 route 標(biāo)記位下次重試的 route
        nextRouteToTry = selectedRoute;
      } else {
        //將新創(chuàng)建的連接放入連接池方便復(fù)用
        connectionPool.put(result);
        transmitter.acquireConnectionNoEvents(result);
      }
    }
    return result;
  }
}

上面的代碼就是 OkHttp 建立連接的整個(gè)過(guò)程霸奕,說(shuō)實(shí)話(huà)整體的代碼是有點(diǎn)復(fù)雜的,而且各種層級(jí)關(guān)系還不少吉拳,如果不耐心閱讀很容易跟錯(cuò)质帅,這里我也是讀了很久才逐漸縷清楚整個(gè)連接過(guò)程。所以讓我們做個(gè)復(fù)盤(pán)總結(jié)一下留攒,首先 ConnectInterceptor 的主要作用就是建立 socket 連接并將其包裝成 Exchange 對(duì)象供下一個(gè)攔截器使用煤惩, 而 Exchange 其實(shí)就是 Http1ExchangeCodecHttp2ExchangeCodec 的包裝類(lèi),負(fù)責(zé)對(duì) request 和 response 的 header body 進(jìn)行流的讀寫(xiě)操作炼邀。建立連接的整個(gè)過(guò)程如下

  • 調(diào)用 transmitter的.newExchange 方法獲取 Exchange 對(duì)象
  • newExchange 方法調(diào)用 ExchangeFinder.find 方法獲取 ExchangeCodec 對(duì)象
  • find 方法 調(diào)用 ExchangeFinder 的 findHealthyConnection 方法獲取 RealConnection 對(duì)象并調(diào)用該對(duì)象的 newCodeC 方法創(chuàng)建一個(gè) ExchangeCodec 對(duì)象返回
  • findHealthyConnection 方法再調(diào)用 ExchangeFinder 的 findConnection 方法獲取 RealConnection 對(duì)象
  • findConnection 方法中通過(guò) DNS 解析 IP地址魄揉,創(chuàng)建 RealConnection 對(duì)象并建立連接后返回 RealConnection 實(shí)例,同時(shí)實(shí)現(xiàn)了連接的復(fù)用邏輯

經(jīng)過(guò)上面的一系列調(diào)用 ConnectInterceptor 就完成了它的光榮使命拭宁,這里在提一個(gè)細(xì)節(jié)洛退,讓我們回顧一下以往的代碼

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors())
   }
   //省略...
}

其實(shí)剛開(kāi)始讀到這里的時(shí)候我是有點(diǎn)疑惑的,interceptors 和 networkInterceptors 有什么區(qū)別杰标? 而經(jīng)過(guò)了上面的分析相信你會(huì)恍然大悟不狮,很簡(jiǎn)單, interceptors 是連接之前的處理,而 ConnectInterceptor 執(zhí)行完之后網(wǎng)絡(luò)連接已經(jīng)建立了在旱,那么后續(xù)的攔截只能是對(duì)連接后的過(guò)程進(jìn)行處理,所以才會(huì)叫 networkInterceptors

三推掸、結(jié)語(yǔ)

注釋里基本對(duì)每個(gè)方法都做了詳細(xì)說(shuō)明桶蝎,相信到這里我們腦海中對(duì) OkHttp 的連接流程的總體脈絡(luò)應(yīng)該有了一個(gè)較為清晰的認(rèn)識(shí),但還沒(méi)完谅畅,Socket 在哪連接的登渣, DNS 解析好像也沒(méi)看到, ExchangeCodec實(shí)例對(duì)象是怎么創(chuàng)建的毡泻?這些在上述的代碼中我們都沒(méi)看到胜茧,所以下面我還會(huì)挨個(gè)對(duì)上述的問(wèn)題一個(gè)一個(gè)解密,限于篇幅,想繼續(xù)了解可以閱讀 OkHttp 源碼深入分析(二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末呻顽,一起剝皮案震驚了整個(gè)濱河市雹顺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌廊遍,老刑警劉巖嬉愧,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異喉前,居然都是意外死亡没酣,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)卵迂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)裕便,“玉大人,你說(shuō)我怎么就攤上這事见咒〕ニィ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵论颅,是天一觀的道長(zhǎng)哎垦。 經(jīng)常有香客問(wèn)我,道長(zhǎng)恃疯,這世上最難降的妖魔是什么漏设? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮今妄,結(jié)果婚禮上郑口,老公的妹妹穿的比我還像新娘。我一直安慰自己盾鳞,他們只是感情好犬性,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著腾仅,像睡著了一般乒裆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上推励,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天鹤耍,我揣著相機(jī)與錄音,去河邊找鬼验辞。 笑死稿黄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的跌造。 我是一名探鬼主播杆怕,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了陵珍?” 一聲冷哼從身側(cè)響起寝杖,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎撑教,沒(méi)想到半個(gè)月后朝墩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伟姐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年收苏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愤兵。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡杈绸,死狀恐怖几苍,靈堂內(nèi)的尸體忽然破棺而出弊知,到底是詐尸還是另有隱情纪铺,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布屹堰,位于F島的核電站肛冶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏扯键。R本人自食惡果不足惜睦袖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荣刑。 院中可真熱鬧馅笙,春花似錦、人聲如沸厉亏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)爱只。三九已至皿淋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恬试,已是汗流浹背沥匈。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留忘渔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓缰儿,卻偏偏與公主長(zhǎng)得像畦粮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351