1. 程式人生 > >OkHttp3 攔截器源碼分析

OkHttp3 攔截器源碼分析

源碼分析 potential 而且 finally this fir network recover 添加用戶

OkHttp 內置攔截器

在這篇博客 OkHttp3 攔截器(Interceptor) ,我們已經介紹了攔截器的作用,攔截器是 OkHttp 提供的對 Http 請求和響應進行統一處理的強大機制,它可以實現網絡監聽、請求以及響應重寫、請求失敗充實等功能。
同時也了解了攔截器可以被鏈接起來使用,我們可以註冊自定義的攔截器(應用攔截器和網絡攔截器)到攔截器鏈上,如下圖:
技術分享圖片

實際上除了我們自定義的攔截器外,OkHttp 系統內部還提供了幾種其他的攔截器,就是上圖中 OkHttp core 的部分。OkHttp 內部的攔截器各自負責不同的功能,每一個功能就是一個 Interceptor,這些攔截器連接起來形成了一個攔截器鏈,最終也就完成了一次網絡請求。
具體如下圖:
技術分享圖片

在上一篇博客 OkHttp3 源碼分析 中,我們分析了 OkHttp 的同步和異步請求的流程源碼,發現無論是同步請求還是異步請求都是通過調用 RealCall 的 getResponseWithInterceptorChain() 方法來獲取 response 響應的。
RealCall. getResponseWithInterceptorChain()源碼:

Response getResponseWithInterceptorChain() throws IOException {
    List<Interceptor> interceptors = new ArrayList();
    //添加自定義的應用攔截器
    interceptors.addAll(this.client.interceptors());
    //負責重定向和失敗重試的攔截器
    interceptors.add(this.retryAndFollowUpInterceptor);
    //橋接網絡層和應用層,就是為用戶所創建的請求補充添加一些服務端還必需的 http 請求頭等
    interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
    //負責讀取緩存,更新緩存
    interceptors.add(new CacheInterceptor(this.client.internalCache()));
    //負責與服務端建立連接
    interceptors.add(new ConnectInterceptor(this.client));
    //配置自定義的網絡攔截器
    if (!this.forWebSocket) {
        interceptors.addAll(this.client.networkInterceptors());
    }
    //向服務端發送請求,從服務端讀取響應數據
    interceptors.add(new CallServerInterceptor(this.forWebSocket));
    //創建 攔截器鏈chain 對象,這裏將各種攔截器的 List 集合傳了進去
    Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
    //通過鏈式請求得到 response
    return chain.proceed(this.originalRequest);
}

在這個方法中,我們就發現了 OkHttp 內置的這幾種攔截器,這幾種攔截器的具體作用稍後再說,先來宏觀的分析一下 getResponseWithInterceptorChain() 做了些什麽工作:

  1. 創建了一系列的攔截器,並將其放入一個攔截器 List 集合中。
  2. 將攔截器的 List 集合傳入 RealInterceptorChain 的構造方法中,創建出一個攔截器鏈 RealInterceptorChain 。
  3. 執行攔截器鏈 chain 的 proceed() 方法來依次調用每個不同功能的攔截器,最終獲取響應。

那麽這個 Chain 對象到底是如何處理攔截器集合的呢,為什麽通過調用 chain.proceed 就能得到被攔截器鏈依次處理之後的 response 呢?
其實這個問題的答案就是責任鏈設計模式,建議先了解一下關於責任鏈模式的介紹,再回頭往下看。

在理解了責任鏈模式之後,我們就能比較容易的理解攔截器是如何工作的了。
首先來看一看 Interceptor 接口,很明顯的它就是責任鏈模式中的抽象處理者角色了,各種攔截器都需要實現它的 intercept 方法

/**
 * Observes, modifies, and potentially short-circuits requests going out and the corresponding
 * responses coming back in. Typically interceptors add, remove, or transform headers on the request
 * or response.
 */
public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    ...
  }
}

