OkHttp3源碼分析(二)-Dispatcher和Interceptor

對OkHttp介紹,我們分爲使用篇源碼分析篇兩個系列進行介紹。

上一篇文章中,我們大致梳理了一下OkHttp的請求過程。但是中間涉及到的client.dispatcher().executed(this);client.dispatcher().enqueue(new AsyncCall(responseCallback));getResponseWithInterceptorChain()這幾個過程我們並沒有進入去詳細分析,只是提了每一個方法的作用。所以本篇文章的內容就是對這幾個方法進行說明。

首先,前兩個方法都是通過client.dispatcher()獲得了Dispatcher對象,然後調用它對應的方法。這裏我們就先介紹下Dispatcher這個OkHttp框架中的任務調度類。

1 Dispatcher

在纖細介紹該類之前,我們先看下爲什麼通過client.dispathc()就能獲得到Dispatcher對象了,也就是先介紹下該對象的創建過程是怎麼樣的。

1.1 Dispatcher的創建過程

各位應該還記得我們OkHttp的基本使用方式吧:

OkHttpClient client = new OkHttpClient();

  String run(String url) throws IOException {
    Request request = new Request.Builder()
        .url(url)
        .build();

    try (Response response = client.newCall(request).execute()) {
      return response.body().string();
    }
  }

第一步就是創建OkHttpClient對象,我們看下它的構造方法:

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

創建Builder後,調用了它的有參構造,我們先看下Builder的構造過程:

public Builder() {
      dispatcher = new Dispatcher();
      ……
    }

這裏省略一些代碼,但是可以看到,在Builder的構造方法中,初始化了一個Dispatcher對象。然後我們在回到上一步流程,調用OkHttpClient的有參構造:

OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    ……
  }

也省略了無關代碼,可以看到,這裏就將Builder中的dispather賦值給了OkHttpClient中的dispatcher屬性。所以我們通過client.dispater()就能獲取到Dispatcher對象了:

public Dispatcher dispatcher() {
    return dispatcher;
  }

Dispatcher怎了來的搞清楚了,我們接着就看下它是怎麼起到任務調度作用的。

1.2 Dispater簡介

在具體跟蹤分析上述提到的excuted(Call)equeue(AnsycCall)之前,我們先來簡單看下Dispatcher這個類的成員變量,這有助於我們後面的流程分析。

public final class Dispatcher {
  // 最大請求數目
  private int maxRequests = 64;
  // 統一個Host最大請求數目
  private int maxRequestsPerHost = 5;
  

  // 線程池 用於執行異步請求任務
  private @Nullable ExecutorService executorService;

  // 準備發起異步請求的雙向隊列
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  // 正在發起異步請求的雙向隊列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  // 正在發起同步請求的雙向隊列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
  ……
}

這裏列出了幾個比較關鍵的字段。其中maxRequests規定了同一個OkHttpClient中可同時發起的最大請求數,maxRequestsPerHost規定了統一Host中可同時發起的最大請求數。接着executorService是一個線程池,最終我們的異步任務就是通過該線程池執行的。從這我們可以看到,並沒有在這裏直接進行初始化,OkHttp框架進行了一種懶加載策略的,在它的getter方法中可以看到它的創建:

public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

可以看到,該線程池的核心線程數爲0,保活60s,也就意味着空閒線程在等待60秒後如果還沒有使用就會被回收。以Integer.MAX_VALUE爲最大線程數數量,我們也就可以認爲該線程池是沒有上界限制的,只要有合法請求,就會創建新的線程,這裏的合法請求是指沒有超過maxRequestsmaxRequestsPerHost的限制。
剩下還有三個雙向隊列的屬性,註釋裏面已經說的比較清楚了,這裏就不再贅述了。

1.3 Dispatcher任務調度過程

有了上面的基礎,就可以進入任務調度過程分析了,這裏我們還是先看同步任務,這裏貼出RealCall.execute方法

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

直接跟進去看下Dispatcher.executed(ReallCall)方法:

synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

非常簡單,就是將傳入的RealCall加入了runningSyncCalls這個隊列中。接着看上面的流程,當請求完成後最終會調用Dispatcher.finishd(ReallCall)方法,我們進去看下:

void finished(RealCall call) {
    finished(runningSyncCalls, call);
}

調用了它的重載方法:

private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
    	// 移除隊列
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }
    // 這裏會取檢查執行異步請求
    boolean isRunning = promoteAndExecute();

    if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }
  }

此時這裏的參數calls就是runningSyncCalls對象,所以這裏第一步就是將之前的Call移除隊列。然後執行了promoteAndExecute();這個方法,該方法如果返回fasle說明當前沒有請求任務了,所以後續做了判斷,如果沒有任務,idleCallback也不爲null的話,就會回調該對象的run。所以如果我們想在空閒的時候做一些處理,就可以設置IdleCallback接口。接着進入promoteAndExecute();方法裏面具體看下是怎麼處理的:

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      // 從readyAsyncCalls取出任務,加入executableCalls集合
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();

        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }
	// 從executableCalls裏面一次取出AsyncCall對象,並調用它的executeOn()方法
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

