OKHTTP異步和同步請(qǐng)求簡(jiǎn)單分析

OKHTTP異步和同步請(qǐng)求簡(jiǎn)單分析
OKHTTP攔截器緩存策略CacheInterceptor的簡(jiǎn)單分析
OKHTTP攔截器ConnectInterceptor的簡(jiǎn)單分析
OKHTTP攔截器CallServerInterceptor的簡(jiǎn)單分析
OKHTTP攔截器BridgeInterceptor的簡(jiǎn)單分析
OKHTTP攔截器RetryAndFollowUpInterceptor的簡(jiǎn)單分析
OKHTTP結(jié)合官網(wǎng)示例分析兩種自定義攔截器的區(qū)別

同步請(qǐng)求就是執(zhí)行請(qǐng)求的操作是阻塞式,直到 HTTP 響應(yīng)返回。它對(duì)應(yīng) OKHTTP 中的 execute 方法。

異步請(qǐng)求就類似于非阻塞式的請(qǐng)求,它的執(zhí)行結(jié)果一般都是通過接口回調(diào)的方式告知調(diào)用者。它對(duì)應(yīng) OKHTTP 中的 enqueue 方法。

示例代碼

下面的代碼演示了如何進(jìn)行同步和異步請(qǐng)求的操作。

OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
        .url("http://www.qq.com")
        .build();
Call call = okHttpClient.newCall(request);
//1.異步請(qǐng)求,通過接口回調(diào)告知用戶 http 的異步執(zhí)行結(jié)果
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.同步請(qǐng)求
//Response response = call.execute();
//if (response.isSuccessful()) {
//    System.out.println(response.body().string());
//}

異步請(qǐng)求的基本原理

OKHTTP異步任務(wù)執(zhí)行圖解.png

Call

負(fù)責(zé)準(zhǔn)備去執(zhí)行一個(gè) request 請(qǐng)求,一個(gè) call 只能負(fù)責(zé)去執(zhí)行一個(gè)請(qǐng)求,不能被執(zhí)行兩次。因?yàn)?OkHttpClient 是實(shí)現(xiàn)了 Call.Factory 因此它具備創(chuàng)建 Call 對(duì)象的功能,內(nèi)部創(chuàng)建的就是 RealCall 對(duì)象。Call 是封裝 request 的,它表示一個(gè)可以執(zhí)行的請(qǐng)求。

Call 的實(shí)現(xiàn)類 RealCall

因?yàn)?Call 是接口,內(nèi)部定義了同步與異步的請(qǐng)求,以及取消請(qǐng)求等操作,這些操作是由 RealCall 真正去實(shí)現(xiàn)的。

在 RealCall 中關(guān)鍵的幾個(gè)屬性:

  • client 就是我們?cè)谕饨鐒?chuàng)建的 OkHttpClient 對(duì)象,通過這個(gè) client 就是調(diào)用 Dispatcher 去分發(fā)請(qǐng)求任務(wù)。

  • executed 它是 boolean 類型,上面介紹 Call 時(shí)已經(jīng)說明了,一個(gè) Call 只能被執(zhí)行一次,在內(nèi)部就是通過這個(gè)屬性進(jìn)行判斷的。

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

Dispatcher 相關(guān)知識(shí)點(diǎn)

異步任務(wù)分發(fā)器,它會(huì)內(nèi)部指定線程池去執(zhí)行異步任務(wù),并在執(zhí)行完畢之后提供 finish 方法結(jié)束異步請(qǐng)求之后從等待隊(duì)列中獲取下一個(gè)滿足條件的異步任務(wù)去執(zhí)行。

1、在 Dispatcher 有幾個(gè)比較重要的屬性,這幾個(gè)屬性會(huì)影響異步請(qǐng)求的執(zhí)行。

  • int maxRequests = 64 會(huì)去指定并發(fā) call 的最大個(gè)數(shù)。

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

  • executorService 就是執(zhí)行異步任務(wù)的線程池,在內(nèi)部中已經(jīng)指定好了線程池,當(dāng)然也可以在 Dispacther 中通過構(gòu)造方法去指定一個(gè)線程池。

  • Deque<AsyncCall> readyAsyncCalls 表示在隊(duì)列中已經(jīng)準(zhǔn)備好的請(qǐng)求。

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

  • Deque<RealCall> runningSyncCalls 正在運(yùn)行的同步請(qǐng)求,包括已經(jīng)取消的請(qǐng)求(還沒有執(zhí)行finish操作的請(qǐng)求。)

2、關(guān)于 Dispatcher 的功能在下面的異步和同步請(qǐng)求中我們?cè)僖灰惶剿鳌?/p>

Call 的實(shí)現(xiàn)者 RealCall

它具備有異步和同步請(qǐng)求,還有取消請(qǐng)求的功能,它內(nèi)部有一個(gè) AsyncCall 內(nèi)部類,在 Dispatcher 中分發(fā)的異步請(qǐng)求任務(wù)就是 AsyncCall 。這里分發(fā)的任務(wù)指的是異步任務(wù),而不是同步任務(wù)。AsyncCall 就是用表示一個(gè)異步任務(wù)的,在 Dispatcher 內(nèi)部有維護(hù)了兩個(gè)隊(duì)列來存儲(chǔ) AsyncCall,分別是 readyAsyncCalls 和
runningAsyncCalls 它們分別表示準(zhǔn)備要執(zhí)行的 AsyncCall 隊(duì)列和正在執(zhí)行的 AsycnCall 隊(duì)列。當(dāng)然還有一個(gè) runningSyncCalls 這個(gè)隊(duì)列,但是它適用于存放 RealCall ,也就是用于存儲(chǔ)同步請(qǐng)求的任務(wù)。

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

