1. 程式人生 > >深入理解OkHttp源碼(一)——提交請求

深入理解OkHttp源碼(一)——提交請求

mat esp 屬於 idt set ref setname 失敗 class

本篇文章主要介紹OkHttp執行同步和異步請求的大體流程。主要流程如下圖:
技術分享
主要分析到getResponseWidthInterceptorChain方法,該方法為具體的根據請求獲取響應部分,留著後面的博客再介紹。

Dispatcher類

Dispatcher類負責異步任務的請求策略。首先看它的部分定義:

public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private 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<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }

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

  ...

}

內部有一個線程池,三個隊列,分別是readyAsyncCalls、runningAsyncCalls和runningSyncCalls隊列,其中runningSyncCalls用於存儲同步請求RealCall對象,runningAsyncCalls隊列用於存儲進入隊列的異步請求AsyncCall對象,而readyAsyncCalls隊列用於當runningAsyncCalls的尺寸達到maxRequests參數時(默認64)存儲新加的異步請求。至於為什麽要什麽做呢?
我的理解是為了避免一時間創造大量的線程浪費資源,那麽為什麽有線程池,還要用到這樣一個控制策略呢?這是因為創建默認線程池的參數導致的。默認的executorService的創建類似於Executors.newCachedThreadPool,該線程池的問題在於不會限制線程數量,如果一下子需要開啟1000乃至更多的線程,依然會開啟,而OkHttp這兒在Dispacther中做了控制。待會兒在下面的分析中可以看到這種控制策略。
其余的參數maxRequestsPerHost表示每個主機的最大請求數,,默認為5,比如說如果這時好多個異步請求都是請求百度上面的圖片,如果達到了5,那麽新的請求就會被放入到readyAsyncCalls隊列中,等該主機的請求數降下去後才會再次執行。
而參數idleCallback是Dispatcher中請求數量為0時的回調,這兒的請求包含同步請求和異步請求,該參數默認為null。
在Dispatcher中,需要明白一點,盡管同步請求自己負責執行請求,但是依然會先加入到Dispatcher的同步隊列,完成後從隊列中移除,而異步請求則完全屬於Dispatcher控制,但是有些方法是對所有請求操作的,有些則是對異步請求操作的,需要特別註意。
一般地,我們會將OkHttpClient作為單例,而Dispatcher是其一個成員,自然也是單例,所以一般整個應用的所有請求都會經過Dispatcher,不論是同步請求還是異步請求。

同步請求的執行流程

在使用OkHttp進行網絡同步異步操作中知道了如何進行同步請求,創建一個Request對象,然後再創建一個Call對象,調用Call對象的execute方法即可。那麽就從execute方法看起。Call是一個接口,具體實現是RealCall,下面是RealCall的execute方法實現:

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

首先是設置executed標誌為true,同一個Call只允許執行一次,執行多次就會拋出異常。接下來是調用OkHttpClient的dispatcher()方法獲得Dispatcher對象,然後調用其executed(RealCall)方法,然後就是調用getResponseWithInterceptorChain方法同步獲取響應,最後調用Dispatcher的finished方法,下面先看executed方法:

/** Used by [email protected] Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

從代碼中可以看出,Dispatcher的executed方法只是將同步請求加入到了runningSyncCalls隊列中。下面再看finished方法:

/** Used by [email protected] Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }

從上面代碼中,可以看到finished方法再調用另一個finished方法,並將runningSyncCalls隊列傳入,具體實現如下:

 private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn‘t in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

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

首先是從隊列中移除請求,如果不能移除,則拋出異常;在上面finished方法調用中看出傳入第三個參數為false,所以不會調用promoteCalls方法,該參數用於異步請求時為true,這個下面分析異步請求時再講。然後調用runningCallsCount統計目前還在運行的請求,最後,如果正在運行的請求數為0表示Dispatcher中沒有可運行的請求了,進入Idle狀態,此時如果idleCallback不為null,則調用其run方法。下面是runningCallsCount()方法的實現:

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

可以看到這個方法返回的請求包括同步請求和異步請求。
至此,同步請求的執行流程分析完成,可以看到Dispatcher只是保存了一下同步請求和移除同步請求,而對於異步請求,Dispatcher的工作就不只是這麽簡單了。

異步請求的執行流程

我們知道如果要發起異步請求,那麽就調用Call的enqueue方法並傳入回調,依然從RealCall的enqueue方法看起:

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

可以看到,依然是首先將executed參數設為true,同樣地,異步請求也不可以被執行兩次,然後調用Dispatcher的enqueue方法,但是這兒涉及到了一個新的類,AsyncCall。AsyncCall是RealCall的一個內部類並且繼承NamedRunnable,那麽首先看NamedRunnable類是什麽樣的,如下:

/**
 * Runnable implementation which always sets its thread name.
 */
public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

可以看到NamedRunnable實現了Runnbale接口並且是個抽象類,其抽象方法是execute(),該方法是在run方法中被調用的,這也就意味著NamedRunnable是一個任務,並且其子類應該實現execute方法。下面再看AsyncCall的實現:

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    private AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl().toString());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

AsyncCall實現了execute方法,首先是調用getResponseWithInterceptorChain()方法獲取響應,然後獲取成功後,就調用回調的onReponse方法,如果失敗,就調用回調的onFailure方法。最後,調用Dispatcher的finished方法。
由於AsyncCall的execute()方法是在run中被調用的,所以getResponseWithInterceptorChain是在非調用線程中被調用的,然後得到響應後再交給Callback。
從上面的流程看出,與Dispatcher的交互主要涉及enqueue方法和finished方法,與同步請求類似。下面先看enqueue方法:

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

首先如果正在運行的異步請求的數量小於maxRequests並且與該請求相同的主機數量小於maxRequestsPerHost,也就是說符合放入runningAsyncCalls隊列的要求,那麽放入隊列,然後將AsyncCall交給線程池;如果不符合,那麽就放入到readyAsyncCalls隊列中。
當線程池執行AsyncCall任務時,它的execute方法會被調用,getResponseWithInterceptorChain()會去獲取響應,最後調用Dispatcher的finished方法,下面看finished方法:

/** Used by [email protected] AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

從上面的代碼可以看出,與同步請求的finished方法不同的是第一個參數傳入的是正在運行的異步隊列,第三個參數為true,下面再看有是三個參數的finished方法:

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn‘t in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

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

與同步請求相同的是,移除請求,獲取運行數量判斷是否進入了Idle狀態,不同的是會調用promoteCalls()方法,下面是promoteCalls()方法:

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }
promoteCalls方法主要負責從異步等待隊列中將請求移步到異步運行隊列中。主要就是遍歷等待隊列,並且需要滿足同一主機的請求小於maxRequestsPerHost時,就移到運行隊列中並交給線程池運行。

至此,分析完了同步請求和異步請求的提交流程,Dispatcher負責異步請求是放入運行隊列還是等待隊列中,並且在每個異步請求執行完後,需要判斷是否需要把等待隊列中的請求移到運行隊列中並運行。不管是同步請求還是異步請求,最終都會調用getResponseWithInterceptorChain()方法進行具體的網絡請求,該方法下篇博客深入理解OkHttp源碼(二)——獲取響應會具體介紹。

深入理解OkHttp源碼(一)——提交請求