這裏我們註意到 Interceptor 還包含了一個內部接口 Chain,通過查看 Chain 接口,也可以大概了解它的功能:

  1. 通過 request() 方法來獲取 request 請求
  2. 通過 proceed(request) 方法來處理 request 請求,並返回 response 響應

剛剛也介紹了在 getResponseWithInterceptorChain() 方法中,正是由 Chain 來依次調用攔截器來獲取 response 的:

    //創建 攔截器鏈chain 對象,這裏將各種攔截器的 List 集合傳了進去
    Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
    //通過鏈式請求得到 response
    return chain.proceed(this.originalRequest);

那麽它具體是怎麽工作的呢?我們先來看一下RealInterceptorChain 的構造方法

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final RealConnection connection;
  private final int index;
  private final Request request;
  private final Call call;
  private final EventListener eventListener;
  private final int connectTimeout;
  private final int readTimeout;
  private final int writeTimeout;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
      EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
    this.call = call;
    this.eventListener = eventListener;
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
    this.writeTimeout = writeTimeout;
  }

需要特別註意的是這個構造方法裏的 index 參數,傳入給構造方法的 index 最終被賦值給了一個全局變量 index(這個變量很重要,之後會被使用到)。在構造出了 RealInterceptorChain 對象之後,接著就調用它的 proceed 方法來執行攔截器了。
來看一下 chain.proceed(request) 方法的具體實現:
RealInterceptorChain#proceed:

  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);
    //調用當前的攔截器的 intercept 方法獲取 response
    Response response = interceptor.intercept(next);

    ...

    return response;
  }

這個方法的關鍵邏輯在這與這三行代碼
一, 在 chain.proceed 的方法中,又 new 了一個 RealInterceptorChain,不過這裏傳入的參數是 index + 1,也就是說,每次調用 proceed 方法,都會產生出一個 index成員變量 +1的 RealInterceptorChain 對象。而且該 chain 對象的名字為 next,所以我們大致也能猜測一下它代表的是下一個 chain 對象。

    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);

二, 根據 index 索引值獲取當前攔截器,這個 index 就是之前創建 chain 構造函數時的 index 值
大家應該還記得在 getResponseWithInterceptorChain 第一次創建 Chain 對象時,index被初始化為0。

Interceptor interceptor = interceptors.get(index);

三, 調用當前攔截器的 intercept(Chain chain) 方法

Response response = interceptor.intercept(next);

這裏我們就以 index 為 0 為例,獲取 interceptors 集合中的第一個攔截器 RetryAndFollowUpInterceptor(假設沒有添加用戶自定義的應用攔截器),來看一下它的 intercept 方法:

  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        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(), streamAllocation, false, request)) {
          throw e.getFirstConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        // We‘re throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

    ...
    }
  }

這段代碼中最關鍵的地方是:

response = realChain.proceed(request, streamAllocation, null, null);

我們發現,原來在 intercept() 中又會調用 chain.proceed() 方法,而每次調用 proceed 方法中又會去獲取一個索引為 index + 1 的下一個攔截器,並執行該攔截器的 intercept() 方法,就是這樣相互的遞歸調用,實現了對攔截器的逐步調用。
這個過程流程圖如下:

技術分享圖片

到這裏也許我們會有一個疑問,那就是為什麽每次都需要創建一個新的 RealInterceptorChain 對象,只需要修改 index 變量的值不是也能實現同樣的效果嗎?這裏的原因是 RealInterceptorChain 對象中還包含了 request 請求信息在內的其他信息,而每次執行攔截器的 intercept 方法時,因為遞歸調用的緣故,本層 的 intercept 並沒有被執行完,如果復用 RealInterceptorChain 對象,則其他層次會對本層次 RealInterceptorChain 對象產生影響。

參考
https://blog.csdn.net/aiynmimi/article/details/79643123
https://blog.csdn.net/qq_16445551/article/details/79008433

OkHttp3 攔截器源碼分析