1、深刻理解OkHttp:同步、異步請求邏輯

1、前言

開篇陳述:OkHttp做爲一個優秀的網絡請求庫,陪咱們這些Android開發佬走過了許多風風雨雨的夜晚,因此今天特寫一篇深刻理解做文。整個系列篇中,在okhttp3.14.0版本上,依照OkHttp的使用爲引線,作結構以下剖析:java

  1. 同步請求的實現流程。
  2. 異步請求的實現流程。
  3. 重要攔截器:CacheInterceptor 的解析。
  4. 重要攔截器:ConnectInterceptor 的解析。
  5. 重要攔截器:CallServerInterceptor 的解析。

2、同步/異步請求的實現流程

【2.1】 同步請求
public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(json, JSON);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
      //見【2.2】client.newCall()
      //見【2.3】RealCall.execute()
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}
複製代碼

如上是一次同步的網絡請求,主要作以下事情:web

  1. 經過Builder模式構建Request。
  2. 調用client.newCall(),經過request生成一個Call對象。他的實現類是RealCall。
  3. 隨後調用RealCall.execute(),進行同步請求。
【2.2】OkHttpClient.newCall()
OkHttpClient.java
 @Override public Call newCall(Request request) {
    //直接調用Realcall的方法,新建了一個RealCall對象。
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }
  
  RealCall.java
  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
  }
  
 private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    //這裏須要特別注意OkHttpClient對象被Call持有。
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
  }

複製代碼

如上所示,OkHttpClient.newCall() 的調用鏈最終構建了一個 RealCall 對象,而且把client做爲 RealCall 的成員變量,方便後續請求從 client 獲取配置。json

【2.3】RealCall.execute()
RealCall.java
@Override public Response execute() throws IOException {
    synchronized (this) {
      // 若是該請求已經執行過,報錯。
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
      //見【2.4】獲取 client 裏面的調度器 Dispatcher 並記錄這個請求。
      client.dispatcher().executed(this);
      //見【2.5】經過責任鏈的方式將發起請求並返回結果。這裏是同步動做,會阻塞。
      return getResponseWithInterceptorChain();
    } finally {
      //請求完後須要把這個請求從調度器中移走
      client.dispatcher().finished(this);
    }
  }

複製代碼

Call在被執行時作以下事情:設計模式

  1. 判斷Call的合法性。
  2. 將RealCall傳進Client裏面的Dispatcher.executed()裏,而Dispatcher是在 OkHttpClient 被的構建函數裏被建立並做爲成員變量的。
  3. 開啓責任鏈模式,進行請求相關邏輯。
  4. 執行完成後,調度器對這個請求進行收尾工做。
【2.4】Dispatcher.executed()
Dispatcher.java  
 /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */ private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); synchronized void executed(RealCall call) { runningSyncCalls.add(call); } 複製代碼

就是將這次請求加入到一個雙端隊列數據集合中。緩存

【2.5】RealCall.getResponseWithInterceptorChain()
Response getResponseWithInterceptorChain() throws IOException {
    // 將請求的具體邏輯進行分層,並採用責任鏈的方式進行構造。
    List<Interceptor> interceptors = new ArrayList<>();
    // 用戶自已的請求攔截器
    interceptors.addAll(client.interceptors());
    //重試和重定向攔截器
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //橋攔截器
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //緩存邏輯攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //網絡鏈接邏輯攔截器
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    // 網絡請求攔截器,真正網絡通行的地方,這個攔截器處理事後會生成一個Response
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //依照如上配置,構建出一個請求的處理邏輯責任鏈,特別注意:這條鏈開始於下標位置爲的0攔截器。
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      //詳見【2.6】,按下處理邏輯鏈條的開關。
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      //返回請求結果
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }
複製代碼

經過該方法的名字咱們也能夠知道,這個方法就是經過攔截器鏈來得到Response的過程。他作了以下事情:bash

  1. 將用戶自定義的攔截器先加入集合中。
  2. 加入一次請求中須要用的攔截器,這些攔截器表明一次完整的網絡請求邏輯被分了幾層以及他們的前後順序。從代碼中咱們不難看出他們的流程是:重試/重定向邏輯->網絡橋邏輯->緩存邏輯->創建網絡鏈接邏輯->網絡通行邏輯。
  3. 用以上攔截器集合構建出一條邏輯處理的攔截鏈,並將這條鏈須要使用的攔截器下標賦值爲0,從第一個開始。
  4. 調用chain.proceed()啓動這條鏈的處理流程。
    使用責任鏈的設計模式來處理一次網絡請求中的邏輯能夠有效的劃分邏輯層。而前一個攔截器能夠根據實際的處理狀況來決定下一攔截器是否應該繼續處理。

【2.6】RealInterceptorChain.proceed()cookie

RealInterceptorChain.java
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    ...

    /**
    * index+1:構建出新的攔截鏈,不過新的攔截鏈的處理攔截器是下標爲index+1的
    * 實現了責任鏈中,處理邏輯的流轉。
    */
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    //此時index = 0;因此拿到了第一個攔截器,而且調用他的intercept 方法進行具體邏輯處理。
    Interceptor interceptor = interceptors.get(index);
    //當前攔截器對網絡請求進行處理。
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // 省略對response合法性的檢查代碼
    ...

    return response;
  }
