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

OkHttp是square公司出品的一款網絡加載框架,咱們今天從源碼的角度來看看,他在咱們進行同步和異步請求的時候,內部都具體作了什麼操做。web

使用

在使用OkHttp的時候,首先第一步是實例化一個OkHttpClient對象。設計模式

OkHttpClient okHttpClient = new OkHttpClient();
複製代碼

咱們來看看他的構造方法。bash

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來處理。cookie

發起網絡請求

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來看看,內部進行的同步操做和異步操做的過程是怎麼樣的。socket

同步請求

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

複製代碼

咱們從源碼裏面能夠看到,一個同步的網絡請求總共作了四件事。ide

1.if判斷,檢查這個executed是否正在執行,若是執行就直接拋出異常。這裏須要說明一點,一個網絡請求的操做,不能同時被操做兩次,若是操做兩次,則報錯。ui

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

相關文章
相關標籤/搜索