1. 程式人生 > >OkHttp深入分析——原始碼分析部分

OkHttp深入分析——原始碼分析部分

一、前言

《OkHttp深入分析——基礎認知部分》對 OkHttp 原始碼的工程已經有一個大致的瞭解了,這篇文章裡就來分析下核心模組 OkHttp。首先看看程式碼結構的框架圖。

OkHttp.jpg

框架圖中可以看到涉及到的東西非常多,也看到了很多協議相關的東西。前面的文章中有分析過 Volley——《Volley還算比較深入的分析》,也分析過 Android 原生的網路框架——《Android原生網路庫HttpURLConnection分析——HTTP部分》《Android原生網路庫HttpURLConnection分析——HTTPS部分》。通過將這 3 者對比不難發現,OkHttp 其實是一個低級別的網路框架,其和 Android 原生網路庫 HttpURLConnection 是一個級別的,屬於 Http 協議層。它是完全取代了系統的原生 Http 協議的實現,同時還加上了高度的抽象與封裝。而 Volley 只是一個更高度的抽象與封裝,其最大的目的在於使我們使用 Http 更簡單,對 Http 的通訊其實是沒有太大實質性的改進的。當然,你可以指定網路庫為 OkHttp。

二、原始碼分析

原始碼分析以發起一個非同步的 Get 請求為主線進行分析,Post 請求以及同步方式請求就忽略了。掌握知識抓主幹,其他的遇到問題再來解決問題。

1.Demo

如下的 Demo 是從工程原始碼 sample 中的 AsynchronousGet 中提出來的。

// 1. 構建 OkHttpClient
OkHttpClient client = new OkHttpClient();
// 2.構建一個 Request
Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt"
) .build(); // 3.使用 client 插入一個非同步請求,在 Callback 中等待返回結果 client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { // 4.請求成功後,返回結果會被封裝在 Response 中 try (ResponseBody responseBody = response.body()) { if
(!response.isSuccessful()) throw new IOException("Unexpected code " + response); Headers responseHeaders = response.headers(); for (int i = 0, size = responseHeaders.size(); i < size; i++) { System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i)); } System.out.println(responseBody.string()); } } }); 複製程式碼

Demo 中的程式碼很簡單,總共分了 4 個步驟,這也是分析 OkHttp 的主線。

2.構建 OkHttpClient

構建 OkHttpClient 的分析是非常重要的,它代表了框架的上下文,為網路請求的執行初始化了一切的環境。先來看一下它的類圖。

OkHttpClient.jpg

屬性非常多,密密麻麻的。關於每個屬性的意思,放在下面的 Builder 中一一講述。那再來看一下構造方法。

  public OkHttpClient() {
    this(new Builder());
  }
複製程式碼

預設構建方法 new 了一個預設的 Builder,也就是一切規則都採用預設的。

......
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
......
public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      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;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }
複製程式碼

把它們用表格整理下再來看看,好像也還數得清的,共 25 個。

序號 欄位名 說明
1 dispatcher 任務分發器
2 protocols 支援的協議集,預設為 Http2 和 Http 1.1
3 connectionSpecs 主要是針對 https 的 socket 連線的配置項,包括在協商安全連線時使用的TLS版本和密碼套件。
4 eventListenerFactory 建立 EventListener 的工廠方法類,重點在於 EventListener,通過 EventListener 我們可以得到更多有關 Http 通訊的可度量資料 ,如數量、大小和持續時間,以便於我們更加詳細的統計網路的狀況。
5 proxySelector 代理選擇器,預設一般用系統的DefaultProxySelector。
6 cookieJar 定義瞭如何儲存或者讀取 cookies,如果不設定則不儲存 cookie。
7 Cache 快取 Response 結果,如果不設定則為 null
8 InternalCache 內部快取
9 socketFactory SocketFactory,主要就是定義如何建立 socket,預設是 DefaultSocketFactory。
10 hostnameVerifier HostnameVerifier 介面的實現,與證書校驗相關。在握手期間,如果通訊 URL 的主機名和伺服器的標識主機名不匹配或者說不安全時,則底層的握手驗證機制會回撥 HostnameVerifier 介面的實現程式來確定是否應該允許此連線。
11 certificatePinner 證書鎖定,防止證書攻擊。典型的用例是防止代理工具抓包。
12 authenticator 授權相關,如著名的 401 返回碼。一般場景是在 token 過期的情況下發生,但在實際開發中,大部分伺服器不會這樣實現,而是正常返回在自定義碼裡面。
13 proxyAuthenticator 經過代理伺服器的授權相關
14 connectionPool 連線池
15 dns DNS,沒有設定的話則為系統的 DNS 列表
16 followSslRedirects 是否允許 SSL 重定向
17 followRedirects 允許重定向
18 retryOnConnectionFailure 允許失敗重連
19 callTimeout 排程超時時間
20 connectTimeout 連線超時時間
21 readTimeout 讀取超時時間
22 writeTimeout 寫入超時時間
23 pingInterval ping 的間隔時間
24 interceptors 攔截器
25 networkInterceptors 網路攔截器

