OkHttp 開源庫使用與原始碼解析

Rickon發表於2019-03-09

關於 OkHttp

OkHttp 是一個適用於 Android 和 Java 應用程式的 HTTP+HTTP/2客戶端。

如何使用

Android 開發只需新增依賴,如下:
implementation("com.squareup.okhttp3:okhttp:3.13.1")

官方示例1:獲取一個 url 上的內容並輸出

//Http 客戶端
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
    //構造請求
  Request request = new Request.Builder()
      .url(url)
      .build();
  //執行請求,獲取資料
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}
複製程式碼

官方示例2:給伺服器post資料

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();
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}
複製程式碼

簡單分析一下這段程式碼:這個post請求操作看起來很簡單。但是我們需要學習其中幾個很重要的介面:

OKHttpClient:它代表著 http 客戶端
Request:它封裝了請求物件,可以構造一個 http 請求物件
Response:封裝了響應結果
Call:client.newCall()呼叫後生成一個請求執行物件Call,它封裝了請求執行過程。

下面我們結合這個例子來分析原始碼:

newCall().execute()

跟蹤原始碼後發現這個方法是在 Call 中的介面,程式碼如下:

/**
 * A call is a request that has been prepared for execution. A call can be canceled. As this object
 * represents a single request/response pair (stream), it cannot be executed twice.
 */
public interface Call extends Cloneable {
    //省略部分程式碼
    
    //同步執行請求
    Response execute() throws IOException;
    
    //請求加入佇列
    void enqueue(Callback responseCallback);
    
    //省略部分程式碼
}
複製程式碼

Call 的實現類是 RealCall,繼續追蹤原始碼的 RealCall.java 檔案,可以看到 execute 方法:

@Override public Response execute() throws IOException {
    //同步鎖檢查該請求是否已經執行,如果沒有則標記executed = ture,否則丟擲異常
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    
    captureCallStackTrace();
    timeout.enter();
    //呼叫了回撥方法 callStart
    eventListener.callStart(this);
    try {
      //okhttp 客戶端呼叫 dispatcher 將執行請求物件
      client.dispatcher().executed(this);
      //呼叫了 getResponseWithInterceptorChain 方法獲取到響應資料 Response,後期還會繼續分析
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      e = timeoutExit(e);
      //請求失敗的回撥 callFailed
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
      //使用 dispather 物件呼叫 finished 方法,完成請求
    }
  }
複製程式碼

接下來我們詳細分析一下 dispatcher.execute 和 getResponseWithInterceptorChain 這兩個方法:

跟蹤原始碼 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;
  }

  void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    promoteAndExecute();
  }
  
  private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      idleCallback = this.idleCallback;
    }
    boolean isRunning = promoteAndExecute();
    if (!isRunning && idleCallback != null) {
      idleCallback.run();
    }
  }

}
複製程式碼

發現 Dispatcher 是一個排程器,它的作用是對請求進行分發。它的內部有三個佇列,分別是同步請求進行佇列、非同步請求等待佇列、非同步請求執行佇列。

核心方法: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, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    //開始責任鏈模式
    return chain.proceed(originalRequest);
  }
複製程式碼

此方法構建數個攔截器之後,又構造了一個攔截器責任鏈。跟蹤原始碼 RealInterceptorChain:

RealInterceptorChain

該類主要負責將所有的攔截器串連起來,使所有的攔截器以遞迴的方式進行實現,從而確保只有所有的攔截器都執行完之後才會返回 Response。以下對該類中的主要方法 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.
    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().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }
    //生成下一個攔截器調動該方法的 RealInterceptorChain 物件,其中 index+1 用於獲取下一個攔截器
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    //獲取當前攔截器
    Interceptor interceptor = interceptors.get(index);
    //呼叫該攔截器並獲取返回結果
    Response response = interceptor.intercept(next);

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

總結

以上就是我們對 OkHttp 核心原始碼的分析。當我們發起一個請求的時候會初始化一個 Call 的例項,然後根據同步和非同步的不同,分別呼叫它的 execute() 和 enqueue() 方法。大致過程都是通過攔截器組成的責任鏈,依次經過重試、橋接、快取、連線和訪問伺服器等過程,來獲取到一個響應並交給使用者。

相關文章