OkHttp 原始碼分析(一)—— 請求流程

空帆船發表於2019-03-12

這篇文章主要梳理一下 OkHttp 的請求流程,對 OkHttp 的實現原理有個整體的把握,再深入細節的實現會更加容易。

建議將 OkHttp 的原始碼下載下來,使用 IDEA 編輯器可以直接開啟閱讀。我這邊也將最新版的原始碼下載下來,進行了註釋,有需要的可以直接從 這裡 下載檢視。

基本使用

我們先看一下 OkHttp 的基本使用。

// 1、建立 Request
Request request = new Request.Builder()
    .get()
    .url("xxx")
    .build(); 

// 2、建立 OKHttpClient
OkHttpClient client = new OkHttpClient();

// 3、建立 Call
Call call = client.newCall(request);

try {
    // 4、同步請求
    Response response = call.execute();
} catch (IOException e) {
    e.printStackTrace();
}

// 5、非同步請求
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {

    }

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

    }
});
複製程式碼

上面的程式碼中,首先構建一個請求 Request 和一個客戶端 OkHttpClient,然後 OkHttpClient 物件根據 request 呼叫 newCall 方法建立 Call 物件,再呼叫 execute 或者 enqueue 方法進行同步或者非同步請求。

接下來我們看一看關鍵類和關鍵流程的具體實現。

Request

Request 類封裝了一次請求需要傳遞給服務端的引數:請求 method 如 GET/POST 等、一些 header、RequestBody 等等。

Request 類未對外提供 public 的建構函式,所以構建一個 Request 例項需要使用構造者模式構建。

Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tags = Util.immutableMap(builder.tags);
}
複製程式碼

OkHttpClient

OkHttpClient 支援兩種構造方式。

一種是預設的構造方式:

OkHttpClient client = new OkHttpClient();
複製程式碼

看一下建構函式:

public OkHttpClient() {
     this(new Builder());
 }
複製程式碼

這裡 OkHttpClient 內部預設配置了一些引數。

OkHttpClient(Builder builder) {...}
複製程式碼

另一種是通過 Builder 配置引數,最後通過 build 方法構建一個 OkHttpClient 物件。

OkHttpClient client = new OkHttpClient.Builder().build();

public OkHttpClient build() {
    return new OkHttpClient(this); // 這裡的 this 是 Builder 例項
}
複製程式碼

我們看一下 OkHttpClient 可配置哪些引數:

final Dispatcher dispatcher;    // 排程器
final @Nullable
Proxy proxy; // 代理
final List<Protocol> protocols;  // 協議
final List<ConnectionSpec> connectionSpecs;  // 傳輸層版本和連線協議
final List<Interceptor> interceptors;  // 攔截器
final List<Interceptor> networkInterceptors;  // 網路攔截器
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector; // 代理選擇器
final CookieJar cookieJar;  // cookie
final @Nullable
Cache cache;  // 快取
final @Nullable
InternalCache internalCache;  // 內部快取
final SocketFactory socketFactory;  // socket 工廠
final SSLSocketFactory sslSocketFactory;  // 安全套接層 socket 工廠,用於 https
final CertificateChainCleaner certificateChainCleaner; // 驗證確認響應證照 適用 HTTPS 請求連線的主機名
final HostnameVerifier hostnameVerifier; // 主機名字驗證
final CertificatePinner certificatePinner; // 證照鏈
final Authenticator proxyAuthenticator; // 代理身份驗證
final Authenticator authenticator; // 本地身份驗證
final ConnectionPool connectionPool;  // 連線池
final Dns dns;  // 域名
final boolean followSslRedirects;  // 安全套接層重定向
final boolean followRedirects;  // 本地重定向
final boolean retryOnConnectionFailure;  // 重試連線失敗
final int callTimeout;
final int connectTimeout;
final int readTimeout;
final int writeTimeout;
final int pingInterval;
複製程式碼

Call

Call 是一個介面,是請求的抽象描述,具體實現類是 RealCall,通過Call.Factory 建立。

public interface Call extends Cloneable {
  // 返回當前請求
  Request request();

  // 同步請求方法
  Response execute() throws IOException;

  // 非同步請求方法
  void enqueue(Callback responseCallback);

  // 取消請求
  void cancel();

  // 請求是否在執行(當execute()或者enqueue(Callback responseCallback)執行後該方法返回true)
  boolean isExecuted();

