天天看點

okhttp 源碼解析(一)前言基本使用方法源碼分析總結

前言

本篇源碼分析介紹的是同步請求和異步請求的執行流程以及排程器的相關知識。如果需要學習攔截器鍊相關知識的,可以直接跳過本篇直接到下一篇進行學習。

基本使用方法

首先要明确一點,

okhttp

使用的是 Builder 模式來進行我們網絡請求的各種參數設定的,接下來我們先舉個簡單的例子來看看

okhttp

的同步請求和異步請求是如何使用的。我們首先來看看同步請求的例子:

同步請求舉例

/**
 * 同步請求的簡單舉例
 */
public static void SyncRequest(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 1. 建構 OkHttpClient
            OkHttpClient client = new OkHttpClient.Builder()
                    .readTimeout(8, TimeUnit.SECONDS)
                    .connectTimeout(8, TimeUnit.SECONDS)
                    .build();
            // 2. 建構 Request 對象
            Request request = new Request.Builder()
                    .get()
                    .url("https://www.baidu.com")
                    .build();

            try {
                // 3. 通過 newCall 方法建立 Call 對象
                Call call = client.newCall(request);
                //4. 通過調用 call 的 execute 方法執行同步請求
                Response response = call.execute();
                Log.d(TAG, response.body().string());
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }).start();
}
           

這裡我們隻簡要地進行一個設定,主要是說明使用步驟。

  1. 首先,我們要構造出一個

    OkHttpClient

    對象,它可以說是我們整個流程的一個核心,我們通過 Builder 模式對它進行參數設定,如讀寫逾時、連接配接逾時時間和緩存設定等,這裡我隻簡單地對讀逾時和連接配接逾時進行了設定。
  2. 其次,我們需要構造出

    Request

    對象,它用于設定我們的請求參數,同樣它也是通過 Builder 模式進行建立的。
  3. 建構完

    Request

    對象後,我們就可以調用

    OkHttpClient

    newCall

    方法,這個方法會将

    Request

    對象封裝成一個

    Call

    類型的對象。
  4. 最後就是通過調用

    Call

    execute

    方法來執行同步請求了,我們這裡通過一個

    Response

    類型的變量來接收請求得到的資料。

在這裡我們需要注意一點的是,我們的同步請求是放在子線程中去執行的,具體原因很簡單:同步請求在沒有得到響應的時候,會阻塞線程,而我們的主線程是不允許有耗時操作的,是以我們在這裡需要開辟一個子線程來執行這個耗時操作。

講完了同步請求的流程步驟,接下來我們來看看異步請求的例子。

異步請求舉例

/**
 * 異步請求的簡單舉例
 */
public static void AsyncRequest(){
    // 1. 建構 OkHttpClient
    OkHttpClient client = new OkHttpClient.Builder()
            .readTimeout(8, TimeUnit.SECONDS)
            .connectTimeout(8, TimeUnit.SECONDS)
            .build();
    // 2. 建構 Request 對象
    final Request request = new Request.Builder()
            .get()
            .url("https://www.baidu.com")
            .build();
    // 3. 通過 newCall 方法建立 Call 對象
    Call call = client.newCall(request);
    // 4. 通過調用 call 的 enqueue 方法執行異步請求
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            Log.d(TAG, e.getMessage());
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            Log.d(TAG, response.body().string());
        }
    });
}
           

可以看到,異步請求和同步請求的設定其實有非常多的相似之處,它們的唯一不同點在于第4步的調用方法上,同步方法調用的是

execute

,而異步調用的則是

enqueue

方法。

enqueue

方法會接收一個

Callback

對象,這個對象會回調兩個關鍵方法:

onFailure

onResponse

onFailure

會在我們請求失敗的時候進行回調,而

onResponse

方法則是在得到響應後進行回調。

這裡同樣需要注意一點,所謂的異步,也就是不會阻塞我們的線程的意思,是以它實際上是在内部自己開辟了一個子線程來接收回調方法的,是以我們的

onFaliure

方法和

onResponse

方法都是位于我們的子線程中,在我們需要執行 UI 的相關操作時,應當先在方法内部切換回主線程進行操作。