合理性的校驗(yàn)操作,我們?cè)诮榻B Dispacther 的相關(guān)屬性時(shí)已經(jīng)說明,在 OKHTTP 中正在執(zhí)行的請(qǐng)求不能超過 64 個(gè),并且同一個(gè)主機(jī)不能超過 5 請(qǐng)求,當(dāng)滿足這兩個(gè)條件,即可將任務(wù)添加到正在執(zhí)行的隊(duì)列 runningAsyncCalls 中,并且通知線程池安排線程去執(zhí)行這個(gè)異步任務(wù),否則就會(huì)被添加到等待隊(duì)列中 readyAsyncCalls。

synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    //當(dāng)正在執(zhí)行的請(qǐng)求小于64個(gè)&&該 call 對(duì)應(yīng)的主機(jī)少于5個(gè) Call 時(shí)
    //將任務(wù)添加到 runningAsycnCalls 中,標(biāo)記為正在執(zhí)行的任務(wù)。
    runningAsyncCalls.add(call);
    //在線程池中執(zhí)行這個(gè)任務(wù)。
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

真正的異步執(zhí)行者 AsyncCall

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

當(dāng)線程池執(zhí)行這個(gè)異步任務(wù)時(shí),那么該 Runnable 的 run 方法就會(huì)被執(zhí)行,我們查閱了源碼,在 run 方法內(nèi)部會(huì)去調(diào)用 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);
  }
}

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

  • 得到 HTTP 請(qǐng)求的響應(yīng) Response :Response response = getResponseWithInterceptorChain();得到一個(gè) response 響應(yīng)。(這個(gè)是一個(gè)遞歸的調(diào)用過程,具體在其他博客中再分析具體實(shí)現(xiàn)。)

  • 給調(diào)用進(jìn)行接口回調(diào)異步任務(wù)執(zhí)行的結(jié)果:responseCallback.onResponse 或者 responseCallback.onFailure

  • 結(jié)束該請(qǐng)求,并且執(zhí)行下一個(gè)等待的異步任務(wù):client.dispatcher().finished(this);

對(duì)于 AsyncCall 中所做的這 3 步中,前面兩步都比較好理解,下面主要看看它是如何結(jié)束一個(gè)請(qǐng)求的并且開啟下一個(gè)異步請(qǐng)求的?

我們?cè)谇懊娼榻B Dispacther 已經(jīng)了解了它的作用,這里再次強(qiáng)調(diào)一下,它是負(fù)責(zé)去分發(fā)一個(gè)異步任務(wù)給指定的線程池去執(zhí)行,并且可以在執(zhí)行完畢之后去等待隊(duì)列中獲取下一個(gè)請(qǐng)求去執(zhí)行。

在 AsyncCall 中的 execute 中執(zhí)行一個(gè)異步請(qǐng)求,注意在 finally 塊內(nèi)部調(diào)用了 client.dispatcher().finished(this);它的作用是通知 Dispatcher 我的任務(wù)執(zhí)行完畢了,你可以將我從集合中移除了,開啟下一個(gè)異步任務(wù)吧。下面就是 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();
  }
}

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

  • 從 calls 中移除該 AysyncCall 對(duì)象,而這個(gè) calls 就是 Dispatcher 中的 runningAsyncCalls。

  • promoteCalls() 調(diào)用該方法就可以實(shí)現(xiàn)從等待隊(duì)列中取出下一個(gè)異步任務(wù)去執(zhí)行。

  • idleCallback.run(); 沒有正在執(zhí)行的任務(wù)時(shí),那么就回調(diào)這個(gè)接口,該回調(diào)接口需要通過 setIdleCallback 方法傳遞進(jìn)來。它可以通知當(dāng)沒有任務(wù)正在執(zhí)行時(shí),通知外界。runningCallsCount 這個(gè)是同步執(zhí)行的任務(wù)數(shù)和異步執(zhí)行任務(wù)數(shù)之和。

通過 promoteCalls() 去執(zhí)行下一個(gè)異步任務(wù)

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

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) {
      //滿足下一個(gè)要執(zhí)行的任務(wù)的要求。
      i.remove();
      //添加到正在請(qǐng)求隊(duì)列中
      runningAsyncCalls.add(call);
      //由線程池去執(zhí)行這個(gè)任務(wù)。
      executorService().execute(call);
    }
    //超過了 64 個(gè)請(qǐng)求那么就直接 return
    if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
  }
}

使用 RealCall 實(shí)現(xiàn)的同步請(qǐng)求

上面所描述的都是異步請(qǐng)求,現(xiàn)在來看看同步請(qǐng)求。

同步請(qǐng)求調(diào)用的是 execute 方法,在內(nèi)部會(huì)調(diào)用 client.dispatcher().executed(this); 方法,進(jìn)去看源碼可知道它實(shí)際就是將 RealCall 添加到 Dispatcher 的 runningSyncCalls 中,表示當(dāng)前正在執(zhí)行的同步隊(duì)列中。在這里使用 Dispacther 的中 execute 僅僅只是將其添加到集合中而已,沒有作別的操作,而真正執(zhí)行同步任務(wù)的核心代碼是 getResponseWithInterceptorChain(); ,該方法負(fù)責(zé)去網(wǎng)絡(luò)請(qǐng)求,并且得到一個(gè)響應(yīng),具體內(nèi)部怎么實(shí)現(xiàn)日后再分析。在最后的 finally 代碼塊執(zhí)行的功能跟異步任務(wù)一樣,也是通過 Dispatcher 去 finish 該請(qǐng)求。

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

@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);
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容