OKHTTP異步和同步請求簡單分析

OKHTTP異步和同步請求簡單分析
OKHTTP攔截器緩存策略CacheInterceptor的簡單分析
OKHTTP攔截器ConnectInterceptor的簡單分析
OKHTTP攔截器CallServerInterceptor的簡單分析
OKHTTP攔截器BridgeInterceptor的簡單分析
OKHTTP攔截器RetryAndFollowUpInterceptor的簡單分析
OKHTTP結合官網示例分析兩種自定義攔截器的區別

同步請求就是執行請求的操作是阻塞式,直到 HTTP 響應返回。它對應 OKHTTP 中的 execute 方法。

異步請求就類似于非阻塞式的請求,它的執行結果一般都是通過接口回調的方式告知調用者。它對應 OKHTTP 中的 enqueue 方法。

示例代碼

下面的代碼演示了如何進行同步和異步請求的操作。

OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
        .url("http://www.qq.com")
        .build();
Call call = okHttpClient.newCall(request);
//1.異步請求,通過接口回調告知用戶 http 的異步執行結果
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        System.out.println(e.getMessage());
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        if (response.isSuccessful()) {
            System.out.println(response.body().string());
        }
    }
});
//2.同步請求
//Response response = call.execute();
//if (response.isSuccessful()) {
//    System.out.println(response.body().string());
//}

異步請求的基本原理

OKHTTP異步任務執行圖解.png

Call

負責準備去執行一個 request 請求,一個 call 只能負責去執行一個請求,不能被執行兩次。因為 OkHttpClient 是實現了 Call.Factory 因此它具備創建 Call 對象的功能,內部創建的就是 RealCall 對象。Call 是封裝 request 的,它表示一個可以執行的請求。

Call 的實現類 RealCall

因為 Call 是接口,內部定義了同步與異步的請求,以及取消請求等操作,這些操作是由 RealCall 真正去實現的。

在 RealCall 中關鍵的幾個屬性:

  • client 就是我們在外界創建的 OkHttpClient 對象,通過這個 client 就是調用 Dispatcher 去分發請求任務。

  • executed 它是 boolean 類型,上面介紹 Call 時已經說明了,一個 Call 只能被執行一次,在內部就是通過這個屬性進行判斷的。

  • originalRequest Request 對象,它就是通過 okHttpClient.newCall(request) 傳入的 Request 對象,這個 Request 在整個網絡請求起到非常重要的作用,它會被傳入到各個 Interceptor 中去。例如用戶創建的 request 對象,只是簡單的設置了 url ,method,requestBody 等參數,但是想要發送一個網絡請求這樣簡單地配置還是不夠的,系統提供的攔截器 BridgeInterceptor 就是負責做這件事,它會為該請求添加請求頭,例如 gzip,cookie,content-length 等,簡單說它會將用戶創建的 request 添加一些參數從而使其更加符合向網絡請求的 request 。其他攔截器的功能也是對 request 進行操作,具體看源碼。

Dispatcher 相關知識點

異步任務分發器,它會內部指定線程池去執行異步任務,并在執行完畢之后提供 finish 方法結束異步請求之后從等待隊列中獲取下一個滿足條件的異步任務去執行。

1、在 Dispatcher 有幾個比較重要的屬性,這幾個屬性會影響異步請求的執行。

  • int maxRequests = 64 會去指定并發 call 的最大個數。

  • maxRequestsPerHost = 5: 每個主機最大請求數為5 ,也就是最多 5 個call公用一個 host。這個host 就是在 RealCall 中通過 originalRequest.url().host() 去獲取的,例如 www.baidu.com

  • executorService 就是執行異步任務的線程池,在內部中已經指定好了線程池,當然也可以在 Dispacther 中通過構造方法去指定一個線程池。

  • Deque<AsyncCall> readyAsyncCalls 表示在隊列中已經準備好的請求。

  • Deque<AsyncCall> runningAsyncCalls 正在執行的異步請求,包括已經取消的請求(還沒有執行finish操作的請求。)

  • Deque<RealCall> runningSyncCalls 正在運行的同步請求,包括已經取消的請求(還沒有執行finish操作的請求。)

2、關于 Dispatcher 的功能在下面的異步和同步請求中我們再一一探索。

Call 的實現者 RealCall

它具備有異步和同步請求,還有取消請求的功能,它內部有一個 AsyncCall 內部類,在 Dispatcher 中分發的異步請求任務就是 AsyncCall 。這里分發的任務指的是異步任務,而不是同步任務。AsyncCall 就是用表示一個異步任務的,在 Dispatcher 內部有維護了兩個隊列來存儲 AsyncCall,分別是 readyAsyncCalls 和
runningAsyncCalls 它們分別表示準備要執行的 AsyncCall 隊列和正在執行的 AsycnCall 隊列。當然還有一個 runningSyncCalls 這個隊列,但是它適用于存放 RealCall ,也就是用于存儲同步請求的任務。

  • 在 RealCall 實現異步請求 call.enqueue(new Callback())
