1. 程式人生 > 程式設計 >OkHttp(二) - 請求流程分析

OkHttp(二) - 請求流程分析

上一篇文章簡要的介紹了OkHttp的API簡單使用,通過建立了OkHttpClient和Request這些物件就能對遠端請求建立連線,獲取資料。本篇文章將對OkHttp的請求流程做更進一步的深入分析,從原始碼的角度來看看它的請求流程是具體怎麼執行的。

請求方式


請求流程

OkHttp提供了兩種請求方法,分別是同步和非同步。其實兩種方式唯一的區別就在於,非同步方式是將我們的請求放入了一個執行緒池來執行的,其具體的底層請求實現機制都是一樣的。這裡先說一下大概的請求流程,以便在看後續的程式碼分析過程中能保持清晰的思路。

我們的請求都是通過一個OkHttpClient的物件來發起的,這個物件是個入口類,儲存了使用者的配置資訊和一些上下文環境資訊。Call是一個介面,可以抽象成使用者發起的請求呼叫,具體的實現類是RealCall這個物件,Requset就是具體的請求,儲存了你要請求的地址,ip,埠等資訊。

客戶端OkHttpClient發起呼叫請求,首先建立一個RealCall物件,然後根據使用者是選擇同步還是非同步方式來執行不同流程。如果是同步方式,則直接執行,如果是非同步方式,則將請求放入執行緒池來執行,最後通過一個攔截器鏈來完成具體的請求過程並獲取響應。這個攔截器鏈是整個OkHttp框架的核心,我會在後面詳細地介紹,這裡你可以就把它理解為一個執行具體請求的物件即可。用一個圖來描述吧,下圖就是請求流程的過程圖:

下面就深入程式碼來看看兩種不同的執行方式是怎麼實現的

同步請求

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://www.baidu.com"
).build(); Response response = client.newCall(request).execute() 複製程式碼

從OkHpptClient的入口newCall方法說起:

  1. 執行newCall,建立一個具體的請求呼叫物件RealCall
  @Override public Call newCall(Request request) {
    //建立一個具體的請求呼叫物件RealCall
    return new RealCall(this,request,false /* for web socket */);
  }

複製程式碼
  1. RealCall通過excute方法執行具體的請求
@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      //將當前的請求加入runningSyncCalls佇列
      client.dispatcher().executed(this);
      //執行請求獲取響應
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }
複製程式碼

在執行請求前會將當前的call加入一個叫做runningSyncCalls的佇列中,用於請求呼叫過程中的狀態管理和跟蹤處理,RealCall共有三個佇列:

  • readyAsyncCalls
    複製程式碼
    非同步請求準備佇列,當非同步請求數量達到上限,會將請求儲存於此佇列中,待後續處理
  • runningAsyncCalls
    複製程式碼
    當前正在執行的非同步請求佇列,儲存當前正在執行的請求
  • runningSyncCalls
    複製程式碼
    當前正在處理的同步請求佇列
  1. 呼叫攔截器鏈完成請求處理
    Response result = getResponseWithInterceptorChain();
    複製程式碼

上面這段程式碼是整個OkHttp框架中最核心的一句,它內部實現是通過一系列的攔截器組成了攔截器鏈,來完成請求的具體請求過程,包括重試,請求體處理,響應頭處理,響應體處理等邏輯。這裡我們不對它進行展開說明,你只需知道我們的請求是通過一個攔截器鏈來請求和獲取響應即可。上面的流程時序圖如下所示:

非同步請求

呼叫Call物件的excute方法執行同步請求,呼叫enqueue(Callback responseCallback)就會執行非同步請求,該方法需要傳入一個響應回撥介面,用於響應請求執行成功或執行失敗時的操作。

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      //標誌狀態
      executed = true;
    }
    captureCallStackTrace();
    //將請求加入佇列,並通過執行緒池來執行
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
複製程式碼
  1. 請求入隊 通過dispatch物件,將請求加入佇列
      synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      //如果已達到當前最大的處理請求數,則排隊等待
      readyAsyncCalls.add(call);
    }
  }
複製程式碼

首先會判斷是否達到了請求上限,這裡的請求上限包含兩種:一、總的最大併發請求數;二、每個host繫結的最大併發請求數。如果兩個都沒有達到上限則會直接加入執行緒池進行處理。再來看看建立執行緒池的程式碼

  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;
  }
複製程式碼

這裡的執行緒池沒有核心執行緒,也就是說當沒有請求時不會有空閒的執行緒消耗資源,但是最大執行緒數為Integer.MAX_VALUE,似乎與我們平時的一些規範是衝突的。但還記得前面剛說過的請求上限嗎,其實這個上限數就限制了最大能建立的執行緒數,所以不用擔心。

  1. 如果沒有達到請求上限,呼叫執行緒池執行請求
    executorService().execute(call)
複製程式碼

執行的call對應的實現類為AsyncCall,實際上它繼承了Runable()介面,在這個物件的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 {
        //完成請求處理,從runningSyncCalls佇列中移除當前請求物件,從readyAsyncCalls中出隊請求,加入runningSyncCalls,並呼叫執行緒池執行
        client.dispatcher().finished(this);
      }
    }
  }
複製程式碼

具體執行請求的方法與同步請求方式中一樣,都是呼叫攔截器鏈來處理的

Response response = getResponseWithInterceptorChain();
複製程式碼
  1. 如果請求達到請求上限,則加入等待佇列,這個在前面第一步中請求進入佇列時已經說了,參考前面介紹

  2. 每執行完一個請求,從等待佇列中按順序出隊,進入執行緒池執行。在ASyncalCall物件的process方法中,最後都會執行finally方法體,呼叫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;
    }
複製程式碼

具體看看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.
    }
  }
複製程式碼

總結

至此,同步請求和非同步請求的流程就分析完了,從中我們可以看出還是比較簡單,但是請求的具體執行執行流程是怎麼實現的,也就是說怎麼在攔截器處理鏈中處理的,這個是框架最核心的部分,我們還沒有具體分析,在後面的篇幅中我們將重點介紹。