接下來我們通過一張圖來對同步和異步的使用做一個簡單的總結:

okhttp 源碼解析(一)前言基本使用方法源碼分析總結

說完了

okhttp

兩種基本的使用方法,接下來我們便要從執行流程來一步步分析

okhttp

的源碼了。

源碼分析

本着先易後難的原則,筆者會首先對同步請求的執行流程進行分析,然後再對異步請求的執行流程進行分析,由于

okhttp

的源碼中涉及的重點如排程器器和攔截器等都是非常大的一塊知識,是以筆者會将執行的流程順序進行梳理,然後在梳理完畢之後再介紹排程器器和攔截器等的知識點。接下來我們來看看同步請求的執行流程。

同步請求的執行流程

通過上面的講解,我們知道了,在同步請求的時候,我們首先會通過

OkHttpClient.Builder()

來對

OkhttpClient

對象進行構造,我們首先來看看

Builder

裡面是什麼:

public Builder() {
    dispatcher = new Dispatcher();   // 排程器
    protocols = DEFAULT_PROTOCOLS;
    connectionSpecs = DEFAULT_CONNECTION_SPECS;
    eventListenerFactory = EventListener.factory(EventListener.NONE);
    proxySelector = ProxySelector.getDefault();
    cookieJar = CookieJar.NO_COOKIES;
    socketFactory = SocketFactory.getDefault();
    hostnameVerifier = OkHostnameVerifier.INSTANCE;
    certificatePinner = CertificatePinner.DEFAULT;
    proxyAuthenticator = Authenticator.NONE;
    authenticator = Authenticator.NONE;
    connectionPool = new ConnectionPool();  // 連接配接池
    dns = Dns.SYSTEM;
    followSslRedirects = true;
    followRedirects = true;
    retryOnConnectionFailure = true;
    connectTimeout = 10_000;  // 連接配接逾時時間
    readTimeout = 10_000;     // 讀逾時時間
    writeTimeout = 10_000;    // 寫逾時時間
    pingInterval = 0;
  }
           

Builder

OkHttpClient

的一個靜态内部類,可以看到它的構造方法初始化了非常非常多的成員變量,我們可以通過調用相應的方法來設定這些成員變量的值。然後通過調用

build

方法,就可以傳回一個構造完畢的

OkHttpClient

的對象了,

build

的源碼如下所示:

public OkHttpClient build() {
      return new OkHttpClient(this);
}
           

由于這個方法是

Builder

類中的方法,是以

this

所指的毫無疑問就是我們構造好的

Builder

對象了,通過傳入這個構造好的對象,我們就可以構造出我們所需的

OkhttpClient

對象了。

好了,在了解完

OkHttpClient

對象是如何構造出來之後,第二步就是構造

Request

對象了,它也是使用的 Builder 模式建構對象的,是以我們也來看看它的

Builder

裡面有什麼:

public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
}
           

可以看到在它的内部的

Builder

構造方法中,預設的請求方法是

get

請求,然後還會建立一個請求頭。最後也是通過

build

方法構造出我們需要的

Request

對象。

構造完

Request

對象後,接下來就到了我們的第三部,将

Request

對象封裝成

Call

對象。它是通過調用

OkHttpClient

newCall

方法實作的,我們來看看這個方法裡面做了什麼:

/**
  * Prepares the {@code request} to be executed at some point in the future.
  */
 @Override public Call newCall(Request request) {
   return RealCall.newRealCall(this, request, false /* for web socket */);
 }
           

在這裡我們可以看到,這個方法傳回的是

RealCall

newRealCall

方法,在這裡需要先說明,

Call

是一個接口,它的實作類就是

RealCall

,我們接下來來看看

RealCall

newRealCall

方法做了什麼吧!

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
}
           

可以看到這個方法構造了一個

RealCall

對象,然後對這個對象設定了事件監聽,我們繼續往下看看

RealCall

的構造方法做了什麼:

private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
           

這個構造方法會将我們之前構造好的

OkHttpClient

對象和

Request

對象儲存起來,然後我們關鍵看到代碼第5行,這是一個重試和失敗重定向攔截器,我們後面會對

okhttp

