OkHttp3.0解析 —— 從原始碼的角度談談發起網路請求時做的操作

晨雨細曲發表於2018-08-22

OkHttp是square公司出品的一款網路載入框架,我們今天從原始碼的角度來看看,他在我們進行同步和非同步請求的時候,內部都具體做了什麼操作。

使用

在使用OkHttp的時候,首先第一步是例項化一個OkHttpClient物件。

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

我們來看看他的構造方法。

public OkHttpClient() {
    this(new Builder());
  }
 
  OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.eventListenerFactory = builder.eventListenerFactory;
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;
 
    boolean isTLS = false;
    for (ConnectionSpec spec : connectionSpecs) {
      isTLS = isTLS || spec.isTls();
    }
 
    if (builder.sslSocketFactory != null || !isTLS) {
      this.sslSocketFactory = builder.sslSocketFactory;
      this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
      X509TrustManager trustManager = Util.platformTrustManager();
      this.sslSocketFactory = newSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }
 
    if (sslSocketFactory != null) {
      Platform.get().configureSslSocketFactory(sslSocketFactory);
    }
 
    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool;
    this.dns = builder.dns;
    this.followSslRedirects = builder.followSslRedirects;
    this.followRedirects = builder.followRedirects;
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;
 
    if (interceptors.contains(null)) {
      throw new IllegalStateException("Null interceptor: " + interceptors);
    }
    if (networkInterceptors.contains(null)) {
      throw new IllegalStateException("Null network interceptor: " + networkInterceptors);
    }
  }

複製程式碼

可以看到,其構造方法裡面的引數非常非常多,包括於對socket證書校驗、設定讀取和寫入的超時時間、設定攔截器、連線池的操作等等。可以說如果我們想使用OkHttp內部預設的設定,那麼我們可以直接new一個OkHttpClient就可以,但是如果我們想按照自己的方法來設定一些引數,那麼我們可以使用Builder來處理。

發起網路請求

        final String URL = "";
 
        OkHttpClient okHttpClient = new OkHttpClient();
 
        Request request = new Request.Builder()
                .url(URL)
                .get()
                .build();
 
        try {
            Response response = okHttpClient.newCall(request).execute();
            String result = response.body().string();
        } catch (IOException e) {
            e.printStackTrace();
        }

複製程式碼

我們可以看到,一般的網路請求分為三步。第一為例項化一個OKHttpClient,第二步例項化一個Request,第三步通過execute()發起同步的網路請求。我們逐步來分析。由於剛剛已經分析過了okHttpClient,所以這裡不再分析,我們從request開始。

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

複製程式碼

點選進入Request裡面我們可以看到裡面的構造方法,其實裡面的功能並不多,構造方法裡面的引數決定了其功能。看到,Reuqest的主要功能就幾個,第一為url,也就是請求的地址。第二為method,請求的方式(GET請求或者POST請求)。headers請求頭,body為請求體。最後有一個tags,這個是幹嘛的吶?其實這個tags是在給一個網路請求做標記,如果我們在進行網路請求的時候出了什麼問題,需要取消掉該網路請求,那麼就在發起網路請求的時候用tags先標記下,然後根據標記來取消網路請求。

newCall

我們點進newCall裡面看看。

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

複製程式碼

可以看到在newCall內部其實用的是newRealCall方法,等於說所有的網路請求操作都是在newRealCall裡面進行的操作。那麼就很好辦了,我們直接通過newRealCall來看看,內部進行的同步操作和非同步操作的過程是怎麼樣的。

同步請求

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

複製程式碼

我們從原始碼裡面可以看到,一個同步的網路請求總共做了四件事。

1.if判斷,檢查這個executed是否正在執行,如果執行就直接丟擲異常。這裡需要說明一點,一個網路請求的操作,不能同時被操作兩次,如果操作兩次,則報錯。

