Okhttp-interceptor原始碼分析,快上車!

ZENLovely發表於2018-06-26
	前言:被一位阿里面試官問到過這個問題,“讓我談一下okhhtp攔截器的呼叫順序及影響。”
	我腦子裡只有責任鏈的設計模式,但是幾種攔截器的順序我確實沒記憶過,有點尷尬。
	所以呢,就來稍微研究一下,順便記憶一下,正所謂“面試造航母,工作擰螺絲。”
複製程式碼

1)基本用法

此次我看的原始碼是v3.9.0, 首先我們來看一下官網上基本用法(get請求為例),如下

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
      .url(url)
      .build();
  Response response = client.newCall(request).execute();
  String jsonStr=response.body().string()

複製程式碼

可以看到主要分四步:1.構建OkHttpClient物件;2.構建Request物件;3.呼叫client.newCall(request)得到Call物件;4.Call物件執行同步方法execute(),或者非同步方法enqueue(Callback responseCallback),得到響應的內容.

2)請求過程原始碼分析

2.1 OkhttpClient構建


 public OkHttpClient() {
    this(new Builder());
  }

  OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.eventListenerFactory = builder.eventListenerFactory;
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;

...剔除部分原始碼

    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool;
    this.dns = builder.dns;
    this.followSslRedirects = builder.followSslRedirects;
    this.followRedirects = builder.followRedirects;
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;
....
  }


 public static final class Builder {
    Dispatcher dispatcher;
    @Nullable Proxy proxy;
    List<Protocol> protocols;
    List<ConnectionSpec> connectionSpecs;
    final List<Interceptor> interceptors = new ArrayList<>();
    final List<Interceptor> networkInterceptors = new ArrayList<>();
    EventListener.Factory eventListenerFactory;
    ProxySelector proxySelector;
    CookieJar cookieJar;
    @Nullable Cache cache;
    @Nullable InternalCache internalCache;
    SocketFactory socketFactory;
    @Nullable SSLSocketFactory sslSocketFactory;
    @Nullable CertificateChainCleaner certificateChainCleaner;
    HostnameVerifier hostnameVerifier;
    CertificatePinner certificatePinner;
    Authenticator proxyAuthenticator;
    Authenticator authenticator;
    ConnectionPool connectionPool;
    Dns dns;
    boolean followSslRedirects;
    boolean followRedirects;
    boolean retryOnConnectionFailure;
    int connectTimeout;
    int readTimeout;
    int writeTimeout;
    int pingInterval;

    public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      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;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }
 Builder(OkHttpClient okHttpClient) {
      this.dispatcher = okHttpClient.dispatcher;
      this.proxy = okHttpClient.proxy;
      this.protocols = okHttpClient.protocols;
      this.connectionSpecs = okHttpClient.connectionSpecs;
      this.interceptors.addAll(okHttpClient.interceptors);
      this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
      this.eventListenerFactory = okHttpClient.eventListenerFactory;
      this.proxySelector = okHttpClient.proxySelector;
      this.cookieJar = okHttpClient.cookieJar;
      this.internalCache = okHttpClient.internalCache;
      this.cache = okHttpClient.cache;
      this.socketFactory = okHttpClient.socketFactory;
      this.sslSocketFactory = okHttpClient.sslSocketFactory;
      this.certificateChainCleaner = okHttpClient.certificateChainCleaner;
      this.hostnameVerifier = okHttpClient.hostnameVerifier;
      this.certificatePinner = okHttpClient.certificatePinner;
      this.proxyAuthenticator = okHttpClient.proxyAuthenticator;
      this.authenticator = okHttpClient.authenticator;
      this.connectionPool = okHttpClient.connectionPool;
      this.dns = okHttpClient.dns;
      this.followSslRedirects = okHttpClient.followSslRedirects;
      this.followRedirects = okHttpClient.followRedirects;
      this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure;
      this.connectTimeout = okHttpClient.connectTimeout;
      this.readTimeout = okHttpClient.readTimeout;
      this.writeTimeout = okHttpClient.writeTimeout;
      this.pingInterval = okHttpClient.pingInterval;
    }

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

可以看到Client物件是通過建造者模式設計的,直接new OkhttpClient()方式,傳入預設的builder,或者通過配置builder,呼叫build()構建;

2.2 client.newCall(Request request

Request物件也是通過建造者模式構建的,根據不同的請求構建不同的Request物件;

  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }
/**
我們可以看到,client呼叫.newCall(request),實際呼叫的是RealCall.newRealCall(this, request, false),返回Call物件,說明Realcall應該是實現了Call介面;
在去看一下RealCall.newCall都幹了些什麼;
*/

final class RealCall implements Call {
...

 static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
   
//可以看到RealCall實現了Call介面,newRealCall 內部實際是將入參傳入構造,構建了RealCall物件,建立一個call的事件監聽;
	在看看RealCall構造幹了什麼;
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

 private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
	//內部將入參client,原始的請求賦值,同時構建了RetryAndFollowUpInterceptor()攔截器;
	//該攔截器的作用就是發生錯誤時,進行重定向;

    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }


}

 
複製程式碼

2.3 call.excute() call.enqueue(callback)