的攔截器鍊機制進行分析,我們在這裡知道對它進行了初始化就可以了。

在知道

newCall

方法最終傳回的是一個

RealCall

對象之後,我們的同步請求就剩下最後一個調用方法

execute

了,這也是同步請求的核心方法。它的源碼如下所示:

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  try {
    client.dispatcher().executed(this);
    Response result = getResponseWithInterceptorChain();  // 發生阻塞的地方
    if (result == null) throw new IOException("Canceled");
    return result;
  } catch (IOException e) {
    eventListener.callFailed(this, e);
    throw e;
  } finally {
    client.dispatcher().finished(this);
  }
}
           

在這個方法中,首先會在同步塊中對

executed

進行判斷,它主要是用于判斷我們目前的網絡請求是否被執行過了,因為一個網絡請求隻能被執行一次,如果我們的網絡請求已經被執行過,就會抛出異常。

接下來我們看到代碼第9行,這裡我們調用了

OkHttpClient

dispatcher

方法,我們來看看這個方法裡面做了什麼:

public Dispatcher dispatcher() {
    return dispatcher;
}
           

可以看到,它就是簡單地傳回了我們的在

OkHttpClient

中的

Dispatcher

對象,它是一個排程器,用于控制并發的請求,我們的請求排程都是通過這個對象來完成的,它的源碼分析我們會留在後面。在得到

Dispatcher

對象後,我們接下來就是調用它的

executed

方法,它的源碼如下所示:

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

可以看到這是一個同步方法,它會将我們的

RealCall

對象通過

add

方法添加到

runningSyncCalls

這個對象當中,我們來看看它是什麼類型的對象:

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
           

可以看到這個對象是一個用于存放

RealCall

的隊列,通過注釋我們可以得知,當我們有産生同步請求時,就會将請求放置在這個隊列中。分析到這裡,我們知道了,同步請求的

executed

會首先将我們的請求添加到

runningSyncCalls

這個隊列中。接下來讓我們繼續回到

executed

方法往下看,我們接着建立了一個

Response

對象,通過

getResponseWithInterceptorChain

方法來接收我們的請求響應,

getResponseWithInterceptorChain

是我們

okhttp

分析的另一個重要的核心,我們同樣留在後面再做分析,這裡我們隻需要知道通過這個方法我們能夠擷取響應資料就可以了。在取得響應後,最後我們一定會執行

Dispatcher

finished

方法,

finished

方法的源碼如下所示:

/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
  finished(runningSyncCalls, call, false);
}
           

可以看到它調用的是另一個

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

在這個方法中,我們看到它内部的同步塊,在代碼第5行,首先判斷能否移除這次已經執行結束的同步請求,如果不能就會抛出異常。接下來的

if

語句

promoteCalls

變量由于我們傳進來的是

false

,是以不會執行

promoteCalls

方法。跳過這個方法繼續往下看,在代碼第7行,我們會通過

runningCallsCount

計算現在正在執行的請求數,我們可以看看這段代碼的源碼:

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

可以看到它傳回的是

runningAsuncCalls

runningSyncCalls

這兩個隊列的大小之和,這兩個隊列分别存放着正在執行的異步和同步請求,是以通過傳回它們的大小也就能知道現在到底有多少請求正在執行了。

分析完這個方法後我們繼續回到

finished

方法的第11行,如果我們正在執行的請求數為0,也就是不存在請求了并且

idleCallback

不為空的話,就會執行

idleCallback

run

方法。

至此,我們的同步請求流程就分析完了,可以看到我們的同步請求流程還是比較簡單的,我們來對同步請求的執行流程做一個小總結:

  • 同步請求首先構造出

    OkHttpClient

    對象和

    Request

    對象,設定好相應的參數。它們都是通過 Builder 模式進行構造的,其中

    Request

    對象的預設請求方法是

    get

  • 緊接着通過

    OkhttpClient

    newCall

    方法将

    Request

    對象封裝成

    Call

    對象,

    Call

    是一個接口,它的實作類是

    RealCall

  • 最後便是調用

    Call

    execute

    方法了,實際上也就是調用了

    RealCall

    execute

    方法,這個方法會首先判斷目前請求是否被執行過,如果已經被執行過就會抛出異常,緊接着會執行排程器

    Dispatcher

    executed

    方法,将

    Call

    對象添加到同步請求隊列

    runningSyncCalls

    中。
  • 擷取的響應資料

    Response

    對象是通過

    getResponseWithInterceptorChain

    方法來獲得的。
  • 在執行完請求之後,無論是否抛出異常,都一定會執行

    finished

    方法來将

    runningSyncCalls

    隊列中已執行完畢的同步請求

    Call

    對象移除。