這裡挑出幾個重要的屬性作進一步的展開,其他沒有展開的看看錶格里的說明即可。

2.1 分發排程器 Dispatcher

先來看一看它的類圖。

Dispatcher.jpg

  • executorService: 執行緒池排程器,可以由外部指定。如果沒有指定,則有預設值。預設執行緒池的指定核心執行緒數為 0,但最大執行緒數為 Integer.MAX_VALUE,阻塞佇列是一個無容量的阻塞佇列,以及超出核心執行緒數的執行緒的存活時間為 60 s。其特點是如果執行緒數量大於核心執行緒數,但小於等於最大執行緒數,且阻塞佇列是 SynchronousQueue 的時候,執行緒池會建立新執行緒來執行任務,因為 SynchronousQueue 是沒有容量的。這些執行緒屬於非核心執行緒,在任務完成後,閒置時間達到了超時時間就會被清除。執行緒同時是由一個執行緒工廠建立的,創建出來的執行緒名字為 "OkHttp Dispatcher"。

  • readyAsyncCalls,runningAsyncCalls,runningSyncCalls: 分別是非同步等待佇列,非同步執行時佇列以及同步執行時佇列。它們的型別都是 ArrayDeque,該類是雙端佇列的實現類。

  • idleCallback: 也就是當 Dispatcher 中沒有任何任務執行時,也就是進入了 idle 狀態了,所執行的 runnable 型別的 Callback。

  • maxRequests 和 maxRequestsPerHost: maxRequests 也就是最大請求數,預設為 64。而 maxRequestsPerHost 指的是以 url 中的域名來判斷的最大的 host 的數量,預設為 5。

Dispatcher 就先了解到這裡了,後面在分析非同步請求的過程中還會講述它是如何進行排程的。

2.2 快取 Cache

快取 Cache 就是將 Response 結果儲存在磁碟上,並且配合 Http 協議的 Cache-controller 等規則對快取結果進行增刪改查。也就是過期了就去請求網路結果並更更新快取或者刪除快取;沒過期則就用快取中的結果而不用去請求網路結果,從而節省時間以及寬頻。

2.3 連線池 ConnectionPool

主要是用來管理HTTP和HTTP/2連線的重用,以減少網路延遲。對於共享同一個 Address 的請求,其也會共享同一個連線。連線池裡的連線一直保持著連線,以待同一連線的其他請求來複用。

2.4 攔截器 interceptors 與 網路攔截器 networkInterceptors

這 2 個都是攔截器,我們也都可以新增自己的攔截器以達到我們自己的目的。其中 interceptors 也稱作 application interceptors,它主要發生在請求前和請求後這 2 個階段。而 network interceptors 則發生在請求中,即網路實際通訊的過程中。官方有一個圖來幫助我們理解它們之間的作用以及關係。

image.png

OkHttpClient 的構建就是一個網路通訊環境的初始化,細數下來其關鍵的幾個部分是任務分發器,快取,連線池,攔截器。這裡先對它們有一個基礎的認知,後面還會再和他們一一碰面的。

3.構建 Request

Request 沒有提供預設可用的構造方法給我們,必須用 Request.Builder 來進行構造。其類圖結構如下。

Request.jpg

Request 看起來就簡單多了。下面就幾個重要的屬性做一個簡要的說明。

3.1 HttpUrl

盜個圖吧,能說明一切的圖。

HttpUrl

3.2 Headers

Headers 就是存放 Http 協議的頭部引數,一般來說是 Key-values 形式。但 Headers 的實際實現不是用 Map 之類的結構,而是用了一個 String 陣列來儲存,並且規定了偶數為 key,奇數為 value。這樣的設計下明顯不管是時間上還是空間上都要優於 Map 類的結構。當然,Headers 不僅用於 Request,其還用於 Response。

3.3 RequestBody

RequestBody 其實是用於 Post 請求的,也就是請求的主體內容。 主體內容可以有許多種類,如檔案,表單以及Json字串等等。RequestBody 由兩部分組成,即 content 以及用於描述 content 型別的 MediaType。

一般情況下,通過 Charles 抓包可以觀察到,一個 Get 請求的樣子通常如下所示,這是請求 www.baidu.com 的根目錄 / 。

