OkHttp源碼解析(三)Interceptor

        這篇文章,對okhttp的另一個非常重要的概念-攔截器(Interceptor)進行源碼分析。或許,有的朋友就要說了,前面兩篇文章分別總結了兩種請求的源碼以及Dispatcher的源碼,爲什麼突然扯到Interceptor了呢?接下來,我們先了解一下,攔截器是什麼。

一、Interceptor是什麼

        Interceptor翻譯過來就叫攔截器。引用官網的解釋:攔截器是okhttp的一種強大的機制。他可以實現網絡監聽、請求以及重寫響應、請求失敗重試等功能。

二、從同步請求說起

        我們前面的源碼分析,在講同步請求的時候,我們對response的獲取一筆帶過,並沒有深入去追究response是如何獲取到的。我們先把同步請求執行的相關源碼貼一下:

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      e = timeoutExit(e);
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }

        上面的代碼,我們在同步請求源碼解析中,大部分都做了解析。但是,有很重要的一行,我一筆帶過。也就是:

Response result = getResponseWithInterceptorChain();

        這個方法,乍一看很簡單,就是在發起請求後,獲取到請求的結果。但實際上真的那麼簡單嗎?當然不是。我們通過同步請求和異步請求的執行可以看出,請求的過程特別是異步請求還是比較麻煩的。因此。請求結果的獲取,也並不是一行代碼就輕鬆得到的,而是通過攔截器鏈一步一步執行獲得的。

三、攔截器

        首先,我們看一下okhttp的攔截器鏈:

        我們接着上面的方法,跟進去源碼。我們看到,代碼量不多,就十幾行代碼。但是,我們看到了這中間有很多個我們從來沒見到過的類,各種XXXInterceptor。這其實就是okhttp的攔截器鏈:

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

         簡單說一下這個方法,其實,如果我們忽視掉這些攔截器的功能,單純看代碼的話,還是比較好理解的。首先,我們往一個攔截器的List中添加了很多個攔截器。然後,我們通過這個攔截器的List等參數去創建了一個攔截器鏈,注意,這個地方傳入的index爲0,也就是第一個攔截器。最後,這個攔截器鏈調用proceed方法。可見,核心的代碼實現是在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");
    }

    // 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);

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

        我們看一下幾處核心代碼,首先是下面這一段。我們看到,跟我們在進入proceed方法前一樣,創建了一個攔截器鏈,但是這裏的index是index+1,也就是攔截器鏈中當前index的下一個攔截器,這其實就是攔截器鏈的實現。然後,獲取當前index的攔截器,去執行下一個攔截器,代碼如下:

// 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);

        我們看一下intercept方法的實現。Interceptor是一個接口,我們看一下實現類的方法實現。在上面我們說過,攔截器有很多個,他們是鏈式調用的。第一個攔截器,其實就是RetryAndFollowUpInterceptor,它的intercept方法具體實現在這裏,我們先不去關注具體實現,我們只看下面這麼一行代碼:

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

        是的,每個攔截器的intercept方法,其實又調用了proceed方法,而proceed方法,又去調用下一個攔截器的intercept方法。接下來,我們簡單看一下每一種攔截器的作用。

1、RetryAndFollowUpInterceptor

        這個中文名字,我們叫他重試和跟蹤攔截器。他的主要作用就是發起網絡請求,如果該請求需要重定向,那麼進行重定向請求,並且在請求失敗的情況下,發起重試。

(1)創建網絡請求

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

(2)重定向

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Request followUp;
      try {
        followUp = followUpRequest(response, streamAllocation.route());
      } catch (IOException e) {
        streamAllocation.release();
        throw e;
      }

      if (followUp == null) {
        streamAllocation.release();
        return response;
      }

(3)重試

        重試次數不是無限的,在這裏設置的是20次。 

 private static final int MAX_FOLLOW_UPS = 20;
     
 if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
 }

2、BridgeInterceptor        

        中文叫橋接攔截器,他的主要作用是添加頭部信息(例如User-Agent,,Host,Cookie等),使請求成爲一個標準的http請求併發起請求,並且處理返回給我們的response,使之轉化爲用戶可用的response。

(1)添加頭部信息

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

(2)處理response

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

 3、CacheInterceptor

        中文叫緩存攔截器。試想一下,是不是我們每次發起網絡請求都真正的去服務器請求呢?當然不是,Okhttp也有自己的緩存策略。當我們發起請求的時候,會去判斷是否有緩存,如果有緩存則直接從緩存中拿這個請求。如果沒有緩存,那麼去使用網絡發起請求。這些緩存功能的實現,就是緩存攔截器來做的。

4、ConnectInterceptor

       網絡連接攔截器,在經過上面一系列攔截器的處理後(是否重試和重定向,拼接頭部信息,是否使用緩存等),終於到了和服務器連接的時刻,網絡連接攔截器就是和服務器進行連接的攔截器。我們看一下源碼:

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

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

        網絡連接攔截器的工作流程如下:

        (1)獲取前面的攔截器傳過來的StreamAllocation

        (2)把StreamAllocation,httpCodec,RealConnection等等傳遞給後面的攔截器。

        HttpCodec是一個非常關鍵的類,看到Codec,我們能猜出它的作用,一般是編解碼相關的類。HttpCodec就是負責對http請求和響應進行編解碼的類。

        RealConnection也是一個非常關鍵的類,他是真正的網絡連接類,它維護了一個網絡連接池ConnectionPool。

5、CallServerInterceptor

        訪問服務端攔截器。這個攔截器從名字可以看出,它是真正向服務端發起請求的攔截器。這裏面都是和網絡請求相關的一些方法和特殊情況的處理,最後會返回我們所需要的response。

        最後,簡單總結一下,okhttp有多個攔截器,這些攔截器一起構成了攔截器鏈,每一個鏈接器在執行自己功能的同時,會調用下一個攔截器,同時對response進行處理,返回上一個攔截器,這樣完成了攔截器的鏈式調用。

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