我們繼續跟進拿到call物件後,真正發起網路請求的操作 繼續看call的實現類RealCall

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();

	//開始監聽call請求

    eventListener.callStart(this);
    try {

	//通過client的dispatcher,分發請求;Dispather中構建了一個執行緒池,所有的請求會加入到執行緒池中執行;
//這裡

      client.dispatcher().executed(this);

	//這個方法很關鍵,也是Okhttp攔截器的責任鏈呼叫的開始,是這個庫的精華所在,可以看到,在這裡返回了伺服器響應的內容
	//我已經迫不及待一探究竟了,哈哈哈哈哈哈= =
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }


  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
//開始監聽
    eventListener.callStart(this);
//非同步呼叫


    client.dispatcher().enqueue(new AsyncCall(responseCallback));

/**
*dispatcher物件的enqueue方法,可以看到,如果儲存非同步Call的集合小於最大請求數,並且未達到同一個host最大的連線數
*那麼直接加入待執行佇列,呼叫execute,執行;否則,先加入預執行佇列
*/
synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

  }

複製程式碼

2.4 攔截器責任鏈的呼叫 getResponseWithInterceptorChain()

為了方便大家理解,請允許我先上個圖:

Okhttp-interceptor原始碼分析,快上車!

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
//1.構建一個空集合interceptors
    List<Interceptor> interceptors = new ArrayList<>();

//2.可以看到首先會將client的interceptors全部加入這個集合頭部,這個client的interceptors就是我們在構建Client Builder時,呼叫
// addInterceptor(Interceptor interceptor)加入的自定義攔截器,也就是所謂的應用攔截器;
    interceptors.addAll(client.interceptors());
//3.尾部追加重定向攔截器 retryAndFollowUpInterceptor,還記得這個攔截器是什麼時候建立的嗎?
//沒錯,就是在new RealCall的時候構建的.
    interceptors.add(retryAndFollowUpInterceptor);
//4.尾部新增BridgeInterceptor,設定預設的cookjar,這是一個請求和響應的聯結器
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
//5.新增快取攔截器,處理一些網路快取邏輯,控制走網咯還是走快取等
    interceptors.add(new CacheInterceptor(client.internalCache()));
//6.新增網路連線攔截器
    interceptors.add(new ConnectInterceptor(client));
//7.如果不是websocket,還新增了網路攔截器,新增自定義連線攔截器(比如我們常用的facebook除錯框架的開源框架stecho)
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
//8.新增了請求服務攔截器
    interceptors.add(new CallServerInterceptor(forWebSocket));
// 以上是存放在arryList中intercptor的順序,
//9.可以看到接下來就是構建責任鏈Chain, 其實現類是RealInterceptorChain,
// RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
//      HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
//     EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) 
//10.其中,我們最主要來關注一下 index這個入參
//可以看到,鏈的開始index入參為0,我們繼續跟進一下chain.proceed(originalRequest)幹了什麼;

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }
/*==========================*/
  public Builder addInterceptor(Interceptor interceptor) {
      if (interceptor == null) throw new IllegalArgumentException("interceptor == null");
      interceptors.add(interceptor);
      return this;
    }
  public List<Interceptor> interceptors() {
    return interceptors;
  }

//進入Chain的實現類RealInterceptorChain,看一下proceed做了哪些事情
//我們看一些關鍵的地方.

 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
//11.首先判斷index是否超出攔截器索引,超出就跑異常,終止責任鏈
    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");
    }

    // 12.構建下一個chain物件,可以看到此處index+1,
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);

//13.從集合中取出下一個攔截器,呼叫 interceptor.intercept(chain)返回Response;我們可以隨便看一個interceptor的實現類,
//研究一下interceptor.intercept(chain) 是如何返回Response的;

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

複製程式碼

2.5 攔截器責任鏈的呼叫 interceptor.intercept(chain)

我們就以CacheInterceptor為例,看看它的intercept() fun都做了哪些事情.

 @Override public Response intercept(Chain chain) throws IOException {

...省略部分原始碼,我們只看關鍵程式碼

//1.可以看到這裡判斷了網路請求是否存在,是否有響應快取,兩則都null,時,構建一個504錯誤碼的Resonse並返回
// 該方法return
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // 2.判斷無請求網路,返回快取內容,方法return
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
	

//3.上述兩種情況都不滿足,走到這裡,呼叫chain.proceed(request)
//看到這個Api是否似曾相識呢~~哈哈哈哈哈,回到getResponseWithInterceptorChain()方法,其就是呼叫了
//chain.proceed(originalRequest)得到Response返回;
//而,chain.proceed 中如上分析,會把index++,構建下一chain,至此,責任鏈就完整形成了.
//前一個chain.proceed 返回值依賴後一個chain.proceed返回,由此遞迴,直到index 超出丟擲超異常.

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

  ....省略程式碼

    return response;
  }
複製程式碼

3)總結

我們可以看到Okhttp在構建Client和Request物件時用到了Builder模式,這才我們開發中也使用場景也很多,尤其是引數很多的情況下,通過構造或者set方法初始化就不是特別友好;Interceptor的責任鏈模式設計模式也很巧妙,我們可以通過很輕鬆的新增或刪除攔截器實現許多業務場景,但是細想一下也不難發現,責任鏈模式如果鏈太長的情況下會發生什麼?方法不斷壓棧,只有鏈呼叫結束或者異常時,才會出棧,釋放棧空間. 有哪裡寫錯或理解錯誤的歡迎討論指出~~

相關文章