OkHttp3.0-原始碼分析

1004145468發表於2018-09-01

1. OkHttp官網介紹:

OkHttp: An HTTP+HTTP/2 client for Android and Java applications. 該庫支援 HTTP1.0、HTTP1.1、HTTP2.0 以及 SPDY ,都在類Protocol 中宣告。

public enum Protocol {
  /**
   * An obsolete plaintext framing that does not use persistent sockets by default.
   */
  HTTP_1_0("http/1.0"),

  /**
   * A plaintext framing that includes persistent connections.
   *
   * <p>This version of OkHttp implements <a href="https://tools.ietf.org/html/rfc7230">RFC
   * 7230</a>, and tracks revisions to that spec.
   */
  HTTP_1_1("http/1.1"),

  /**
   * Chromium's binary-framed protocol that includes header compression, multiplexing multiple
   * requests on the same socket, and server-push. HTTP/1.1 semantics are layered on SPDY/3.
   *
   * <p>Current versions of OkHttp do not support this protocol.
   *
   * @deprecated OkHttp has dropped support for SPDY. Prefer {@link #HTTP_2}.
   */
  SPDY_3("spdy/3.1"),

  /**
   * The IETF's binary-framed protocol that includes header compression, multiplexing multiple
   * requests on the same socket, and server-push. HTTP/1.1 semantics are layered on HTTP/2.
   *
   * <p>HTTP/2 requires deployments of HTTP/2 that use TLS 1.2 support {@linkplain
   * CipherSuite#TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256} , present in Java 8+ and Android 5+. Servers
   * that enforce this may send an exception message including the string {@code
   * INADEQUATE_SECURITY}.
   */
  HTTP_2("h2");
}
複製程式碼

2. OkHttp的基本使用:

(1) Step One: 構建OkHttpClient物件

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

(2) Step Two: 構建Request物件

Request request = new Request.Builder().url("xxxxx").build();

(3) Step Three: 通過上兩步建立的物件生成Call

Call newCall = okHttpClient.newCall(request);

(4) Step Four: 使用Call傳送非同步或同步請求,獲取Response物件。

 //  同步請求:
Response response = newCall.execute();

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

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

           }
       });
複製程式碼

(5) 取消網路請求:

newCall.cancel();

(6) 注意事項:

  1. OkHttp傳送非同步請求,CallBack回撥依舊執行在子執行緒,所以不能直接進行UI更新操作。
  2. 同一個Call只能執行一次同步或者非同步網路請求。

3. OkHttp流程圖

okhttp網路請求流程圖.PNG
從整體來看,我們通過構建OkHttpClient物件,並呼叫其newCall (Request) 方法生成一個真正用於執行網路請求的Call例項。call.execute()進行同步網路請求,call.enqueue()進行非同步網路請求。但不管是同步還是非同步,在網路請求前,先將這個請求放入到dispatcher的請求佇列中,然後getResponseWithInterceptorChain()來鏈式呼叫各攔截器(如下圖所示)獲取Response物件,最後將這次請求從佇列中移除。

請求鏈式呼叫流程圖.PNG

4. 核心程式碼分析

4.1 OkHttpClient建立(Builder模式):

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

    public Builder() {
      dispatcher = new Dispatcher(); 
       ...
      followRedirects = true;
      retryOnConnectionFailure = true;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }
複製程式碼
    public OkHttpClient build() {
      return new OkHttpClient(this);
    }
複製程式碼
OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
   ...
}
複製程式碼

其中在Builder構造器內建立了Dispatcher物件,最終通過Builder.build()將Dispatcher傳遞給OkHttpClient,所以我們一定要記住OkHttpClient中存在Dispatcher物件。當然,既然OkHttpClient採用Builder模式建立例項,就允許我們以鏈式呼叫的方式對OkHttpClient進行配置,正如下面所示,但這不是本文關注的重點。

  • connectTimeout() 設定連線超時時間
  • cache() 設定快取檔案並配置快取大小
  • addInterceptor() 新增應用層攔截器(請求鏈式呼叫流程圖.PNG 圖中 “自定義應用層攔截器”)
  • addNetworkInterceptor() 新增網路層攔截器(請求鏈式呼叫流程圖.PNG 圖中 “自定義網路層攔截器”)
  • ...
4.2 Request建立(Builder模式):

Request request = new Request.Builder().url("xxxxx").build();

和上面建立OkHttpClient一樣,依舊Builder模式允許使用者靈活配置請求。

  • url() 新增網路請求地址
  • addHeader() 新增網路請求頭資訊
  • cacheControl() 設定本次請求的快取方式
  • get() post() put() delete() ... 設定請求的方式,支援restful風格
4.3 Call物件生成:

Call newCall = okHttpClient.newCall(request);

OkHttpClient.class 中的newCall() 方法:

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

RealCall.class 中的newRealCall() 方法:

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    ... 
    return call;
  }
複製程式碼

到此處為止,我們通過OkHttpClient.newCall(Request)生成一個newRealCall物件,這個物件包含了OkHttpClient和Request引用,所以我們完全可以在RealCall類中做剩餘工作了,而事實也正是如此。

4.4 開始同步(非同步)網路請求:

RealCall 的同步方法:

  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");  // 這裡可以解釋為什麼每個call只能執行一次。
      executed = true;
    } 
    try {
      client.dispatcher().executed(this);           // 將RealCall存到之前強調的OkHttpClient的dispatcher中
      Response result = getResponseWithInterceptorChain(); // 真正向伺服器傳送網路請求的程式碼,後面會具體說明。
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) { }
      finally {
      client.dispatcher().finished(this);     // 執行請求後將RealCall從dispatcher中移除
    }
  }
複製程式碼

同步請求,通過dispatcher對RealCall儲存和移除邏的輯相當簡單,只是維護了一個集合用於管理。

Dispatcher.class中的同步請求的新增和移除方法:

public final class Dispatcher {
  ...
   // 同步請求集合
   private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

   // 新增同步請求
   synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
   }
 
   // 移除同步請求
   void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
   }

   private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    ...
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();  // 同步請求,此處不會被呼叫。
    }
    ...
   }
  ...
 }
複製程式碼

RealCall的非同步方法:

 @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");  // 這裡可以解釋為什麼每個call只能執行一次。
      executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(responseCallback));  // AsyncCall為RealCall的內部類,實現Runnable介面,用於執行緒池的排程。
  }
複製程式碼

先不講client.dispatcher().enqueue(xx)具體程式碼實現,我們先看一下AsyncCall的結構。

final class RealCall implements Call {
    ... 
    final class AsyncCall extends NamedRunnable {
        AsyncCall(Callback responseCallback) {
           super("OkHttp %s", redactedUrl());
           this.responseCallback = responseCallback;
        }
        
       @Override protected void execute() { ... }
    }
}
複製程式碼
 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();
}
複製程式碼

AsyncCall作為RealCall的內部類,AsyncCall引用RealCall的例項物件,同時AsyncCall實現了Runnable介面,一旦開始執行就會呼叫AsyncCall 的execute()方法。知道了AsyncCall 的基本結構,就可以看client.dispatcher().enqueue(new AsyncCall(responseCallback)) 內部具體實現了。

public final class Dispatcher {

 private int maxRequests = 64;  // 同時進行的非同步網路請求最大數

  private int maxRequestsPerHost = 5;  // 同一個網路請求主機地址允許最大請求個數

  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();  // 非同步請求快取佇列

  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();  // 非同步請求執行佇列

  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;
  }

  synchronized void enqueue(AsyncCall call) {   // 新增非同步請求。
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {  // 判斷能否放入非同步網路請求執行佇列
      runningAsyncCalls.add(call);  // 將非同步請求新增到執行佇列
      executorService().execute(call);  // 執行非同步網路請求
    } else {
      readyAsyncCalls.add(call); // 將非同步請求新增到等待佇列
    }
  }

 ...
}
複製程式碼

執行非同步網路請求交由ThreadPoolExecutor處理,執行runnable的run方法,之前先看過了AsyncCall的結構,runable的具體實現是通過AsyncCall的execute()方法處理的,具體程式碼如下:

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();            // 真正向伺服器傳送網路請求的程式碼,後面會具體說明。
        if (retryAndFollowUpInterceptor.isCanceled()) {                     // 呼叫了call.cancel()方法取消網路請求
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled")); // 通過CallBack進行失敗的回撥
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);    // 通過CallBack進行成功的回撥
        }
      } catch (IOException e) {
        ... 
      } finally {
        client.dispatcher().finished(this);    // 非同步請求結束後,從執行佇列中移除請求
      }
    }
複製程式碼

非同步請求的移除操作

  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }


  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    ... 
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");    //對執行佇列中移除請求
      if (promoteCalls) promoteCalls();    // 將非同步請求準備佇列中的將請求放入執行佇列中,做補位操作
      ...
    }
   ... 
  }

  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; 
    if (readyAsyncCalls.isEmpty()) return; 

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; 
    }
  }
複製程式碼

5. 鏈式呼叫傳送網路請求

之前只是知道通過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中的proceed()方法:

 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {

     ... 
   //  1.  獲取獲截器鏈中的第一個攔截器
   //  2.  通過index + 1,去掉攔截器鏈中的第一個攔截器獲得新的攔截器鏈
   //  3.  呼叫原攔截器鏈中第一個攔截器的intercept()方法,並傳入新的攔截器鏈

     RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    ... 
    return response;
  }
複製程式碼

這裡面的具體程式碼實現簡單粗暴,無非是按順序新增不同的攔截器,用於分級處理Request和Response,最後建立了一個RealInterceptorChain物件,用於順序執行每個攔截器中的intercept()方法。

接著看其中一個攔截器RetryAndFollowUpInterceptor中intercept()方法

@Override public Response intercept(Chain chain) throws IOException {

      Request request = chain.request();

      ...   //  加工處理網路請求體

      response = realChain.proceed(request, streamAllocation, null, null);  // 將請求傳遞給下一個攔截器

      ...  //   加工處理響應體

     return response; 
 }
複製程式碼

可以看到每個攔截器做的事無非是加工請求物件,將請求交由下一個攔截器處理,當然最後一個攔截器就不需要下交請求,而是直接向伺服器傳送網路請求,最後對響應加工處理並返回。

相關文章