我們接下來先來分析我們的異步請求的執行流程。

異步請求的執行流程

在我們前面的例子中,我們已經知道了,同步和異步的差別僅僅在第4步的調用方法上:同步調用的是

execute

方法,而異步則是調用

enqueue

方法,前3步二者都是一樣的,是以在這裡的異步請求執行流程我們就直接從

enqueue

方法講起。

Call

enqueue

方法需要傳入一個

Callback

對象,我們首先來看看這個參數是怎麼定義的:

public interface Callback {

  void onFailure(Call call, IOException e);

  void onResponse(Call call, Response response) throws IOException;
}
           

可以看到非常簡單的一個接口,定義了兩個方法,分别對應于我們請求失敗和請求成功的情況,這個接口的作用是回調。接下來我們就來看看

enqueue

方法的内部代碼,前面提到過了

Call

是個接口,是以

enqueue

方法的實作應該在它的實作類

RealCall

裡面,

RealCall

中的

enqueue

代碼如下所示:

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

可以看到它其實和我們同步請求調用的

execute

方法非常相似,都是首先在同步塊中判斷這個請求是否被執行過了,如果為

true

就會抛出異常。接下來就是它和

execute

方法的不同點了,我們看到代碼第8行,它通過

OkHttpClient

獲得

Dispatcher

對象,然後再通過

Dispatcher

對象調用

enqueue

方法。

Dispatcher

enqueue

方法接收一個

AsyncCall

類型的參數,在

AsyncCall

中會對我們的

Callback

對象進行包裝,我們首先來看看這個

AsyncCall

是何方神聖:

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

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

可以看到它内部維護了一個

Callback

對象,我們傳進來的

Callback

在這裡被儲存了起來,我們還看到它是繼承自

NamedRunnable

的,我們來繼續看看

NamedRunnable

裡面是什麼:

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

是一個抽象類,它是實作了

Runnable

的。在它的

run

方法中,調用了

execute

方法,而

execute

方法我們在代碼第18行可以看到是一個抽象方法,因為它是在

run

方法中被執行的,是以它就是在子線程中被執行的,那麼接下來我們便回到

NamedRunnable

的父類

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 {
      eventListener.callFailed(RealCall.this, e);
      responseCallback.onFailure(RealCall.this, e);
    }
  } finally {
    client.dispatcher().finished(this);
  }
}
           

我們首先看到代碼第4行,在這裡我們依然是通過

getResponseWithInterceptorChain

方法來獲得我們的響應資料

Response

getResponseWithInterceptorChain

方法這裡後面做講解,我們現在隻需要知道通過這個方法我們能擷取我們想要的資料就行了。接下來看到第5行的

if

判斷,這裡我們通過

retryAndFollowUpInterceptor

做了一個判斷來決定回調哪個方法。

retryAndFollowUpInterceptor

前面我們已經說過了是一個重試和失敗重定向攔截器,它的

isCanceled

方法如果傳回

true

,說明攔截器鍊被關閉了,那麼我們也就無法獲得響應了,關于它的知識我們會留在後面一篇部落格進行講解。是以如果這個

if

判斷為

true

,那麼我們就會回調我們

Callback

對象的

onFailure

方法,否則就會回調我們

Callback

對象的

onResponse

方法。而在代碼第18行我們可以看到,如果抛出異常,也會回調

onFailure

方法。

最後看到

finally

塊中調用的

finished

方法,既然是放在

finally

中的,那就一定會被執行,我們來看看它裡面做了什麼:

void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
}
           

可以看到它調用的是另一個

finished

方法,這個方法和我們在同步請求中調用的是同一個

