OkHttp 源碼解析(一):基本流程

簡介

OkHttp 是一款用於 Android 和 Java 的網絡請求庫,也是目前 Android 中最火的一個網絡庫。OkHttp 有不少的優勢:java

  • 在 HTTP/2 上容許對同一個 host 的請求共同一個 socket
  • 鏈接池的使用減小請求延遲(若是 HTTP/2 不支持)
  • 透明的 GZIP 壓縮減小數據量大小
  • 響應的緩存避免重複的網絡請求

以前寫過一篇 Retrofit 源碼解析,Retrofit 底層其實就是用的 OkHttp 去請求網絡。本文分析 OKHttp 的源碼,主要是針對一次網絡請求的基本流程,源碼基於 OKHttp-3.8.0web

基本用法

下面是 OkHttp 的使用示例:segmentfault

OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
                .url(url)
                .build();
// 同步
Response response = client.newCall(request).execute();
// 異步
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

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

首先是建立一個 OkHttpClient 對象,其實用過的應該知道能夠用 new OkHttpClient.Builder().build() 的方式來配置 OkHttpClient 的一些參數。有了 OkHttpClient 以後,下面是建立一個 Request 對象,這個對象也是經過 Builder 模式來生成,其中能夠配置一些與這條請求相關的參數,其中 url 是必不可少的。在發送請求的時候,須要生成一個 Call 對象,Call 表明了一個即將被執行的請求。若是是同步請求,調用 execute 方法。異步則調用 enqueue,並設定一個回調對象 Callback緩存

下面就一步步分析發送一條網絡請求的基本流程。cookie

OkHttpClient、Request 及 Call 的建立

OkHttpClient 的建立採用了 Builder 模式,能夠配置 Interceptor、Cache 等。能夠設置的參數不少,其中部分參數以下:網絡

final Dispatcher dispatcher;  // 請求的分發器
  final @Nullable Proxy proxy; // 代理
  final List<Protocol> protocols;  // http協議
  final List<ConnectionSpec> connectionSpecs; 
  final List<Interceptor> interceptors;
  final List<Interceptor> networkInterceptors;
  final EventListener.Factory eventListenerFactory;
  final ProxySelector proxySelector;
  final CookieJar cookieJar;
  final @Nullable Cache cache;

Request 與 OkHttpClient 的建立相似,也是用了 Buidler 模式,可是其參數要少不少:併發

public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
...
}

參數的含義都很明確,即便 Http 協議的url、header、method 以及 body 部分。變量 tag 用於標識一條 Request,可用於發送後取消這條請求。異步

client.newCall(request) 生成一個 Call 對象。Call 其實是一個接口,它封裝了 Request,而且用於發起實際的網絡請求。下面是 Call 的所有代碼:socket

public interface Call extends Cloneable {

  Request request();