Request

4.提交非同步請求,等待返回結果

構建好了 OkHttpClient 就有了基礎環境,構建好了 Request 知道了我們要傳送什麼出去。接下來就是提交請求,等待處理。那麼提交給誰呢?接下來一步一步分析。

4.1 提交非同步 Get 請求

先來過一遍時序圖。

提交非同步Get請求.jpg

首先通過 OkHttpClient 的 newCall 方法建立一個 RealCall,RealCall 實現自介面 Call。它的類圖結構如下。

Call.jpg

newCall 方法比較簡單,它只是進一步通過 RealCall 的靜態方法 newRealCall 來實際建立一個 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 物件同時拿到 OkHttpClient,Request 以及 eventListener 3 個重要的引用,並記錄在其物件中。

然後下一步呼叫的就是 RealCall 的 enqueue() 方法,但注意這裡的引數是一個 Callback。看看它的實現。

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    // 通知連線開始
    eventListener.callStart(this);
    // 向 OkHttpClient 的 Dispatcher 插入一個 AsyncCall
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
複製程式碼

enqueue() 方法又進一步呼叫了 OkHttpClient 的屬性 Dispatcher 的 enqueue,並且還 new 了一個 AsyncCall 來作為引數入隊。看一看 AsyncCall 的類圖結構。

AsyncCall.jpg

AsyncCall 是一個 Runnable,這麼說來 AsyncCall 才是 Dispatcher 就實際排程的 Runnable。進一步看看 Dispatcher 的 enqueue 方法。

  void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);
    }
    promoteAndExecute();
  }
複製程式碼

先是將其新增到佇列 readyAsyncCalls 中去,然後呼叫 promoteAndExecute() 方法將 AsyncCall 從 readyAsyncCalls 移到 runningAsyncCalls 中去,並將 AsyncCall 交由 ExecutorService 來進行執行緒池排程。關於 Dispatcher 以及 ExecutorService 的特點已經在上面講過了。這裡就不再贅述了。而排程實際上是交給 AsyncCall 的 executeOn () 方法來發起的。

void executeOn(ExecutorService executorService) {
      ......
        executorService.execute(this);
        ......
    }
複製程式碼

向 ExecutorService 提交了 AsyncCall ,下一步就是等待 AsyncCall 被排程到,然後完成進一步的工作,構建 Interceptor Chain。

4.2 構建 Interceptor Chain

同樣是先過一下時序圖。

構建 Interceptor Chain.jpg

如果 AsyncCall 被排程到的話,那麼就會調起它的 run() 方法。而它的 run() 方法又會呼叫它的 execute() 方法。

@Override protected void execute() {
      .....
      Response response = getResponseWithInterceptorChain();
      ......
}
複製程式碼

這裡呼叫的是 RealCall 的 getResponseWithInterceptorChain() 方法,getResponseWithInterceptorChain() 方法執行完成後就得到了最終的結果 Response。

看到這裡插入一段話,這裡總結一下 RealCall 與 AsyncCall 的關係,還真是有一點微妙。通過 OkHttpClient 建立了 RealCall 的時候將 Request 已經給 RealCall 來持有了,然後向 RealCall 的 enqueue 入隊時其是一個 Callback。而內部其實又建立了一個 AsyncCall 用來給 OkHttpClient 的 Dispatcher 來進行排程。也就是說 RealCall 才是真正執行任務的,而 AsyncCall 是起到其中的排程作用。被排程起的 AsyncCall 又來驅動 RealCall 來執行任務。

然後再來看看 getResponseWithInterceptorChain() 的實現吧。

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    // 使用者新增的 interceptor,注意此時請求還未開始。
    interceptors.addAll(client.interceptors());
    // 新增框架內所必須的 ****Interceptor
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      // 非 web socket 的情況下,還會新增使用者所新增的 networkInterceptors。
      interceptors.addAll(client.networkInterceptors());
    }
    // 最後新增 CallServerInterceptor,這是 Interceptor Chain 的終止符。什麼意思呢?後面還會再講到。
    interceptors.add(new CallServerInterceptor(forWebSocket));
    // 構建一個起始 RealInterceptorChain
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    // 開始 Interceptor Chain 的執行以獲取 Response。
    return chain.proceed(originalRequest);
  }
複製程式碼

這段構造 Interceptor Chain 的程式碼看起來多,但其實是非常簡單的。就是將所有的 Interceptor 包括使用者定義的以及框架所必須的按照順序組織成一個列表,並結合 RealInterceptorChain 來構造出 Interceptor Chain。這個 Interceptor Chain 實現的確實很精妙,但說實話它又被很多人所神話了,好像其非常難且不可思議一樣。我們來進一步看看它的工作原理。