finished

方法,不同點是第一個參數異步請求傳進來的是

runnigAsyncCalls

隊列,并且第三個參數傳進來的是

true

。我們再次來看看這個方法:

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

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

首先在同步塊中判斷能否移除這個已經執行完畢的異步

Call

請求,不能移除就會抛出異常。緊接着由于

promoteCalls

參數我們傳進來的值是

true

,那麼在異步中我們就會執行

promoteCalls

方法了,這個方法涉及到了我們

Dispatcher

排程器的内容,我們同樣是留在後面做講解,它的作用是在任務執行完之後,手動清除掉線程池中的緩存區,并把等待中的請求添加到執行隊列中去,我們先記住這一點就可以了。接下來就是通過判斷正在執行的請求數來決定是否調用

idleCallback

run

方法了,這個在前面講解同步請求的時候已經介紹過了,是以也就不再贅述了。

總結一下

AsyncCall

execute

方法,它的作用就是在新開辟的子線程中,擷取響應,然後根據不同的情況來決定回調

Callback

onFailure

或者

onResponse

方法。這也印證了我們之前的注意事項,在

onFaliure

onResponse

方法中操作 UI 需要先調回主線程再進行操作。在執行完目前的異步請求之後,會調用

promoteCalls

方法手動清理線程池中的緩存區,并把等待中的請求添加到執行隊列中去。

分析完了

AsyncCall

execute

方法,我們的

AsyncCall

就分析到這裡了。

AsyncCall

的重點也就是它的

execute

方法,要記住它是運作在

run

方法中的。分析完了

Dispatcher

中的

enqueue

方法的參數

AsyncCall

,我們接下來就來看看

Dispatcher

enqueue

方法裡面做了什麼吧:

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

可以看到這是一個同步方法,在這段代碼中,我們先來看看

runningAsyncCalls

readyAsyncCalls

的含義:

/** 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<>();
           

從注釋中我們可以得知,

readyAsyncCalls

是一個用于存放異步請求的等待隊列,而

runningAsyncCalls

則是一個用存放正在執行異步請求的隊列,這個正在執行也包括那些未執行完但被我們取消了的請求。知道了這些資訊,我們繼續回到

enqueue

方法,看到

enqueue

方法的第2行,在這個

if

判斷中,如果

runningAsyncCalls

的大小小于

maxRequests

并且

runningCallsForHost

方法的傳回值小于

maxRequestsPerHost

時,我們的

AsyncCall

對象就會被添加到

runningAsyncCalls

隊列中,否則

AsyncCall

對象就會被添加到

readyAsyncCalls

隊列中。

我們來分析這個

if

判斷,

maxRequests

的預設值為64,也就是說當異步請求正在執行的個數小于64的時候,第一個條件成立。而

maxRequestsPerHost

的 預設值為5,

runningCallsForHost

方法傳回目前請求的

host

共享的請求數,如果不超過5個,那麼第二個條件就成立了。也就是說,當正在執行的異步請求個數少于64個并且目前請求的

host

請求數不超過5個時,我們的

AsyncCall

對象就會被直接添加到

runningAsyncCalls

隊列中,否則就會添加到

readyAsyncCalls

隊列中。

if

條件成立後,我們除了将

AsyncCall

對象添加到

runningAsyncCalls

隊列中外,我們還執行了

executorService().execute(call)

語句,我們首先來看看

executorService

方法裡面做了什麼:

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

可以看到這個方法傳回的是一個線程池,緊接着這個傳回的線程池就會調用它的

execute

方法來執行我們的

AsyncCall

對象的線程的

run

方法,我們之前分析過了

AsyncCall

方法的線程執行的

run

方法其實就是執行

AsyncCall

execute

方法,在它裡面會根據不同的情況回調我們

Callback

對象的

onFailure

方法或

onResponse

方法。是以千萬要記住一句話:

onFailure

onResponse

方法都是位于子線程中的方法,不要在裡面直接進行 UI 操作!

到這裡,我們的異步請求的執行流程分析就結束了。我們來對異步請求的執行流程做一個小總結:

  • 首先異步請求調用的

    enqueue

    方法内部會判斷這個請求是否被執行過了,如果是執行過的就會抛出異常,因為每個請求隻能被執行一次。
  • 接着就會通過排程器來執行

    Dispatcher

    enqueue

    方法,這個方法中我們會傳入

    Callback

    對象的包裝類

    AsyncCall

    的對象,然後通過判斷我們的異步請求應當添加到

    runningAsyncCalls

    隊列中直接執行還是

    readyAsyncCalls

    隊列中進行等待,如果是直接執行,那麼就會線上程池中執行我們的請求。
  • 線上程池中執行請求實際上會調用到

    AsyncCall

    中的

    execute

    方法,這個方法會根據不同情況決定回調

    Callback

    對象的

    onFailure

    方法還是

    onResponse

    方法,是以這兩個方法是在子線程中執行的,不要在裡面直接操作 UI !!!
  • 在執行完請求之後,無論是否抛出異常,都一定會執行

    finished

    方法來将

    runningAsyncCalls

    隊列中的請求對象移除。

排程器 Dispatcher

我們在前面的源碼分析中,一直跳過的排程器部分,就要在這裡進行分析了。從前面的源碼中我們也能感受的到,無論是同步請求還是異步請求,在執行的方法(

execute

/

enqueue

)中總會通過擷取到的

Dispatcher

對象的

execute

enqueue

方法來執行我們的網絡請求。是以

okhttp

的請求管理,也就是通過

Dispatcher

對象來排程的了。首先我們來看到

Dispatcher

這個類的代碼:

public final class Dispatcher {
  ......
  /** Executes calls. Created lazily. */
  private @Nullable 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;
  }
  ......
}
           