複製代碼

總結:網絡

  1. 先index+1,構建index指向下一個攔截器的責任鏈。
  2. 在鏈中取出下標當前index(此時爲0)的攔截器,並調用intercept(next)進行這次攔截器邏輯的真正處理。這裏注意,傳遞進去的參數正是1中構建出來的next責任鏈。在Interceptor.intercept()方法內部進行自身的邏輯處理之後,會調用next.proceed()進行一次傳遞,由下一個攔截器進行處理。而這次請求也是這樣在各個攔截器中被 處理->傳遞->處理->...->返回結果

3、異步請求實現流程

3.1app

public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(json, JSON);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
      //client.newCall():同【2.2】
      //詳見【3.2】RealCall.enqueue()
  try (Response response = client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        }) {
    return response.body().string();
  }
}
複製代碼

如上是一次OkHttp的異步請求使用方法,基本於【2.1】的同步請求一致,惟一不一樣的是,call的異步調用是經過RealCall.enqueue()實現的。而請求結果經過Callback回調到主線程。異步

【3.3】RealCall.enqueue()

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.callStart();
    //詳見【3.4】:AsyncCall()
    //詳見【3.5】:Dispatcher.enqueue()
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
複製代碼

將用戶建立的callback做爲參數傳入AsyncCall()構造函數。AsyncCall 繼承於Runnable.

【3.4】AsyncCall;

AsyncCall.java
final class AsyncCall extends NamedRunnable {
    
    private volatile AtomicInteger callsPerHost = new AtomicInteger(0);

    ...
    /**
     * 該方法是在dispather須要執行此請求的時候,分配給它線程池,此異步請求便在這個線程池中執行網絡請求。
     */
    void executeOn(ExecutorService executorService) {
      ...
      boolean success = false;
      try {
        //異步的關鍵:將請求放到線程池中執行。
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
       ...
       success = false;
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // 執行失敗會經過Dispatcher進行finished,之後不再會用此AsyncCall。
        }
      }
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
        //此處同【2.5】
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
        //請求成功時,回調Response給到用戶
        responseCallback.onResponse(RealCall.this, response);
      } catch (IOException e) {
        ...
        //請求錯誤時,回調錯誤接口給到用戶
          responseCallback.onFailure(RealCall.this, e);
        
      } finally {
       //詳見【3.6】,結束一次請求。
        client.dispatcher().finished(this);
      }
    }
  }
複製代碼

從上面能夠看出,AsyncCall繼承於Runnable,它提供了將Call放到線程池執行的能力,實現了請求的異步流程。

【3.5】Dispatcher.enqueue();

Dispatcher.java
//準備進行異步調用的請求。
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
 //正在執行的異步請求。
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  
void enqueue(AsyncCall call) {
    synchronized (this) {
      //將異步請求加入到雙端隊列中
      readyAsyncCalls.add(call);

      // 尋找是否有同Host的請求,若是有進行復用
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    //【詳見3.7】將符合條件的Ready的異步請求轉入runningAsyncCalls,並執行
    promoteAndExecute();
  }
複製代碼

總結:

  1. 將此請求登記到Dispatcher的預備雙端隊列中。
  2. 以這次的請求的Host來查找可服用的異步請求,若是存在,進行復用。
  3. 嘗試將剛剛加入預備隊的請求執行。

【3.6】Dipatcher.finish()

Dipatcher.java
private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
    ...
    //【詳見3.7】一個請求完成後,檢查此時是否有在等待執行的請求,並處理。
    boolean isRunning = promoteAndExecute();
    if (!isRunning && idleCallback != null) {
      //通知此時已經沒有異步請求任務
      idleCallback.run();
    }
  }
複製代碼

總結:調度器結束一次請求

  1. 當一個異步任務完成後,調度器會觸發一次預備任務執行流程。讓以前由於最大請求數等限制而不能執行的請求有機會獲得執行。
  2. 經過idleCallback.run()通知此時的調度器空閒狀態。

【3.7】Dipatcher.promoteAndExecute()

private boolean promoteAndExecute() {
    ...
    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;
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue;
        
        //知足條件,便把預備隊的請求提高到執行隊列。
        i.remove();
        asyncCall.callsPerHost().incrementAndGet();
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }

    //將可執行的異步請求放進線程池執行
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      //【詳見3.4】
      asyncCall.executeOn(executorService());
    }

    return isRunning;
![](https://user-gold-cdn.xitu.io/2020/6/21/172d4b4a7205d520?w=444&h=446&f=png&s=28383)
  }
複製代碼

總結 :該方法是對預備隊列裏的請求提高至執行隊列並執行的一次嘗試。若是不能執行,他啓動時機將會延後到其餘請求結束(如【3.6】邏輯)。

小篇結:本篇中以Okhttp的用法爲主線,探究了它的同步請求、異步請求的代碼邏輯。而OkHttp最主要的設計模式:責任鏈模式也在其中有涉及到。最後,咱們經過2張圖片來理解同步和異步的整個請求流程(圖片不是本人畫的):

同步請求    
複製代碼

異步請求流程複製代碼
相關文章
相關標籤/搜索