這個方法裏面就是從readyAsyncCalls隊列中取出待執行的異步任務,調用異步任務的executeOn(executorService())方法。其中參數是之前所述的線程池對象——executorService。到這裏就和上篇分析的異步請求流程接上了,這裏就不再往下分析了。
到這裏,同步調度過程就分析完了,可以看到,同步請求任務並沒真正由Dispatcher來調度,只是在其內部維護了一個雙向隊列而已。所以Dispatcher主要目的還是用來調度異步任務的,接下來看異步調度過程。
還是從ReallCallenqueue(Callback)方法開始跟蹤:

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

比較簡單,通過我們傳入的Callback對象構造了一個AsyncCall,然後調用了Dispatcherenqueue(AsyncCall)方法,跟進去:

void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);
    }
    promoteAndExecute();
  }

就是將call加入到readyAsyncCalls隊列中,然後調用了promoteAndExecute();這個方法,前面已經分析過了,這裏不再贅述。通過上篇文章的分析,我們知道,異步請求,最終在AsyncCall對象中執行excute()方法的時候,最終也會調用Dispatcher.finish(AsyncCall)方法,最終會調用finished(Deque<T> calls, T call)這個方法,這個方法前面已經分析又會去執行promoteAndExecute();方法,直到任務隊列中沒有待執行的任務。

到這,整個整個任務的調度就分析完了。接下來我們接着看下上篇沒有詳細說明的getResponseWithInterceptorChain();方法。

2 Interceptor調用鏈

上篇文章我們只是說了getResponseWithInterceptorChain();會進入到我們的設置的Interceptor中,並最終返回請求的數據。具體是怎麼實現的呢?我們跟進去看下:

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //添加開發者應用層自定義的Interceptor
    interceptors.addAll(client.interceptors());
    //這個Interceptor是處理請求失敗的重試,重定向
    interceptors.add(retryAndFollowUpInterceptor);
    //這個Interceptor就是OkHttp框架自動是添加一些額外的支持
    //(如:使用篇裏面說到的gzip壓縮支持就是在這裏面處理)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //這個Interceptor的職責是判斷緩存是否存在,讀取緩存,更新緩存等等
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //這個Interceptor的職責是建立客戶端和服務器的連接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
    //添加開發者自定義的網絡層攔截器
      interceptors.addAll(client.networkInterceptors());
    }
    //一個包裹這request的chain
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
//這裏就把chain傳遞到第一個Interceptor調用鏈中了
    return chain.proceed(originalRequest);
  }

這裏創建了一個ArrayList,讓後不斷的往這個list裏面添加Interceptor。至於每個Interceptor的作用,代碼註釋裏面已經說明了。最後通過RealInterceptorChain.proceed(Request)方法進入了Interceptor的調用鏈

2.1.5 RealInterceptorChain.proceed()

接着上面的步驟,我們看下怎麼通過proceed()方法就進入了Interceptor的調用鏈的。分析這個方法之前,先注意上面創建RealInterceptorChain對象的初始化參數:

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

這裏傳入了我們的Interceptor的List集合和Request對象,同時初始化index=0(第五個參數)。
接着看下proceed()方法:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    ……
    // 創建下一個Interceptor所需的RealInterceptorChain
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
	// 獲取index所在的Interceptor,並調用該Interc的intercept(Chain)方法
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
……

    return response;
  }

這裏暫時只需要我們先把整個調用鏈的流程搞清楚,所以就只留下了關鍵代碼。其他內容,後續文章分析網絡請求的時候,還會詳細說明。
根據上面的描述我們知道當前這個Chain對象中的index=0,所以Interceptors.get(index)是第一個Interceptor,緊接着又使用Request和index+1的索引構建了下一個Chain對象。然後進入第一個Interceptor的intercept(Chain)方法,傳入的是新的Chain對象。根據之前使用篇裏面學習的知識,我們知道在這個方法中,如果我們需要放行一般會調用chain.process(Request)方法,這樣就會進入上面新創建的Chainprocess(Request)會進入該方法裏面,此時index=1的,所以又會取出下一個Interceptor出來,以此類推,一層一層往下執行,最終就形成了Interceptor的調用鏈。
根據以上分析,我們可以畫出大致的調用鏈的圖如下:
Interceptor調用鏈
這裏只做整體流程分析,具體每個Interceptor裏面做的事情就不做詳述了。最後一個CallServerInterceptorintercept(Chain)方法裏面裏面是沒有調用chain.proceed()方法的,直接從網絡獲取Response並返回了,又一層一層的返回到最上面getResponseWithInterceptorChain方法中。最終就形成了一個“U”型結構的調用鏈。

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