可以看到,這個類的内部定義了一個線程池

ExecutorService

,還有3個隊列,分别是

readyAsyncCalls

runningAsyncCalls

runningSyncCalls

。這些我們在之前的分析中都有介紹過,在這裡我們再來對它們進行一次介紹,因為它們太重要了。

首先是線程池

ExecutorService

,它可以通過構造方法傳入我們自定義的線程池進行構造或者通過預設的線程池進行構造,從注釋中我們知道它是通過懶漢模式加載的,也就是用到它的時候它才會被執行個體化。它是通過

executorService

方法執行個體化的,這個方法代碼如下:

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

這個方法在之前我們粗略地介紹了一下,我們在這裡仔細地看看它是如何構造出一個線程池的。在代碼第3行,可以看到建立了一個線程池,傳進的前4個參數分别為 0,

Integer.MAX_VALUE

,60 和

TimeUnit.SECONDS

。因為這裡涉及到的是線程池的知識,不懂的同學請自行搜尋相關知識進行學習,這裡不會進行細緻的介紹。

這個線程池的核心線程數為 0,最大線程數為

Integer

的最大值,也就是說理論上我們可以建立出無限多個線程,但事實上它會受到我們其它變量的限制,是以我們實際上無法建立出無限多的線程出來。

緊接着兩個參數設定的是非核心線程閑置的逾時時間,在這裡的設定是 60s。而我們的線程池中所有線程均為非核心線程,也就是說,當我們的線程線上程池中超過 60s 沒有被執行的時候,它就會被回收掉。

介紹完了線程池,我們接下來看到三個隊列:

  • readyAsyncCalls:異步等待隊列,當異步請求無法添加到異步執行隊列中時,就會被添加到這個隊列中來。
  • runningAsyncCalls:異步執行隊列,在這個隊列中的異步請求不但包括了正在執行的請求,也包括未執行完成但被取消了的請求。
  • runningSyncCalls:同步執行隊列,在這個隊列中的同步請求不但包括了正在執行的請求,也包括未執行完成但被取消了的請求。

在簡單分析完 Dispatcher 這個類的幾個重要的成員之後,我們依然分為同步請求和異步請求的排程來進行分析。

同步請求的排程

我們在這裡就直接看到同步請求的

execute

方法:

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

這段代碼我們之前已經分析過了,接下來我們站在

Dispatcher

的角度來進行分析。我們直接看到代碼第9行,調用

Dispatcher

executed

方法,這個方法的代碼如下:

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

這個方法非常的簡單,就是直接将