  Response execute() throws IOException;

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

其中包含了與網絡請求相關的操做,包括髮起、取消等。看一下 OkHttpClient 是如何建立 Call 的:async

@Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

從代碼能夠看到,其實是建立了一個 RealCall 對象,它也是 Call 的惟一一個實現類。

有了 RealCall 對象後,就能夠發起網絡請求了,能夠是同步請求(execute)或者是異步請求(enqueue)。異步請求涉及到 Dispatcher,先從相對簡單的同步請求開始分析。

同步請求

調用 RealCall#execute() 便是發起同步請求,代碼以下:

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

首先判斷這條請求是否是已經執行過,若是是則會拋出異常(一條請求只能執行一次,重複執行能夠調用 Call#clone())。接着執行了 client.dispatcher().executed(this),這行代碼是把當前的 Call 加入到 Dispatcher 的一個隊列中,這個暫時能夠忽略,後面會分析 Dispatcher。

下面一行 Response result = getResponseWithInterceptorChain() 是關鍵,在 getResponseWithInterceptorChain 中真正執行了網絡請求並得到 Response 並返回。(下一小節具體分析其中的邏輯)

最後在 finally 中調用 Dispatcher 的 finished,從隊列中移除這條請求。

攔截器 Interceptor

getResponseWithInterceptorChain 的代碼以下:

Response getResponseWithInterceptorChain() throws IOException {
   // Build a full stack of interceptors.
   List<Interceptor> interceptors = new ArrayList<>();
   interceptors.addAll(client.interceptors());
   interceptors.add(retryAndFollowUpInterceptor);
   interceptors.add(new BridgeInterceptor(client.cookieJar()));
   interceptors.add(new CacheInterceptor(client.internalCache()));
   interceptors.add(new ConnectInterceptor(client));
   if (!forWebSocket) {
     interceptors.addAll(client.networkInterceptors());
   }
   interceptors.add(new CallServerInterceptor(forWebSocket));

   Interceptor.Chain chain = new RealInterceptorChain(
       interceptors, null, null, null, 0, originalRequest);
   return chain.proceed(originalRequest);
 }

能夠看到,其中建立了一個 List 用於添加 Interceptor。首先添加的是 client 中的 interceptors,也就是在建立 OkHttpClient 對象時自定義的 interceptors,而後依次添加 retryAndFollowUpInterceptor(重試及重定向)、BridgeInterceptor(請求參數的添加)、CacheInterceptor(緩存)、ConnectInterceptor(開始鏈接)、用戶自定義的 networkinterceptorsCallServerInterceptor(發送參數並讀取響應)。從這裏能夠知道,OkHttp 默認添加了好幾個 interceptor 用於完成不一樣的功能。

在研究各個 interceptor 以前,須要考慮一下如何讓這些攔截器一個接着一個的執行?繼續看上面的代碼,在添加了各類 interceptors 以後,建立了一個 RealInterceptorChain 對象。(它的構造函數須要的參數不少,而且這些參數涉及到鏈接池、請求數據的發送等。因爲這篇文章主要分析 OkHttp 的基本流程,因此暫時略過這部分)RealInterceptorChain 是接口 Chain 的實現類,Chain 的意思,其做用是把各個 Interceptor 串起來依次執行。在得到了 RealInterceptorChain 以後調用其 proceed 方法,看名字就能知道是讓 Request 請求繼續執行。

下面具體分析 RealInterceptorChain,它有以下的成員變量:

private final List<Interceptor> interceptors;  // 攔截器
  private final StreamAllocation streamAllocation; // 流管理器
  private final HttpCodec httpCodec; // http流,發送請求數據並讀取響應數據
  private final RealConnection connection;  // scoket的鏈接
  private final int index; // 當前攔截器的索引
  private final Request request; // 當前的請求
  private int calls; // chain 的 proceed 調用次數的記錄

其中 streamAllocationhttpCodecconnection 都與 socket 鏈接有關,後續文章再分析。看一下 proceed 方法:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    // 若是已經有了一個流,確保即將到來的 request 是用它
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    // 若是已經有了一個流,確保這是對 call 惟一的調用
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }


    // Call the next interceptor in the chain.         
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);                           // (1)
    Interceptor interceptor = interceptors.get(index); // (2)
    Response response = interceptor.intercept(next);   // (3)

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

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

剛開始作了一些鏈接方面的判斷,須要關注的是標了(1)、(2)、(3)的幾行,主要作了如下操做:

  1. 建立新的 RealInterceptorChain,其中 index 加1用於標識當前的攔截器
  2. 經過 index 獲取當前的攔截器
  3. 調用下一個攔截器的 intercept 方法,並把上面生成的新的 RealInterceptorChain 對象 next 傳進去

由以前的 getResponseWithInterceptorChain 方法能夠知道,當前 RealInterceptorChain 的 interceptors 的第一個是 RetryAndFollowUpInterceptor,下面是其 intercept 的代碼:

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response = null;
      boolean releaseConnection = true;
      try {
        // 調用 chain 的 proceed
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        ... // 省略部分代碼,主要是錯誤重試以及重定向
    }
  }

這個 Interceptor 主要用於出錯重試以及重定向的邏輯,其中省略了部分代碼。在這個方法當中要關注的是再次調用了 chainproceed 方法,這裏的 chain 是以前新建立的 next 對象。至關於說經過調用 Chain#proceed() 將網絡請求推向下一個攔截器(proceed 中會獲取下一個 Interceptor 並調用其 intercept 方法),而且獲得 response 對象,而下一個攔截器也是相似的操做。因而,多個 interceptors 就經過這種方式串起來依次執行,而且前一個 Interceptor 能夠獲得後一個 Interceptor 執行後的 response 從而進行處理。

經過不一樣的 Interceptor,OkHttp 實現了不一樣的功能。各個 Inercept 職責分明又不會互相耦合,而且能夠很是方便的添加 Interceptor,這是 責任鏈 模式的體現,很是優雅的設計。如今能夠發現 OkHttp 中的攔截器的調用過程以下圖所示:

圖片描述

異步請求

相比於同步請求,異步請求主要是增長了 Dispatcher 的處理。Dispatcher 是請求的分發器,它有一下的成員變量:

private int maxRequests = 64;   // 最大鏈接數
private int maxRequestsPerHost = 5;  // 單個 host 最大鏈接數
private @Nullable Runnable idleCallback;   // 空閒時的回調

/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService; // 線程池