  // 請求是否被取消
  boolean isCanceled();
  
  Timeout timeout();

  // 建立一個新的一模一樣的請求
  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}
複製程式碼

OkHttpClient 實現了 Call.Factory,負責根據 Request 建立新的 Call:

Call call = client.newCall(request);
複製程式碼

看一下 newCall 方法。

@Override
public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製程式碼

這裡我們發現實際上呼叫了 RealCall 的靜態方法 newRealCall, 不難猜測 這個方法就是建立 Call 物件。

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 的 execute 方法。

@Override public Response execute() throws IOException {
    synchronized (this) {
      // 每個 call 只能執行一次
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
      // 請求開始, 將自己加入到runningSyncCalls佇列中
      client.dispatcher().executed(this);
      // 通過一系列攔截器請求處理和響應處理得到最終的返回結果
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      e = timeoutExit(e);
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      // 請求完成, 將其從runningSyncCalls佇列中移除
      client.dispatcher().finished(this);
    }
  }
複製程式碼

這裡主要做了這幾件事:

  • 檢測這個 call 是否已經執行了,保證每個 call 只能執行一次。
  • 通知 dispatcher 已經進入執行狀態,將 call 加入到 runningSyncCalls 佇列中。
  • 呼叫 getResponseWithInterceptorChain() 函式獲取 HTTP 返回結果。
  • 最後還要通知 dispatcher 自己已經執行完畢,將 call 從 runningSyncCalls 佇列中移除。

這裡涉及到了 Dispatcher 這個類,我們在非同步請求這一節中再介紹。

真正發出網路請求以及解析返回結果的是在 getResponseWithInterceptorChain 方法中進行的。

Response getResponseWithInterceptorChain() throws IOException {
    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, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    
    return chain.proceed(originalRequest);
複製程式碼

getResponseWithInterceptorChain 方法的程式碼量並不多,但是卻完成了所有的請求處理過程。

這裡先是建立了一個 Interceptor 的集合,然後將各類 interceptor 全部加入到集合中,包含以下 interceptor:

  • interceptors:配置 OkHttpClient 時設定的 inteceptors
  • RetryAndFollowUpInterceptor:負責失敗重試以及重定向
  • BridgeInterceptor:負責把使用者構造的請求轉換為傳送到伺服器的請求、把伺服器返回的響應轉換為使用者友好的響應
  • CacheInterceptor:負責讀取快取直接返回、更新快取
  • ConnectInterceptor:負責和伺服器建立連線
  • networkInterceptors:配置 OkHttpClient 時設定的 networkInterceptors
  • CallServerInterceptor:負責向伺服器傳送請求資料、從伺服器讀取響應資料

新增完攔截器後,建立了一個 RealInterceptorChain 物件,將集合 interceptors 和 index(數值0)傳入。接著呼叫其 proceed 方法進行請求的處理,我們來看 proceed方法。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    ...
    // 建立下一個RealInterceptorChain,將index+1(下一個攔截器索引)傳入
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    // 獲取當前的攔截器
    Interceptor interceptor = interceptors.get(index);
    // 通過Interceptor的intercept方法進行處理
    Response response = interceptor.intercept(next);
    ...
    return response;
  }
複製程式碼

我們來看一些關鍵程式碼:

RealInterceptorChain 的 proceed 方法先建立 RealInterceptorChain 的物件,將集合 interceptors 和 index + 1 傳入。從前面的分析知道,初始 index 為 0。

然後獲取當前 index 位置上的 Interceptor,將建立的 RealInterceptorChain 物件 next 傳入到當前攔截器的 intercept 方法中,intercept 方法內部會呼叫 next 的 proceed 方法,一直遞迴下去,最終完成一次網路請求。

所以每個 Interceptor 主要做兩件事情:

  • 攔截上一層攔截器封裝好的 Request,然後自身對這個 Request 進行處理,處理後向下傳遞。
  • 接收下一層攔截器傳遞回來的 Response,然後自身對 Response 進行處理,返回給上一層。

非同步請求

非同步請求呼叫的是 RealCall 的 enqueue 方法。

 public void enqueue(Callback responseCallback) {
     synchronized(this) {
         if (this.executed) {
             throw new IllegalStateException("Already Executed");
         }

         this.executed = true;
     }

     this.captureCallStackTrace();
     this.eventListener.callStart(this);
     this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
 }