Call

對象添加到了

runningSyncCalls

這個同步執行隊列中,表明它正在被執行,緊接着當執行結束之後,我們一定會調用到第17行

Dispatcher

finished

方法,我們重新來看看這個方法裡面幹了什麼:

/** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }

  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

方法,在這個方法中,我們看到代碼第10行,我們會在這裡從

runningSyncCalls

隊列中移除我們的

Call

對象,因為它已經執行完了嘛。如果無法移除了,那麼它就會抛出異常。是以到這裡,我們就把同步請求中排程器對

Call

從添加到移除的流程介紹完了,接下來介紹更為複雜的異步請求排程過程。

異步請求的排程

在異步請求中,我們也是直接看到我們的

enqueue

方法:

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

我們直接看到代碼第8行,在這裡我們執行了

Dispatcher

enqueue

方法,它的代碼如下:

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

這段代碼我們在之前同樣也進行過分析,我們在這裡重新在

Dispatcher

的角度進行分析。首先

if

判斷語句會判斷正在執行的異步請求線程數是否小于 64 以及目前請求的主機請求數是否小于 5,如果都滿足的話,那麼就會将請求直接添加到

runningAsyncCalls

隊列中,并且将請求交由線程池進行處理,否則就會被添加到

readyAsyncCalls

隊列中進行等待。線程池中處理的

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 {
      eventListener.callFailed(RealCall.this, e);
      responseCallback.onFailure(RealCall.this, e);
    }
  } finally {
    client.dispatcher().finished(this);
  }
}
           

這段代碼我們之前也是分析過,我們這裡同樣隻分析和

Dispatcher

相關的部分,我們直接看到代碼第21行的

finally

塊中,在這裡的時候我們的請求已經執行完畢了,是以接下來就會調用

Dispatcher

finished

方法:

/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
  finished(runningAsyncCalls, call, true);
}
           

它内部調用的

finished

和同步請求的調用的是同一個

finished

方法,差別是它的第一個參數傳入的是異步執行隊列

runningAsyncCalls

,第三個參數傳入的是

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

同樣的,在同步塊中,我們會先将執行完的

Call

移除,由于我們傳進的

promoteCalls

值為

true

,是以在異步請求中

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

這個方法主要是用于将異步等待隊列中的請求排程到異步執行隊列中。在代碼第2行的

if

判斷中,首先判斷異步執行隊列

runningAsyncCalls

的數量是否超過了最大值,如果是就直接傳回,因為這意味着我們的異步執行隊列沒有空間留給我們等待中的請求了。接下來看到代碼第3行的

if

判斷,如果異步等待隊列為空,也是直接傳回,這也好了解,如果沒有等待中的請求了,也就不需要排程了。

接下來就到了代碼第5行的

for

循環了,這是整段代碼的關鍵。在這個

for

循環中,我們會周遊異步等待隊列

readyAsyncCalls

的請求,然後在執行隊列

runningAsyncCalls

有空閑位置的時候就會将等待隊列中的

Call

移除,然後添加到執行隊列中去。也就是在這裡,

okhttp

實作了對異步等待隊列中的請求的排程。對于異步請求的排程,到這裡也就分析完了。

在最後,我們對排程器做個小總結:

  • 在同步請求中,排程器在同步請求執行前将其添加到同步隊列

    runningSyncCalls

    中,在執行結束後從隊列中将它移除。
  • 在異步請求中,排程器首先會判斷異步執行隊列

    runningAsyncCalls

    是否未滿以及請求的主機請求數是否未超過5個,如果這2個條件均滿足就會直接将請求添加到

    runningAsyncCalls

    中并線上程池中執行這個請求,當請求執行結束後,排程器會通過

    promoteCalls

    方法将

    readyAsyncCalls

    隊列中的請求排程到

    runningAsyncCalls

    中去執行。

總結

本次源碼分析主要是對同步請求和異步請求的異同、執行流程和排程器

Dispatcher

做了一個分析,相對而言還是比較簡單的。對于本篇遺留下來的知識點攔截器鍊,會在後面的系列文章進行講解。祝大家學習愉快!有問題的話可以在下面評論區給我留言。