4.3 Interceptor Chain 工作原理

先來看看它的工作原理圖

Interceptor Chain 工作原理.jpg

從原理圖上可以看出,在 Chain 的 proceed() 方法中呼叫了 Interceptor 的 intercepter() 方法,而在 Interceptor 的 intercepter() 方法中又呼叫了 Chain 的 proceed() 方法。這是 2 個方法組成的遞迴呼叫,入口是 getResponseWithInterceptorChain() 中所 new 出來的 Chain,而出口是 CallServerInterceptor。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    calls++;
   ......
    // 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;
  }
複製程式碼

把該方法精簡單了一下,核心程式碼就上面那 3 句。先構造出 next Chain,注意其中的 index + 1,也就是說下一個 Chain 得呼叫下一個 Interceptor。這裡先獲取了當前 index 的 Interceptor 的 interceptor 方法。注意這裡傳遞了引數 next Chain,告訴 Interceptor 應該在執行完後就呼叫 Chain 的 proceed 繼續處理,直到遇到 CallServerInterceptor 不再呼叫 Chain 的 proceed 了,從而終結 Intercepter Chain 的執行。

Intercepter Chain 執行起來了,下面就該獲取其結果了。

5.獲取 Response

獲取 Response 的過程,就是 Intercepter Chain 的執行過程,而進一步精簡地說,就是 Interceptor 的執行。這裡假設沒有使用者所新增的 Interceptor,那接下來要分析的就是框架內的那 5 個重要的 Interceptor。也就是 getResponseWithInterceptorChain 中所新增的 RetryAndFollowUpInterceptor,BridgeInterceptor,CacheInterceptor,ConnectInterceptor 以及 CallServerInterceptor。而它們的執行順序就是它們的新增順序。

而在分析之前,先來看看這個要獲取的 Response 究竟是什麼?

5.1 關於 Response

Response.jpg

相對於 Request 來說,Response 顯然要內容上要多得多。code,protocol以及message 就是請求行。headers 與 Request 中的 headers 含義上是一樣的。handshake 主要是用於握手的。body 就是它的實際內容。

一般情況下,通過 Charles 抓包可獲取如下圖所示的結果。

Response.png

5.2 RetryAndFollowUpInterceptor

對於每一個 Interceptor 而言,只要從它的 intercept() 方法開始分析就可以了。

@Override public Response intercept(Chain chain) throws IOException {
    // 獲取 Request
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    // 獲取 RealCall
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    // 建立 StreamAllocation
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;
   // 記錄請求的次數,包括正常,重試,重定向以及認證的次數
    int followUpCount = 0;
    Response priorResponse = null;
    // 通過迴圈體來實現失敗後的重試或者重定向等
    while (true) {
      ......
      Response response;
      ......
        response = realChain.proceed(request, streamAllocation, null, null);
        ......
      Request followUp;
      try {
        followUp = followUpRequest(response, streamAllocation.route());
      } catch (IOException e) {
        streamAllocation.release();
        throw e;
      }

      if (followUp == null) {
        streamAllocation.release();
        return response;
      }
      // 達到次數上限了
      if (++followUpCount > MAX_FOLLOW_UPS) {
        ......
      }
     ......
      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(followUp.url()), call, eventListener, callStackTrace);
        this.streamAllocation = streamAllocation;
      } 
      request = followUp;
      priorResponse = response;
    }
  }
複製程式碼

用流程圖簡化一下上面的邏輯,如下所示。

RetryAndFollowUpInterceptor.jpg

從上面的實現來看,其主要是用了一個迴圈體來控制,如果請求沒有成功就會再次執行 Chain.proceed() 方法,以再次執行起 Interceptor Chain。而迴圈的次數由 MAX_FOLLOW_UPS 來決定,其預設大小是 20。

而是否成功的檢查都是通過 followUpRequest 來進行的。從上面程式碼也可以看到,如果它返回 null 則說明請求是成功的,否則肯定是出了其他“異常”。

private Request followUpRequest(Response userResponse, Route route) throws IOException {
    ......
    int responseCode = userResponse.code();
    final String method = userResponse.request().method();
    switch (responseCode) {
      case HTTP_PROXY_AUTH:
       ......
        return client.proxyAuthenticator().authenticate(route, userResponse);
      case HTTP_UNAUTHORIZED:
        return client.authenticator().authenticate(route, userResponse);
      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        .......
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        ......
        return requestBuilder.url(url).build();
      case HTTP_CLIENT_TIMEOUT:
        ......
        return userResponse.request();
      case HTTP_UNAVAILABLE:
        if (userResponse.priorResponse() != null
            && userResponse.priorResponse().code() == HTTP_UNAVAILABLE
          return null;
        }
        if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
          return userResponse.request();
        }
        return null;
      default:
        return null;
    }
  }