複製程式碼

與同步請求一樣,非同步請求也涉及了一個重要的參與者 Dispatcher,它的作用是:控制每一個 Call 的執行順序和生命週期。它內部維護了三個佇列:

  • readyAsyncCalls:等待的非同步請求佇列
  • runningAsyncCalls:正在執行的非同步請求佇列
  • runningSyncCalls:正在執行的同步請求佇列

對於同步請求,由於它是即時執行的, Dispatcher 只需要執行前請求前儲存到 runningSyncCalls,請求結束後從 runningSyncCalls 中移除即可。

對於非同步請求,Dispatcher 是通過啟動 ExcuteService 執行,執行緒池的最大併發量 64,非同步請求先放置在 readyAsyncCalls,可以執行時放到 runningAsyncCalls 中,執行結束從runningAsyncCalls 中移除。

我們看一下具體實現細節,下面是 Dispatcher 的 enqueue 方法,先將 AsyncCall 新增到 readyAsyncCalls。

void enqueue(AsyncCall call) {
  // 將AsyncCall加入到準備非同步呼叫的佇列中
  synchronized (this) {
    readyAsyncCalls.add(call);
  }
  promoteAndExecute();
}
複製程式碼

再看 promoteAndExecute 方法:

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    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; // Max capacity.
            if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

            i.remove();
            executableCalls.add(asyncCall);
            runningAsyncCalls.add(asyncCall);
        }
        isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
        AsyncCall asyncCall = executableCalls.get(i);
        asyncCall.executeOn(executorService());
    }

    return isRunning;
}
複製程式碼

這裡主要的工作有:

  • 從準備非同步請求的佇列中取出可以執行的請求(正在執行的非同步請求不得超過64,同一個host下的非同步請求不得超過5個),加入到 executableCalls 列表中。
  • 迴圈 executableCalls 取出請求 AsyncCall 物件,呼叫其 executeOn 方法。
void executeOn(ExecutorService executorService) {
    assert (!Thread.holdsLock(client.dispatcher()));
    boolean success = false;
    try {
        executorService.execute(this);
        success = true;
    } catch (RejectedExecutionException e) {
        InterruptedIOException ioException = new InterruptedIOException("executor rejected");
        ioException.initCause(e);
        eventListener.callFailed(RealCall.this, ioException);
        responseCallback.onFailure(RealCall.this, ioException);
    } finally {
        if (!success) {
            client.dispatcher().finished(this); // This call is no longer running!
        }
    }
}
複製程式碼

可以看到 executeOn 方法的引數傳遞的是 ExecutorService 執行緒池物件,方法中呼叫了執行緒池的 execute方法,所以 AsyncCall 應該是實現了 Runnable 介面,我們看看它的 run 方法是怎樣的。

AsyncCall 繼承自 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();
}
複製程式碼

所以當執行緒池執行 execute 方法會走到 NamedRunnable 的 run 方法,run 方法中又呼叫了 抽象方法 execute,我們直接看 AsyncCall 的 execute 方法。

@Override
protected void execute() {
    boolean signalledCallback = false;
    timeout.enter();
    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) {
        e = timeoutExit(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);
    }
}
}
複製程式碼

這裡我們又看到了熟悉的 getResponseWithInterceptorChain 方法。

這樣看來,同步請求和非同步請求的原理是一樣的,都是在 getResponseWithInterceptorChain() 函式中通過 Interceptor 鏈條來實現的網路請求邏輯。

總結

以上便是 Okhttp 整個請求與響應的具體流程,OkHttp 流程圖如下。

OkHttp 原始碼分析(一)—— 請求流程

簡述 OkHttp 的請求流程:

  • OkHttpClient 實現了 Call.Fctory,負責為 Request 建立 Call。
  • RealCall 是 Call 的具體實現,它的非同步請求是通過 Dispatcher 排程器利用 ExcutorService 實現,而最終進行網路請求時和同步請求一樣,都是通過 getResponseWithInterceptorChain 方法實現。
  • getResponseWithInterceptorChain 方法中採用了責任鏈模式,每一個攔截器各司其職,主要做兩件事。
    • 攔截上一層攔截器封裝好的 Request,然後自身對這個 Request 進行處理,處理後向下傳遞。
    • 接收下一層攔截器傳遞回來的 Response,然後自身對 Response 進行處理,返回給上一層。

參考

相關文章