源碼分析OKHttp的執行過程

OKHttp 是目前 Android 平臺主流的網絡請求的基礎框架。所以咱們有必要對其源碼進行閱讀學習,瞭解其內部的原理、項目結構、以及請求的執行過程。java

它的項目地址爲:github.com/square/okht…git

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 請求操做是很簡單的。有幾個很重要的接口github

  • OKHttpClient : 它表明着 http 客戶端
  • Request:它封裝了請求對象,能夠構造一個 http 請求對象
  • Response:封裝了響應結果
  • Callclient.newCall調用後生成一個請求執行對象Call,它封裝了請求執行過程。

這幾個接口是程序員在使用 OKHttp 庫中常常遇到的。緩存

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

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 是一個準備請求的執行對象,它能夠被取消,表明一個 「請求/響應」 對,不能執行兩次。app

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 中的關鍵的流程是經過攔截器責任鏈來完成的。

相關文章
相關標籤/搜索