複製程式碼

主要就是通過 response code 進行的一系列異常行為的判斷,從而決定是否需要重新執行起 Interceptor Chain 從面達到重連或者修復網路的問題。

5.3 BridgeInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
    // 處理 body,如果有的話
    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }
      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }
    // 處理 host
    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;
    // 請求以 GZip 來壓縮
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
    // 處理 Cookies,如果有的話
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    // 處理 UA
    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }
    // 調 proceed() 等待 Response 返回
    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }
複製程式碼

同樣先用流程圖來總結一下。

BridgeInterceptor.jpg

BridgeInterceptor 如其名字,橋接器,用於橋接 request 和 reponse。主要就是依據 Http 協議配置請求頭,然後通過 chain.proceed() 發出請求。待結果返回後再構建響應結果。而關於上面程式碼各部分的細節,請參考 Http 協議相關文件以瞭解每個 Header 的意義。

5.4 CacheInterceptor

@Override public Response intercept(Chain chain) throws IOException {
        // 從 Cache 中取得 Reponse
        Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    // 通過CacheStrategy.Factory構造出CacheStrategy,從而判斷 Cache 是否可用
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
   .....
    // 命中 Cache
    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 (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
       // 儲存 Cache
        return cacheWritingResponse(cacheRequest, response);
      }
    }

    return response;
  }
複製程式碼

流程圖總結如下:

CacheInterceptor.jpg

上面的實現就是先看一看是否命中 cahce,如果命中的話就直接返回,如果沒有的話就進一步請求網路。而如果最終有獲取的結果,只要條件允許 ,就會將 response 寫入到 Cache 中。而是否命中 Cache 的實現都在 CacheStrategy 中。

5.5 ConnectInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
複製程式碼

其流程圖如下

ConnectInterceptor.jpg

其主要目的尋找一個可用的 Connection,並將獲取到可用的 httpCodec 以及 connection 推到下一個 Chain 中,以向伺服器發起真正的請求。這裡的 Connection 具體點其實指的就是 TCP,甚至包括了 TLS 在內的連線。並同時通過 Okio 包裝了寫入流 BufferSink 與讀取流 BufferSource。

關於 Okio,可以去參考《Okio深入分析——基礎使用部分》《Okio深入分析—原始碼分析部分》

5.6 CallServerInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    ......
    // 寫入請求頭
    httpCodec.writeRequestHeaders(request);
    ......
    HttpSink httpSink = null;
    Response.Builder responseBuilder = null;
    ......
    ......
          // 如果需要寫入 request body 的話,則寫入
          BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
          request.body().writeTo(bufferedRequestBody);
          .......
    ......
    // 完成請求的寫入
    httpCodec.finishRequest();
    .......
   // 獲取並構造 response
   responseBuilder = httpCodec.readResponseHeaders(false);
   ......
    responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis());
    Internal.instance.httpSink(responseBuilder, httpSink);
    Response response = responseBuilder.build();
   .......
   .......
    return response;
  }
複製程式碼

這是整個 Interceptor Chain 中最後一個 Intercepter,它利用在 ConnectInterceptor 建立起的連線以及獲取到的寫入流,將請求寫入到流中,從而實現向服務傳送請求呼叫。

關於這 5 個 Interceptor 都只是點到即止,並沒有進行進一步的詳細展開。原因是因為這 5 個 Interceptor 恰好是這個庫的精華所在,它們其實分別代表了 OkHttp 的基本特性:失敗重連,協議支援,快取,連線重用。每一個特性都該獨立成文,同時,從篇幅上來考慮,這裡只做大綱性的展開以及結論性的總結。並且,對這些細節不感興趣的同學看到這裡也基本瞭解了 OkHttp 的整體輪廓了。

三、總結

又到了總結的時候了。這篇文章前後也經歷不短的時間,可以說是跨年了。文章先梳理出了 OkHttp 的程式碼結構,以對庫有一個全域性的認知。然後以官方 Demo 程式碼 AsynchronousGet 的請求流程為關鍵路徑來分析請求的整個過程,從而從原始碼層面瞭解 OkHttp 的工作原理。

最後,感謝你能讀到並讀完此文章。受限於作者水平有限,如果分析的過程中存在錯誤或者疑問都歡迎留言討論。如果我的分享能夠幫助到你,也請記得幫忙點個贊吧,鼓勵我繼續寫下去,謝謝。