OkHttp3.9攔截器原理與區別

接上回傳送門

上回我們講到,OkHttp的請求過程中有個非常重要的東西-“攔截器”,而且攔截器又分爲interceptors和networkInterceptors兩種,那它們具體有何區別呢?又要怎麼來使用?現在來一探究竟

攔截器工作原理

在弄清楚區別之前,要先知道他們工作的原理,還是來到RealCall.execute方法裏面的getResponseWithInterceptorChain:

RealCall.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);
  }
}

其實可以看出來,這個方法主要就是添加各種interceptor,然後文章最開始提到的第一個interceptors其實是自定義的攔截器,添加到了最前面,然後依次添加了四種攔截器過後,第五個是networkInterceptors,最後一共加了七種攔截器。

攔截器責任鏈

在這之後他們是怎麼執行的?這就要說說Interceptor.Chain這個類了,其實從這個getResponseWithInterceptorChain方法名也可知一二,劃重點了!這裏用到了責任鏈模式,在請求的各個階段都有相應的攔截器來負責,這些攔截器組成了一個鏈。在這裏首先實例化成了RealInterceptorChain,來到這個類的構造方法來看看:

  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;
  }

這裏主要是初始化了各種屬性,這裏面的參數大多數和OkHttp的屬性無異,但需要注意的是這裏的interceptors不同於OkHttp構造裏面的那個interceptors,這裏是表示剛纔組成的所有攔截器的數組,包含interceptors和networkInterceptors在內的七種攔截器,然後index這個屬性初始化爲“0”。

接下來在getResponseWithInterceptorChain最後一句執行了它的proceed方法,在這個方法裏有很多拋出異常的語句,這裏不去深究,主要看看中間主要的一兩句:

RealInterceptorChain.proceed

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // 重點!!!主要就看看這幾句
    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);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

劃重點!在這裏就到了最核心的幾句了

首先:又實例化了一個RealInterceptorChain,取名爲next(從名字真的可以看出很多蹊蹺),構造參數沒變化,只是把index+1

然後:從interceptors數組中用index來依次取出Interceptor(每次執行index都會+1,所以是依次取出來);

最後:調用interceptor.intercept(next),把next傳入調用intercept方法

完;

這裏如果直接點進去intercept方法會發現,這個方法在接口中:

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

所以要去它的具體實現類去看看這個方法,從一開始的getResponseWithInterceptorChain方法可知,我們首先加入的攔截器是自定義的interceptors,第二個是retryAndFollowUpInterceptor,這裏我們對攔截器的具體作用按下不表,先看看責任鏈的執行,找到RetryAndFollowUpInterceptor的intercept方法,方法很長,只截取了關於chain操作的部分:

RetryAndFollowUpInterceptor.intercept(Chain chain)

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    
    response = realChain.proceed(request, streamAllocation, null, null);
}

這個方法裏面會對一些參數做修改,又調用了proceed方法,在proceed方法裏我們知道index又會+1,然後取出下一個攔截器並且傳入修改後的參數。這就完成了這個鏈式結構的運轉,一步一步的往下傳遞,然後到了最後又依次返回Response

各種攔截器

看到這裏,不禁會問,上面看到的那麼多種攔截器到底分別是用來幹啥的呢,在這裏來總結一下(來自網絡,只是做個歸納):

RetryAndFollowUpInterceptor

用來實現連接失敗的重試和重定向

BridgeInterceptor

用來修改請求和響應的 header 信息

CacheInterceptor

用來實現響應緩存。比如獲取到的 Response 帶有 Date,Expires,Last-Modified,Etag 等 header,表示該 Response 可以緩存一定的時間,下次請求就可以不需要發往服務端,直接拿緩存的

ConnectInterceptor

用來打開到服務端的連接。其實是調用了 StreamAllocation 的newStream 方法來打開連接的。建聯的 TCP 握手,TLS 握手都發生該階段。過了這個階段,和服務端的 socket 連接打通

CallServerInterceptor

用來發起請求並且得到響應。上一個階段已經握手成功,HttpStream 流已經打開,所以這個階段把 Request 的請求信息傳入流中,並且從流中讀取數據封裝成 Response 返回

上一張圖,看着直觀一點(話說這個其實看着跟Android的點擊事件分發機制很像啊)

Interceptor

最後來說說這個面試題

看到這裏,對OkHttp的攔截器機制也瞭解了個大概,但是開篇提到的那個問題的答案到底是什麼呢?

interceptors和networkInterceptors分別位於攔截器鏈的第一個和第六個,從每個攔截器的作用大致猜到一些。interceptors是肯定每次都會執行的,但是,在networkInterceptors之前有個ConnectInterceptor,這個攔截器的作用是用於建立跟服務器的連接。那如果我們現在設備離線,直接讀取緩存呢?對,那這樣的話networkInterceptors就不會執行了。

當然,這只是他們的一個最容易理解的區別,這個問題在OkHttp的官方文檔已經給了我們答案 OkHttp文檔中對於這兩個攔截器的解釋,下面來結合資料做個翻譯

OkHttp文檔圖片

Application interceptors

  • Don’t need to worry about intermediate responses like redirects and retries.

  • Are always invoked once, even if the HTTP response is served from the cache.

  • Observe the application’s original intent. Unconcerned with OkHttp-injected headers like If-None-Match.

  • Permitted to short-circuit and not call Chain.proceed().

  • Permitted to retry and make multiple calls to Chain.proceed().

  • 不需要擔心中間過程的響應,如重定向和重試

  • 只會被調用一次,即使這個響應是從緩存中獲取的

  • 只關注最原始的請求, 不關心OkHttp注入的頭信息如: If-None-Match

  • 可以中斷調用過程,有權決定是否要執行Chain.proceed()

  • 允許重試,可以多次調用Chain.proceed()


Network Interceptors

  • Able to operate on intermediate responses like redirects and retries.

  • Not invoked for cached responses that short-circuit the network.

  • Observe the data just as it will be transmitted over the network.

  • Access to the Connection that carries the request.

  • 能夠操作中間過程的響應,如重定向和重試.

  • 當返回緩存時不被調用.(也就是我們剛纔舉的例子)

  • 觀察在網絡上傳輸的數據變化,比如重定向

  • 攜帶請求來訪問連接(這裏已經建立了連接)

結題

看懂了攔截器其實也就是看懂了OkHttp,它的整個工作原理其實就是以攔截器責任鏈模式爲核心,這種模式之下,我們可以很方便的來定製我們自己攔截器,比如可以改變請求頭,處理緩存等等等等,不知道還有沒有什麼關於OkHttp的知識點呢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章