2.通過分發器(又可以叫排程器)dispatcher來執行同步任務操作。關於這個dispatcher這裡要說明一下,很多情況下,其實這個排程器是在非同步操作裡面才會用到,其實在同步裡面也做了操作。關於dispatcher有機會我專門寫一篇文章來講解。

3.通過getResonseWithInterceptorChain()將結果返回。

4.通過dispatcher結束掉任務finished()。

其實通過上面幾步,我們還是沒有看出來網路請求到底如何發起的,那麼到底是在哪裡做了操作網路請求的吶?答案就在第三步getResonseWithInterceptorChain()。我們看下這個原始碼。

getResonseWithInterceptorChain

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

複製程式碼

可以看到,這裡應該就是整個okhttp的核心了。在這裡,首先會new一個攔截器的集合,之後把我們自己定義的攔截器和okhttp自帶的攔截器一起放入到這個集合當中去,最後放入到一個叫做攔截器鏈RealInterceptorChain裡面,通過proceed發起請求,從而實現網路請求操作。說道攔截器,這裡需要帶一筆,攔截器可以說是okhttp的精華核心部分,在我看來,okhttp之所以牛逼就是牛逼在這些攔截器上面。我們可以通過不同的需求新增和改造不同的攔截器,並且這些攔截器之間的耦合度非常的低,等於說在改造一種攔截器之後,並不會對另外的攔截器有任何的影響。可以說降低耦合度是寫程式碼的最高境界,這相比較volley不知道強了多少倍。

我們可以看到,在攔截器這裡使用了責任鏈的設計模式。他通過一個一個的攔截器,根據新增順序來環環相扣,執行完一個攔截之後再執行另外一個,最後把他們湊成一個環chain,放入到RealInterceptorChain當中去。我們再來看下RealInterceptorChain方法裡面操作。

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final RealConnection connection;
  private final int index;
  private final Request request;
  private int calls;
 
  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
                              HttpCodec httpCodec, RealConnection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }
 
  @Override public Connection connection() {
    return connection;
  }
 
  public StreamAllocation streamAllocation() {
    return streamAllocation;
  }
 
  public HttpCodec httpStream() {
    return httpCodec;
  }
 
  @Override public Request request() {
    return request;
  }
 
  @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 {
 
    ......
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    
    ...... 
 
    return response;
  }
}

複製程式碼

從原始碼可以看到,其實在RealInterceptorChain方法中除了賦值以外其他並沒有做什麼特別的操作。那麼我們可以知道,主要的操作其實是在這個類的proceed方法裡面做了操作。在proceed的方法裡面我們可以看到,做了兩件事。第一,建立下一個攔截器,並且把i(ndex + 1)傳入裡面。第二,通過index獲取到攔截器,並且將下一個攔截器鏈放入到這個攔截器裡面。由此可以看到,攔截器的執行是層層遞進,每次都是執行下一個攔截器,由下一個攔截器來完成方法操作。由網上找來的一張圖我們可以看到這裡面的具體操作。

圖片

非同步操作

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

複製程式碼

在說明非同步操作的時候,我們需要先了解一下任務佇列的概念。在okhttp中有三種任務佇列和一種執行緒池。

readyAsyncCalls: 待執行非同步任務佇列

runningAsyncCalls: 執行中非同步任務佇列

runningSyncCalls: 執行中同步任務佇列

executorService: 任務佇列執行緒池

當可以執行非同步任務的列隊數大於執行緒中可以請求的最大請求數量時,則證明執行緒池比較繁忙,不能再往裡面新增任務,所以先把他放到等待非同步執行的任務佇列當中去,等到執行緒池有空間執行再把他取出來。如果執行緒池有空間,則直接將call任務放到執行中非同步任務佇列中去,然後通過exectorService執行緒池來啟動執行。非同步操作其實本質上和同步操作類似,只是多了這麼個等待和執行的概念而已。

下面通過一張圖來展示okhttp整體的發起請求的流程,到此okhttp發起網路請求部分完成。

image

OkHttp3.0解析 —— 從原始碼的角度談談發起網路請求時做的操作

相關文章