/** Ready async calls in the order they'll be run. */
// 準備執行的異步 Call 的隊列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
// 正在執行的的異步 Call 的隊列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
// 正在執行的同步 Call 的隊列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

在 Dispatcher 中,默認支持的最大併發鏈接數是64,每一個 host 最多能夠有5個併發請求。

下面看一下線程池 executorService 的建立。線程池會在兩個地方建立,分別是 Dispatcher 的構造函數或者是 executorService 方法中(若是調用了默認的構造函數):

// 默認構造函數沒有建立
public Dispatcher() {
}
// 自定義線程池
public Dispatcher(ExecutorService executorService) {
    this.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;
  }

Dispatcher 支持自定義的線程池,不然會默認建立一個。在生成 OkHttpClient 對象時,默認調用的是 Dispatcher 無參的構造方法。這個默認線程池經過 `new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,

new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false))` 建立,看上去相似於一個 CachedThreadPool,沒有常駐的 core 線程,空閒線程60秒後自動關閉。

enqueue

每一個 Call 被添加到某一個隊列,若是是同步請求添加到 runningSyncCalls 中:

synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
}

異步請求添加的邏輯以下:

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

具體步驟是:

  1. 判斷是否超出總共的最大鏈接數以及單個 host 的最大鏈接數
  2. 若是沒有則添加到 runningAsyncCalls 而且提交到線程池執行
  3. 不然添加到 readyAsyncCalls 等待後續執行

須要注意的是異步請求的 Call 不是原始的 Call,而是被包裝爲 AsyncCall

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }
    ...
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        // 調用 getResponseWithInterceptorChain
        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);
      }
    }
  }

AsyncCall 繼承自 NamedRunnable,它其實就是一個爲線程設置了名字的 Runnable,在其 Run 中調用 execute,因此 AsyncCall 的主要邏輯都寫在 execute 中。能夠看到最終仍是調用了 getResponseWithInterceptorChain 方法,因此後續執行網絡請求的邏輯是同樣的。在得到 response 以後,就能夠調用 responseCallback 返回最終的信息。

finished

在上面的代碼中,finally 裏面執行了 client.dispatcher().finished(this),在同步請求 RealCall#execute() 中也有相似的一行代碼。finished 的做用是讓 Dispatcher 從隊列中移除已完成的 Call,對於異步請求還會從 readyAsyncCalls 中取出等待中的請求提交給線程池。下面是具體代碼:

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

  /** 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();
    }
  }
    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();
      // 找到一個等待隊列中的 Call,符合鏈接數要求時加入 runningAsyncCalls 並提交給線程池執行。
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

有兩個重載的 finished 方法均調用了另外一個 pirvate 的 finished,區別在於這個 finished 的最後一個參數 promoteCalls。對於同步請求(參數爲 RealCall) promoteCallsfalse,而異步請求(參數爲 AsyncCall) promoteCallstrue。 pirvate 的 finished 主要是從隊列中移除 Call,異步請求會執行 promoteCallspromoteCalls 裏面主要是從 readyAsyncCalls 取出一個 Call,若是知足最大鏈接數的要求,則把這個 Call 加入 runningAsyncCalls 並提交給線程池執行。

經過 runningAsyncCallsreadyAsyncCalls,Dispatcher 實現了異步請求的調度執行。這裏比較巧妙的方式是在 finally 中去執行 readyAsyncCalls 中的請求,避免了 wait/notity 的方式,避免了代碼的複雜性。

總結

OkHttp 的基本執行流程以下圖所示:

圖片描述

主要是如下步驟:

  1. OkHttpClient 調用 newCall 建立 RealCall 對象,Call 封裝了 Request,表明一條即將執行的請求。
  2. 根據同步仍是異步請求分別調用 RealCallexecuteenqueue 方法,將Call 加入 Dispatcher 的相應隊列中。最終,同步或異步請求都會調用 getResponseWithInterceptorChain
  3. getResponseWithInterceptorChain 中,OkHttp 添加用戶自定義以及默認的 inceptors,並用一個 Chain 管理並依次執行每一個 Interceptor。
  4. 每一個 Interceptor 調用 Chain#proceed() 將請求發送給下一級的 Inceptor,並能經過這個方法得到下一級 Interceptor 的 Response。因此上圖所示,Request 一級級地往下傳遞,而獲取了網絡的 Response 以後一級級地往上傳遞。

OkHttp中一條網絡請求的基本流程就是這樣,下一篇文章介紹 OkHttp 如何創建鏈接:OkHttp 源碼解析(二):創建鏈接

若是個人文章對您有幫助,不妨點個贊支持一下(^_^)

相關文章
相關標籤/搜索