@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    //檢測該 call 是否被執行過了,如果已經執行了,那么就拋出異常
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
 //關鍵代碼:將 AsycnCall 添加到隊列中。將任務交給 Dispatcher 去執行。
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
  • 使用 Dispatcher 去分發一個異步任務

合理性的校驗操作,我們在介紹 Dispacther 的相關屬性時已經說明,在 OKHTTP 中正在執行的請求不能超過 64 個,并且同一個主機不能超過 5 請求,當滿足這兩個條件,即可將任務添加到正在執行的隊列 runningAsyncCalls 中,并且通知線程池安排線程去執行這個異步任務,否則就會被添加到等待隊列中 readyAsyncCalls。

synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    //當正在執行的請求小于64個&&該 call 對應的主機少于5個 Call 時
    //將任務添加到 runningAsycnCalls 中,標記為正在執行的任務。
    runningAsyncCalls.add(call);
    //在線程池中執行這個任務。
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

真正的異步執行者 AsyncCall

在前面提到了 AsyncCall 表示的是一個異步任務,在使用 Dispatcher 會將 AsyncCall 交給指定的線程去執行,而 AsyncCall 是 NamedRunnable 的子類,因此它也具備 Runnble 的特性,換句話說,在線程池中執行的任務就是 AsyncCall 了。

當線程池執行這個異步任務時,那么該 Runnable 的 run 方法就會被執行,我們查閱了源碼,在 run 方法內部會去調用 AsyncCall 的 execute 方法。

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

總結該方法中它主要做了這 3 件事:

  • 得到 HTTP 請求的響應 Response :Response response = getResponseWithInterceptorChain();得到一個 response 響應。(這個是一個遞歸的調用過程,具體在其他博客中再分析具體實現。)

  • 給調用進行接口回調異步任務執行的結果:responseCallback.onResponse 或者 responseCallback.onFailure

  • 結束該請求,并且執行下一個等待的異步任務:client.dispatcher().finished(this);

對于 AsyncCall 中所做的這 3 步中,前面兩步都比較好理解,下面主要看看它是如何結束一個請求的并且開啟下一個異步請求的?

我們在前面介紹 Dispacther 已經了解了它的作用,這里再次強調一下,它是負責去分發一個異步任務給指定的線程池去執行,并且可以在執行完畢之后去等待隊列中獲取下一個請求去執行。

在 AsyncCall 中的 execute 中執行一個異步請求,注意在 finally 塊內部調用了 client.dispatcher().finished(this);它的作用是通知 Dispatcher 我的任務執行完畢了,你可以將我從集合中移除了,開啟下一個異步任務吧。下面就是 finish 的源碼:

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

總結該方法中它主要做了這 3 件事:

  • 從 calls 中移除該 AysyncCall 對象,而這個 calls 就是 Dispatcher 中的 runningAsyncCalls。

  • promoteCalls() 調用該方法就可以實現從等待隊列中取出下一個異步任務去執行。

  • idleCallback.run(); 沒有正在執行的任務時,那么就回調這個接口,該回調接口需要通過 setIdleCallback 方法傳遞進來。它可以通知當沒有任務正在執行時,通知外界。runningCallsCount 這個是同步執行的任務數和異步執行任務數之和。

通過 promoteCalls() 去執行下一個異步任務

該方法是用于在等待隊列中獲取下一個異步任務去執行。在內部會還是會對 Dispatcher 內部的幾個屬性進行判斷,例如對正在執行的請求數量是否超過了 64 個,還有遍歷等待隊列里的所有的 AsyncCall ,每遍歷出一個 AsycnCall 都校驗它的主機是否有超過 5 個正在執行的異步請求在使用了,在滿足條件的情況下,就馬上會線程池去執行這個任務,以此類推,任務就這樣一個一個的被執行。

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);
    }
    //超過了 64 個請求那么就直接 return
    if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
  }
}

使用 RealCall 實現的同步請求

上面所描述的都是異步請求,現在來看看同步請求。

同步請求調用的是 execute 方法,在內部會調用 client.dispatcher().executed(this); 方法,進去看源碼可知道它實際就是將 RealCall 添加到 Dispatcher 的 runningSyncCalls 中,表示當前正在執行的同步隊列中。在這里使用 Dispacther 的中 execute 僅僅只是將其添加到集合中而已,沒有作別的操作,而真正執行同步任務的核心代碼是 getResponseWithInterceptorChain(); ,該方法負責去網絡請求,并且得到一個響應,具體內部怎么實現日后再分析。在最后的 finally 代碼塊執行的功能跟異步任務一樣,也是通過 Dispatcher 去 finish 該請求。

在 finish 中雖然同步和異步執行的方法是一樣的,但是執行流程并不一樣,異步任務需要通過 promoteCalls 去執行下一個異步任務,而同步請求是不需要的,這個的判斷標記就 private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {...} 就是第三個參數,當該 promoteCalls 為 false 表示同步請求,true 表示異步請求,其他操作都是和異步請求是一樣的。

@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);
  }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容