OkHttp源碼解析(二)dispatcher

        上一篇文章,總結了okhttp的同步請求和異步請求,並跟蹤源碼進行了分析。我們發現,不管是同步請求還是異步請求,都離不開一個類Dispatcher。事實上,Dispatcher是okhttp的一個非常關鍵的類,是okhttp請求的分發器。這篇文章,我們重點分析一下Dispatcher的源碼。

        Dispatcher的源碼不多,200多行。但是,它的設計非常精妙,而且它是okhttp網絡請求的核心類之一。對Dispatcher的源碼解析,需要結合同步請求和異步請求來進行。其實,我們在上一篇文章對同步請求和異步請求進行源碼分析的時候,已經對Dispatcher的關鍵代碼進行了分析。這篇文章,我們更詳細的從頭到尾去剖析一下Dispatcher的源碼。

一、Dispatcher類的幾個變量

        首先,我們看一下Dispatcher類源碼的一些變量,在這裏,我不會深入去解釋每個變量在後面的作用,我只是去總結一下這些變量是什麼:

private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

     (1)maxRequest:默認64。這是okhttp允許的最大請求數量。

     (2)maxRequestsPerHost :默認5。這是okhttp對同一主機允許的最大請求數量。

     (3)idleCallback:我們可以看到,他是一個Runnable,後面我會說一下他的作用。

     (4)executorService:不用多說,這個是Dispatcher用於執行請求的線程池。

     (5)readyAsyncCalls:"將會"被按順序執行的異步請求隊列。因此,他們只是被加進了一個就緒態的隊列。。

     (6)runningAsyncCalls:正在執行的異步請求隊列。也就是說該隊列裏的請求是正在被執行或者被取消掉的沒執行完的異步請求。

     (7)runningSyncCalls:正在執行的同步請求隊列。也就是說該隊列裏的請求是正在被執行或者被取消掉的沒執行完的同步請求。

二、關鍵方法

1、executorService

        繼續往下跟蹤源碼,構造函數不多說,再往下是線程池,我們看一下源碼:

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

        在這裏,簡單說一下線程池的實現。我們創建一個線程池,需要幾個關鍵的參數。corePoolSize:核心線程數;maxPoolSize:最大線程數;keepAliveTime:存活時間;workQueue:等待隊列以及線程工廠和拒絕策略。對這幾個關鍵參數,我不去做過多的解釋,這是線程池的基礎,相信大家比我熟悉。

        corePoolSize爲0,maxPoolSize爲Integer的最大值,keepAliveTime爲60秒。在這裏,有一個很有意思的設定。爲什麼說有意思呢,因爲corePoolSize設置爲0。熟悉線程池的朋友應該知道,當線程池中執行完所有的任務,並且在keepAliveTime的時間裏沒有來新的任務時,如果核心線程數不爲0,那麼線程池將無法關閉。如果設置核心線程數爲0,那麼到了60秒之後,線程池自動關閉。

2、setMaxRequests&&getMaxRequests

        這兩個方法意思很明確,就是我們上面說過的最大請求數的get和set方法,然後會執行promoteAndExecute方法,我們後面去看這個方法:

  public void setMaxRequests(int maxRequests) {
    if (maxRequests < 1) {
      throw new IllegalArgumentException("max < 1: " + maxRequests);
    }
    synchronized (this) {
      this.maxRequests = maxRequests;
    }
    promoteAndExecute();
  }
  public synchronized int getMaxRequests() {
    return maxRequests;
  }

3、setMaxRequestsPerHost和getMaxRequestsPerHost

         這兩個方法意思很明確,就是我們上面說過的每個主機最大請求數的get和set方法,然後會執行promoteAndExecute方法,我們後面去看這個方法:

public void setMaxRequestsPerHost(int maxRequestsPerHost) {
    if (maxRequestsPerHost < 1) {
      throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
    }
    synchronized (this) {
      this.maxRequestsPerHost = maxRequestsPerHost;
    }
    promoteAndExecute();
  }

  public synchronized int getMaxRequestsPerHost() {
    return maxRequestsPerHost;
  }

4、enqueue

        這就是上一篇文章說過的,dispatcher執行異步請求的方法。把call加入就緒的異步請求隊列。這個和同步請求的實現不一樣,同步請求是直接加入了runningSyncCalls,而異步請求是放進了readyAsyncCalls。然後會執行promoteAndExecute方法,我們後面去看這個方法:

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

