原始碼分析OKHttp的執行過程

GoT陽仔發表於2018-10-27

OKHttp 是目前 Android 平臺主流的網路請求的基礎框架。因此我們有必要對其原始碼進行閱讀學習,瞭解其內部的原理、專案結構、以及請求的執行過程。

它的專案地址為:github.com/square/okht…

0x00 簡單使用

先從一個簡單的官方示例來看,這是一個同步 GET 請求

public class GetExample {
  //1.http客戶端
  OkHttpClient client = new OkHttpClient();
    
  String run(String url) throws IOException {
    //2.構造請求
    Request request = new Request.Builder()
        .url(url)
        .build();
	//3.執行請求,獲取響應資料
    try (Response response = client.newCall(request).execute()) {
      return response.body().string();
    }
  }

  public static void main(String[] args) throws IOException {
    GetExample example = new GetExample();
    String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
    System.out.println(response);
  }
}
複製程式碼

可以看出這個 GET 請求操作是很簡單的。有幾個很重要的介面

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

這幾個介面是程式設計師在使用 OKHttp 庫中經常遇到的。

接下來將從這個示例開始閱讀 OkHttp 的原始碼

0x01 Call.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

Call 的實現類是 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 = ture,否則丟擲異常
  • 呼叫了回撥函式callStart
  • okhttp客戶端呼叫dispatcher 將執行請求物件
  • 呼叫了getResponseWithInterceptorChain 方法獲取到響應資料Response,這個方法很重要,後面會繼續跟進
  • 然後是對請求失敗的回撥callFailed
  • 最後還是使用dispather物件呼叫finished方法,完成請求

這裡的邏輯還是比較清晰的,出現兩個重要的方法

  1. dispatcher.execute
  2. getResponseWithInterceptorChain

接下來分別看這兩個方法

0x02 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<>();
  //...

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

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

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

可以看出Dispatcher 是一個排程器,它內部有一個執行緒池executorService ,還有三個佇列,分別代表同步請求進行佇列、非同步請求等待佇列、非同步請求執行佇列。

我們發現呼叫execute方法時就是將Call物件加入到同步請求進行佇列runningSyncCalls中,而呼叫finished 方法則是將Call請求從佇列中移除

0x03 getResponseWithInterceptorChain

現在在回到RealCall 原始碼中,這個方法可以說是OkHttp最關鍵的部分了

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()));//處理cookie的攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));//處理快取的攔截器
    interceptors.add(new ConnectInterceptor(client));//負責連線的攔截器
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());//新增程式設計師自定義的network攔截器
    }
    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 包含了所有的攔截器物件。然後呼叫chain.proceed方法開始執行請求,這時就到了RealInterceptorChain 這個類中。

0x04 RealInterceptorChain

@Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }

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

    calls++;

    //省略無關程式碼...

    //1. 執行攔截器責任鏈中的下一個攔截器
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    //2. 獲取當前的攔截器
    Interceptor interceptor = interceptors.get(index);
    //3. 執行攔截,並返回響應
    Response response = interceptor.intercept(next);

    //省略...

    return response;
  }
複製程式碼

可以看到,在proceed方法,又構造了RealInterceptorChain並且呼叫了interceptor.intercept方法,

而這個方法中又會呼叫next.proceed方法,直至返回response。這個過程有點像遞迴呼叫。

0x05 Interceptor

攔截器,它是一個介面,內部還有一個Chain介面

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    @Nullable Connection connection();

    Call call();

    int connectTimeoutMillis();

    Chain withConnectTimeout(int timeout, TimeUnit unit);

    int readTimeoutMillis();

    Chain withReadTimeout(int timeout, TimeUnit unit);

    int writeTimeoutMillis();

    Chain withWriteTimeout(int timeout, TimeUnit unit);
  }
}
複製程式碼

所有的攔截器都需要實現這個介面。

0x06 非同步的情況

public final class AsynchronousGet {
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();
	//呼叫enqueue方法,並設定回撥介面
    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
       	//這裡獲取到響應結果資料
      }
    });
  }
複製程式碼

然後我們再看RealCall中的enqueue方法

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //最終執行了dispatcher的enqueue方法
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
複製程式碼

其實是執行了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中通過執行緒池來執行AsyncCall物件,因此跟進到AsyncCall中的execute方法

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

發現最終還是執行了getResponseWithInterceptorChain,因此不管是同步還是非同步、最終的流程還是一樣。

0x07 總結

  1. OKHttpClient

這是一個 http 客戶端。構建很簡單,可以使用無參建構函式。其內部是通過 Builder 物件進行構建的。也可以通過其內部靜態類 Builder 來構建,然後通過 builder 設定 OkHttpClient 構造引數。

  1. Request

請求物件。其內部也是使用 Builder 模式封裝了構造的過程,通過Builder使用鏈式呼叫也是目前很多開源庫中常見的模式。

  1. Response

響應結果。客戶端執行後返回響應結果,通過 Response 可以很方便的獲取到響應資料。

  1. Call

請求執行。可以執行同步或者非同步的請求,分別將請求傳送到dispatcher

  1. Dispatcher

排程器。其內部有一個執行緒池,並維護了三個佇列:同步進行請求佇列、非同步請求等待佇列、非同步請求進行佇列。

還有兩個重要的方法executeenqueue方法,分別代表同步、非同步的方法。這兩個方法的最終的執行流程都是一樣的

  1. Interceptor

攔截器。攔截器在OKHttpClient中使是用責任鏈模式來實現的。Okhttp 中的關鍵的流程是通過攔截器責任鏈來完成的。

相關文章