OkHttp源碼解析(二)- Interceptors 攔截器鏈工作流程

OkHttp的配置、使用步驟這裏就不展開描述了,網絡上有很多優秀的文章,這裏主要是對學習源碼中理解到的知識進行概括總結。

該文章根據OkHttp-3.11.0版本進行分析,並且強烈建議自己跟着源碼配合文章的思路一起閱讀

攔截器鏈是如何在OkHttp請求過程中被調用的請看我的另一篇文章:
OkHttp源碼解析(一)- Dispatcher

  • 攔截器鏈

    意義:採用責任鏈模式,鏈中的每一個攔截器只做自己所負責的事情,將對Request的處理結果交給下一個攔截器,從最後一個攔截器開始,將返回結果依次返回給上一個攔截器,最終返回完整的Response結果。

    攔截器類型

    1. 用戶自定義攔截器
    2. RetryAndFollowUpInterceptor:處理重試、重定向機制的攔截器。
    3. BridgeInterceptor:處理流大小、壓縮、編碼的攔截器。
    4. CacheInterceptor:處理緩存的攔截器。
    5. ConnectInterceptor:處理連接的攔截器。
    6. NetworkInterceptors:用戶自定義的處理網絡請求與響應的攔截器。
    7. CallServerInterceptor:處理與服務器交互細節的攔截器。

    攔截器定義的源碼如下:

// Build a full stack of interceptors.
List<Interceptorinterceptors = 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));

我們用流程圖的形式來描述攔截器鏈的工作過程:
Interceptors工作流程
從我的另一篇文章可以知道,攔截器鏈開始工作是通過 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);
}

可以看到各個不同的攔截器是按順序添加進ArrayList中,在最後幾行代碼中構建了一個 RealInterceptorChain 對象,重點關注構造方法傳遞的第一個和第五個參數,第一個參數是攔截器的List集合, 第五個參數這裏傳的是0 ,這裏加粗是需要注意這裏傳的是0。RealInterceptorChain 對象構建完之後立刻調用的自己的 proceed() 方法,我們跟蹤進去看:

//構造方法
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;
}


//proceed方法
@Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }

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

在構造方法當中可以看到只做了賦值操作,把index賦給了全局變量index,然後在proceed中調用了自己的4個參數的重載方法。在這個重載方法中我們忽略掉那些判斷,重點關注其中的幾行代碼:

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

第一行就 new 了自己,又構建了一個對象,但是這次跟之前不同的是第5個參數 index+1,這裏讓下標增加了1,緊接着從攔截器的List集合中獲取到當前下標的攔截器,這個時候我們應該能知道會發生什麼,在 RealCall 中第一次構建 RealInterceptorChain 對象時,index爲0,等到新的 RealInterceptorChain 對象執行它的 proceed() 方法時,index會+1,以此類推,直到index的值超過攔截器List集合的大小爲止。

但是看到這裏我們也只是看到取出了攔截器,下一次 RealInterceptorChain 調用 proceed() 是什麼時候呢?我們再看下一行代碼,將當前下標的攔截器取出之後,立即執行的它(攔截器)的 intercept() 方法,並且將 第五個參數爲index+1RealInterceptorChain 對象作爲參數傳遞進去,聯繫到文章之前添加攔截器的順序,我們假設沒有添加自定義的攔截器,那麼現在第一個攔截器就是 RetryAndFollowUpInterceptor ,我們跟蹤到這個攔截器的 intercept() 方法中去,記住,這個方法中的參數index是進行了+1操作的:

//這裏的源碼會忽略跟本篇文章分析攔截器工作流程無關的一部分代碼
@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

	//忽略部分代碼

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

	  //忽略部分代碼
    }
}

我們可以看到,首先將參數 chain 強轉爲 RealInterceptorChain ,然後在 try代碼塊 中又調用了 **proceed()**方法,這時我們再回到 RealInterceptorChain 的 **proceed()**方法中,此時index+1,取出的攔截器就是集合中第二次添加的 BridgeInterceptor 了,如此反覆該項操作。

最後能夠發現,在每一個攔截器的 proceed() 方法中都會對之前攔截器處理過的 request 再做自己的處理,處理之後返回 response 給上一個攔截器,這也印證了文章開頭的介紹攔截器鏈意義的那句話以及流程圖中 requestresponse 的傳遞順序。

至於OkHttp中已經定義好的攔截器做了什麼工作、我們自定義攔截器能夠做什麼工作,我找到了一篇講解的比較好的文章:okhttp3源碼分析之攔截器

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