5、cancelCall

        這個方法是取消請求的方法。通過源碼我們不難發現,會遍歷就緒異步隊列,執行異步隊列,執行同步隊列,對其中所有的call進行取消的操作:

  public synchronized void cancelAll() {
    for (AsyncCall call : readyAsyncCalls) {
      call.get().cancel();
    }

    for (AsyncCall call : runningAsyncCalls) {
      call.get().cancel();
    }

    for (RealCall call : runningSyncCalls) {
      call.cancel();
    }
  }

6、promoteAndExecute

        接下來,我們看一下上面多次被提及到的promoteAndExecute方法。這個方法我們在上一篇文章中也分析過。在這裏再去總結一下:

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

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      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;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

        這個方法,首選會去遍歷就緒隊列中的call,在遍歷的過程中,會去做兩個判斷:(1)如果正在執行隊列的請求數超過了最大的請求數容量則break,如果一個Host的請求比允許的每個host的請求容量小,那麼將會continue,並且把異步請求添加到兩個隊列:可執行隊列和正在執行異步請求隊列。後面,去遍歷可執行隊列中的請求,並且通過executeOn方法,去執行異步請求。

 7、runningCallsForHost

        這個方法,返回的是對一個主機的請求數量,這個返回值就是用於上面的判斷:

  /** Returns the number of running calls that share a host with {@code call}. */
  private int runningCallsForHost(AsyncCall call) {
    int result = 0;
    for (AsyncCall c : runningAsyncCalls) {
      if (c.get().forWebSocket) continue;
      if (c.host().equals(call.host())) result++;
    }
    return result;
  }

8、executed

        很熟悉的一個方法咯,執行同步請求的方法,把同步請求加入到同步請求執行隊列。在後面,我們會看到同步請求隊列和異步請求隊列的用處。

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

9、finished

        finished方法,我們在上一篇文章也多次提到過,在執行完同步請求或者異步請求後,會在finally中去執行finished。我們看一下finished具體做了什麼:

  /** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call);
  }

  /** Used by {@code Call#execute} to signal completion. */
  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();
    }
  }

        在這裏,我們看到,首先進行移除call的操作。如果已經移除了,那麼拋出一個異常,如果沒有移除則進行移除。isRunning是promoteAndExecute方法的返回值,這個返回值的意思是當前是否正在執行一個請求。後面是一個判斷,如果沒有在執行請求並且idldCallback不爲空,則執行run方法。在這裏,用到了我們最上面提到的一個變量idleCallback,我們知道他是一個Runnable對象。我們看一下這個callBack的作用:

Set a callback to be invoked each time the dispatcher becomes idle (when the number of running calls returns to zero)

        設置每次調度程序空閒時調用的回調(當正在運行的調用數返回到零時)。也就是說,當正在執行的請求數爲0的時候,Dispatcher處於idle狀態,這時候,通過回調這個callback我們知道Dispatcher是否處於空閒狀態。

10、runningCalls&&queuedCalls

        這兩個方法,也就是各自把就緒隊列和正在執行隊列的請求加到了一個隊列。這樣,外部可以調用這兩個方法,獲取到就緒隊列和正在執行隊列中的請求:

  /** Returns a snapshot of the calls currently awaiting execution. */
  public synchronized List<Call> queuedCalls() {
    List<Call> result = new ArrayList<>();
    for (AsyncCall asyncCall : readyAsyncCalls) {
      result.add(asyncCall.get());
    }
    return Collections.unmodifiableList(result);
  }

  /** Returns a snapshot of the calls currently being executed. */
  public synchronized List<Call> runningCalls() {
    List<Call> result = new ArrayList<>();
    result.addAll(runningSyncCalls);
    for (AsyncCall asyncCall : runningAsyncCalls) {
      result.add(asyncCall.get());
    }
    return Collections.unmodifiableList(result);
  }

11、queuedCallsCount&&runningCallsCount

        這兩個方法也是可以供外部調用的,返回的是就緒隊列的請求數量和執行隊列的請求數量。注意,這裏的執行隊列的請求數量,是同步請求和異步請求的和:

  public synchronized int queuedCallsCount() {
    return readyAsyncCalls.size();
  }

  public synchronized int runningCallsCount() {
    return runningAsyncCalls.size() + runningSyncCalls.size();
  }

        以上就是對Dispatcher類所有變量和方法的具體分析。最後,我們總結一下:不管是同步請求還是異步請求,都是由dispatcher來進行任務調度的,最終是由其內部維護的線程池執行網絡請求。

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