天天看點

OkHttp3源碼詳解一、概述二、源碼分析三、總結

前言:為什麼有些人甯願吃生活的苦也不願吃學習的苦,大概是因為懶惰吧,學習的苦是需要自己主動去吃的,而生活的苦,你躺着不動它就會來找你了。

一、概述

OKHttp是一個非常優秀的網絡請求架構,已經被谷歌加入到Android源碼中,目前比較流行的Retrofit底層也是使用OKHttp的,OKHttp的使用是要掌握的,有不懂得可以參考我的部落格OKHttp3的使用和詳解

在早期版本中,OKHttp支援http1.0,1.1,SPDY協定,但是http2的協定問世也導緻了OKHttp做出了改變,OKHttp鼓勵開發者使用http2,不再對SPDY協定給予支援,另外,新版的okhttp還支援WebScoket,這樣就可以非常友善地建立長連接配接了。

OkHttp3源碼詳解一、概述二、源碼分析三、總結

作為非常優秀的網絡請求架構,okhttp也支援網絡緩存,okhttp的緩存基于DiskLruCache,DiskLruCache雖然沒有被加入到Android源碼中,但是也是一個非常優秀的緩存架構,有時間可以學習一下。在安全方面,okhttp支援如上圖所示的TLS版本,以確定一個安全的Scoket連結,此外還支援網絡連結失敗的重試以及重定向。

二、源碼分析

2.1 okhttp的使用

OkHttpClient okHttpClient = new OkHttpClient();

        //通過Builder輔助類建構請求對象
        Request request = new Request.Builder()
                .get()//get請求
                .url("https://www.baidu.com")//請求位址
                .build();//建構

        //通過mOkHttpClient調用請求得到Call
        final Call call = mOkHttpClient.newCall(request);

        //執行同步請求,擷取Response對象
        Response response = call.execute();
        String string = response.body().string();
        Log.e(TAG, "get同步請求success==" + string);

        //異步執行
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Log.e(TAG, "get異步請求failure==" + e.getMessage());
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                String string = response.body().string();
                Log.e(TAG, "get異步請求success==" + string);
            }
        });
           

2.2 同步Request的請求流程

OkHttp3源碼詳解一、概述二、源碼分析三、總結

在開始流程講解前先來了解三個概念(注釋來自于源碼):

Connections:連結遠端伺服器的無理連結

Streams:基于Connection的邏輯Http請求/響應對。一個連結可以承載多個Stream都是有限制的,http1.0、1.1隻支援承載一個Stream,http2支援承載多個Stream(支援并發請求,并發請求共用一個Connection)

Calls:邏輯Stream序列,典型的例子是一個初始請求以及後續的請求。對于同步和異步請求,唯一的差別就是異步請求會放到線程池(ThreadPoolExecutor)中執行,而同步請求會在目前線程執行,會阻塞目前線程。

call:最終的請求對象

Interceptors:這個是okhttp最核心的部分,一個請求會經過okhttp若幹個攔截器的處理,每一個攔截器都完成一個功能子產品,一個Request經過攔截器的處理後,最終會得到Response。攔截器裡面包含的東西很多,這裡将重點講解。

2.3 OkHttpClient

首先,我們構造OkHttpClient對象執行個體,OkHttpClient建立執行個體的方式有兩種,第一種是使用預設構造函數直接new一個執行個體,另一種就是,通過建造者(Builder)模式new OkHttpClient().Builder().build,這兩種方式有什麼差別呢?其實第二種預設的設定和第一種相同,但是我們可以利用建造者模式來設定每一種屬性。

我們先來看第一種方式:OkHttpClient okHttpClient = new OkHttpClient();

public OkHttpClient() {
    this(new Builder());
  }

 public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }
           

可以看到簡單的一句new OkHttpClient(),Okhttp就為我們做了很多工作,很多需要使用到的參數在這裡獲得預設值,我們來分析一下他們的含義:

  • dispatcher :排程器的意識,主要作用通過雙端隊列儲存Calls(同步/異步call),同時線上程池中執行異步請求;
  • protocols:預設支援的Http協定版本,Protocol.HTTP_2/Protocol.HTTP_1_1;
  • connectionSpecs :Okhttp的連結(Connection)配置,ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT,一個針對TLS連結的配置,一個針對普通的http連結配置;
  • eventListenerFactory:一個Call狀态的監聽器,這個是OKHttp新添加的功能,這個還不是最終版本,最後還會改變;
  • proxySelector:使用預設的代理選擇器;
  • cookieJar:預設是沒有Cookie的;
  • socketFactory:使用預設的Socket工廠生産Socket;
  • hostnameVerifier、certificatePinner、proxyAuthenticator、authenticator:安全相關的配置;
  • connectionPool:連接配接池;
  • dns:域名解析系統,domain name -> ip address;
  • followSslRedirects、followRedirects、retryOnConnectionFailure:起始狀态;
  • connectTimeout、readTimeout 、writeTimeout:分别為:連結逾時時間、讀取逾時時間、寫入逾時時間;
  • pingInterval:這個就和WebSocket有關了,為了保證長連接配接,必須每隔一段時間發送一個ping指令進行保活。

注意事項:OkHttpClient強烈建議全局單例使用,因為每一個OkHttpClient都有自己單獨的線程池和連接配接池,複用連接配接池和線程池能夠減少延遲和節省記憶體。

2.4 RealCall(生成一個Call)

在我們定義了請求對象Request之後,通過okHttpClient.newCall()生成一個Call,該對象代表一個執行的請求,Call是可以被取消的,Call對象代表一個request/response 對(Stream),一個Call隻能被執行一次,執行同步請求(call.execute()):

/**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

 @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 = true說明已經執行了,如果再次調用就會抛出異常,這裡說明一個Call隻能被執行一次,注意同步執行請求和異步執行請求的差別:異步執行請求(call.equeue(CallBack callback))

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
           

可以看到同步執行請求生成的是RealCall,而異步執行請求生成的是AsyncCall,AsyncCall其實就是Runnable的子類,如果可以執行則對目前添加監聽操作,然後将Call對象放入排程器(dispatcher)中,最後由攔截器中的各個攔截器對該請求進行處理,最終傳回Response。

2.5 排程器(dispatcher)

在上面同步執行請求中看到client.dispatcher().executed(this)後由Response result = getResponseWithInterceptorChain()傳回結果,最後執行finished()方法,我們先來看看client.dispatcher().executed(this)。

排程器(dispatcher)是儲存同步和異步Call的地方,并負責執行異步AsyncCall。我們來看看維護的變量含義:

  • int maxRequests = 64:最大并發請求數為64
  • int maxRequestsPerHost = 5:每個主機的最大請求數為5
  • Runnable  idleCallback:Runnable對象,在删除請求時執行
  • ExecutorService executorService:線程池
  • Deque<AsyncCall> readyAsyncCalls:緩存異步調用準備的任務
  • Deque<AsyncCall> runningAsyncCalls:緩存正在運作的異步任務,包括取消尚未完成的任務
  • Deque<RealCall> runningSyncCalls:緩存正在運作的同步任務,包括取消尚未完成的任務
OkHttp3源碼詳解一、概述二、源碼分析三、總結

如上圖,針對同步請求Dispatcher使用一個Deque儲存了同步請求任務;針對異步Dispacher使用了兩個Deque儲存任務,一個儲存準備執行的請求,一個儲存正在執行的請求,為什麼要兩個呢?因為Dispatch預設支援并發請求是64個,單個Host最多執行5個并發請求,如果超過,則call會被放入readyAsyncCalls中,當出現空閑線程時,再講readyAsyncCalls中的線程移到runningAsyncCalls中,執行請求。

OkHttp3源碼詳解一、概述二、源碼分析三、總結

那麼client.dispatcher().executed(this)裡面做了什麼呢?

@Override public Response execute() throws IOException {
    ······
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      return result;
    } catch (IOException e) {
     ·····
    } finally {
      client.dispatcher().finished(this);
    }
  }
           
/** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }
           

裡面隻是将同步任務加入到runningSyncCalls中,由Response result = getResponseWithInterceptorChain()攔截器攔截後傳回Response 結果,最後執行finished()方法。

/** 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();
    }
  }
           

對于同步請求,隻是簡單地在runningSyncCalls集合中移除請求,promoteCalls為false,是以不會執行promoteCalls()中的方法,promoteCalls()裡面主要是周遊并執行異步請求待執行合集中的請求。下面來看看異步執行中的方法:

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

可以看到當“正在執行的請求總數<64 && 單個Host真正執行的請求<5”則将請求加入到runningAsyncCalls執行中的集合中,然後利用線程池執行該請求,否則就直接加入到readyAsyncCalls準備執行的集合中。因為AsyncCall 是Runnable的子類,線上程池中最終會調用AsyncCall.execute()方法執行異步請求。

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();//攔截器
        if (retryAndFollowUpInterceptor.isCanceled()) {//如果攔截失敗回調onFailure方法
          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);//結束
      }
    }
  }
           

此處的執行邏輯跟同步的大緻相同,隻是client.dispatcher().finished(this)不一樣,這是一個異步任務,會回調另外一個finish()方法:

/** Used by {@code AsyncCall#run} to signal completion. */
  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();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }
    ·····
  }
           

可以看到promoteCalls為true,是以會執行promoteCalls()方法,

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    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; // Reached max capacity.
    }
  }
           

這個方法主要是周遊readyAsyncCalls集合中待執行請求,如果runningAsyncCalls的任務數<64并且readyAsyncCalls不為空,将readyAsyncCalls準備執行的集合請求加入到runningAsyncCalls中并且執行請求。如果runningAsyncCalls的任務數>=64,則說明正在執行的任務池已經滿了,暫時無法加入;如果readyAsyncCalls集合為空,說明請求都已經執行了,沒有還沒執行的請求。放入readyAsyncCalls集合的請求會繼續走上述的流程,直至到所有的請求被執行。

2.6 攔截器Intercepoter

我們回到最重要的地方攔截器部分:

Response response = 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,攔截器鍊,所有攔截器的合集,網絡的核心也是最後的調用者。

可以看到上面依次添加interceptors,retryAndFollowUpInterceptor,BridgeInterceptor,CacheInterceptor,ConnectInterceptor到RealInterceptorChain中,攔截器之是以可以依次調用,并最終從後先前傳回Response,都是依賴chain.proceed(originalRequest)這個方法,

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

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
     ·····
    // Call the next interceptor in the chain.
    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;
  }
           

執行目前的攔截器intercept(RealInterceptorChain  next)方法,并且調用下一個(index+1)攔截器,下一個攔截器的調用依賴于目前攔截器的intercept()方法中,對RealInterceptorChain的proceed()方法的調用:

response = realChain.proceed(request, streamAllocation, null, null);
           

可以看到目前攔截器的Response依賴于一下一個攔截器的Response,是以沿着這條攔截器鍊會依次調用下一個攔截器,執行到最後一哥攔截器的時候,就會沿着相反方向依次傳回Response,得到最終的Response。

2.7 RetryAndFollowUpInterceptor:重試及重定向攔截器

攔截器類都是繼承Interceptor類,并重寫intercept(Chain chain)方法:

@Override 
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();//擷取Request對象
    RealInterceptorChain realChain = (RealInterceptorChain) chain;//擷取攔截器對象,用于調用下一個proceed()
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
        call, eventListener, callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {//循環
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null);//調用下一個攔截器
        releaseConnection = false;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), false, request)) {//路由異常,嘗試恢複,如果再失敗就跑出異常
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        continue;//繼續重試
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, requestSendStarted, request)) throw e;//連結關閉異常,嘗試恢複
        releaseConnection = false;
        continue;//繼續重試
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {//前一個重試得到的response
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      //主要為了新的重試Request添加驗證頭等内容
      Request followUp = followUpRequest(response);

      if (followUp == null) {//如果一個響應的到的code是200,那麼followUp==null
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

        //----------------異常處理----------------------
      closeQuietly(response.body());
      
      if (++followUpCount > MAX_FOLLOW_UPS) {//超出最大次數,抛出異常
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }

      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(followUp.url()), call, eventListener, callStackTrace);
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;//得到處理後的Request,沿着攔截鍊繼續請求
      priorResponse = response;
    }
  }
           

該攔截器的作用就是重試以及重定向,當一個請求由于各種原因失敗,或者路由異常,則嘗試恢複,否則根據響應碼ResponseCode在followUpRequest(response)方法中對Request進行再處理得到行的Request,然後沿着攔截器繼續新的請求,如果ResponseCode==200,那麼這些過程就結束了。

2.8 BridgeInterceptor:橋攔截器

BridgeInterceptor的主要作用是為請求Request添加請求頭,為響應Response添加響應頭:

@Override 
public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
    //----------------Request---------------------
    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {//添加Content-Type請求頭
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");//分塊傳輸
        requestBuilder.removeHeader("Content-Length");
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

    Response networkResponse = chain.proceed(requestBuilder.build());

    //----------------Response---------------------
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());//儲存cookie

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")//Content-Encoding和Content-Length不能用于gzip解壓
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }
           

這個攔截器相對比較簡單。

2.9 CacheInterceptor:緩存攔截器

我們先來看看緩存的響應頭:

OkHttp3源碼詳解一、概述二、源碼分析三、總結

Cache-control:标明緩存最大的存活時常;

Date:伺服器告訴用戶端,該資源發送的時間;

Expires:表示過期時間(該字段是1.0的東西,與cache-control同時存在時,cache-control的優先級更高);

Last-Modified:伺服器告訴用戶端,資源最後的修改時間;

E-Tag:目前資源在伺服器的唯一辨別,用于判斷資源是否被修改。

還有If-Modified-since和If-none-Match兩個字段,這兩個字段是配合Last-Modified和E-Tag使用的,大緻流程如下:伺服器收到請求時,會在200 OK中傳回該資源的Last-Modified和ETag頭(伺服器支援緩存的時候才有這兩個頭),用戶端将該資源儲存在cache中,并且記錄這兩個屬性,當用戶端需要發送相同的請求時,根據Date + Cache-control來判斷緩存是否過期,如果過期了則會在請求中攜帶If-Modified-Since和If-None-Match兩個頭,這兩個頭的值分别是Last-Modified和ETag頭的值,伺服器通過這兩個頭判斷資源未發生變化,用戶端重新加載,傳回304響應。

OkHttp3源碼詳解一、概述二、源碼分析三、總結

先來看看CacheInterceptor幾個比較重要的類:

CacheStrategy:緩存政策類,告訴CacheInterceptor是使用緩存還是使用網絡請求;

Cache:封裝了實際的緩存操作;

DiskLruCache:Cache基于DiskLruCache。

@Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())//以請求的URL作為可以來擷取緩存
        : null;

    long now = System.currentTimeMillis();

    //緩存政策類,該類決定了是使用緩存還是網絡請求
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;//網絡請求,如果為null則代表不适用網絡請求
    Response cacheResponse = strategy.cacheResponse;//緩存響應,如果為null則表示不适用緩存

    if (cache != null) {//根據緩存政策,更新統計名額:請求次數,使用網絡請求次數,緩存次數
      cache.trackResponse(strategy);
    }

    //緩存不可用,關閉
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    //如果既無網絡可用,也無緩存可用,則傳回504錯誤
    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    //緩存可用,直接使用緩存
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      //進行網絡請求,得到網絡響應
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    //HTTP_NOT_MODIFIED緩存有效,合并網絡請求和緩存
    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);//更新緩存
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      //有響應體并且可緩存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);//寫緩存
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {//判斷緩存的有效性
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }
           

再簡單說一下流程:

1.如果網絡不可用而且無可用的緩存,則傳回504錯誤;

2.繼續,如果不需要網絡請求,則直接使用緩存;

3.繼續,如果網絡請求可用,則進行網絡請求;

4.繼續,如果有緩存,并且網絡請求傳回HTTP_NOT_MODIFIED,說明緩存還有效的,則合并網絡響應和緩存的結果,同時更新緩存;

5.繼續,如果沒有緩存,則寫入新的緩存。

CacheStrategy在CacheInterceptor中起到了很關鍵的作用,該類決定了是網絡請求還是緩存,該類最關鍵的代碼是getCandidate()方法:

private CacheStrategy getCandidate() {
      // No cached response.
      //沒有緩存,直接使用網絡請求
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      //https, 但是沒有握手,直接進行網絡請求
      // Drop the cached response if it's missing a required handshake.
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }

      // If this response shouldn't have been stored, it should never be used
      // as a response source. This check should be redundant as long as the
      // persistence store is well-behaved and the rules are constant.
      if (!isCacheable(cacheResponse, request)) {//不可以緩存,直接進行網絡請求
        return new CacheStrategy(request, null);
      }

      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        //請求頭nocache或者請求頭包含If-Modified-Since或者If-None-Match
        //請求頭包含If-Modified-Since或者If-None-Match意味着本地緩存過期,需要伺服器驗證
        //本地緩存是不是還能繼續使用
        return new CacheStrategy(request, null);
      }

      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.immutable()) {//強制使用緩存
        return new CacheStrategy(null, cacheResponse);
      }

      long ageMillis = cacheResponseAge();
      long freshMillis = computeFreshnessLifetime();

      if (requestCaching.maxAgeSeconds() != -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }

      long minFreshMillis = 0;
      if (requestCaching.minFreshSeconds() != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }

      long maxStaleMillis = 0;
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }

      //可緩存,并且ageMillis + minFreshMillis < freshMillis + maxStaleMillis
      //意味着雖過期,但是可用,在請求頭添加“warning”
      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        return new CacheStrategy(null, builder.build());//使用緩存
      }

      // Find a condition to add to the request. If the condition is satisfied, the response body
      // will not be transmitted.
      String conditionName;
      String conditionValue;
      //走到這裡說明緩存已經過期
      //添加請求頭:If-Modified-Since或者If-None-Match
      //etag與If-None-Match配合使用
      //lastModified與If-Modified-Since配合使用
      //前者和後者的值是相同的
      //差別在于前者是響應頭,後者是請求頭。
      //後者用于伺服器進行資源比對,看看是資源是否改變了。
      // 如果沒有,則本地的資源雖過期還是可以用的
      if (etag != null) {
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
      } else {
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }
           

大緻的流程如下:(if-else的關系)

1.沒有緩存,直接使用網絡請求;

2.如果是https,但是沒有握手,直接網絡請求;

3.不可緩存,直接網絡請求;

4.請求頭nocache,或者請求頭包含If-Modified-Since或者If-None-Match,則需要伺服器驗證本地緩存是否繼續使用,直接網絡請求;

5.可緩存,并且ageMillis + minFreshMillis < freshMillis + maxStaleMillis(意味着過期,但是可用,會在請求頭添加warning),則使用緩存;

6.緩存過期,則添加請求頭,If-Modified-Since或者If-None-Match,進行網絡請求。

緩存攔截器流程圖:

OkHttp3源碼詳解一、概述二、源碼分析三、總結

2.10 ConnectInterceptor(連接配接池攔截器)

ConnectInterceptor是一個連結相關的攔截器,這個攔截器的代碼最少,但并不是最簡單的,先來看看ConnectInterceptor中比較重要的相關類:

OkHttp3源碼詳解一、概述二、源碼分析三、總結

RouteDatabase:這是一個關于路由器白名單和黑名單類,處于黑名單的資訊會避免不必要的嘗試;

RealConnection:Connect子類,主要 實作連結的建立等工作;

ConnectionPool:連接配接池,實作連結的複用;

Connection和Stream的關系:Http1是1:1的關系,Http2是1對多的關系;就是說一個http1.x的連結隻能被一個請求使用,而一個http2.x連結對應多個Stream,多個Stream的意思是Http2連接配接支援并發請求,即一個連結可以被多個請求使用。還有Http1.x的keep-alive機制的作用是保證連結使用完不關閉,當下一次請求與連結的Host相同的時候,連接配接可以直接使用,不用建立(節省資源,提高性能)。

StreamAllocation:流配置設定,流是什麼呢?我們知道Connection是一個連接配接遠端伺服器的Socket連接配接,而Stream是基于Connection邏輯Http 請求/響應對,StreamAllocation會通過ConnectionPool擷取或者新生成一個RealConnection來得到一個連接配接到server的Connection連接配接,同時會生成一個HttpCodec用于下一個CallServerInterceptor,以完成最終的請求。

HttpCodec:Encodes HTTP requests and decodes HTTP responses(源碼注釋),大意為編碼Http請求,解碼Http響應。針對不同的版本OKHttp為我們提供了HttpCodec1(Http1.x)和HttpCodec2(Http2.x)。

一句話概括就是:配置設定一個Connection和HttpCodec,為最終的請求做準備。

/** Opens a connection to the target server and proceeds to the next interceptor. */
public final class ConnectInterceptor implements Interceptor {
  public final OkHttpClient client;

  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    //我們需要網絡來滿足這個請求,可能是為了驗證一個條件GET請求(緩存驗證等)
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}
           

代碼量表面看起來很少,但是大部分都已經封裝好的了,這裡僅僅是 調用,為了可讀性和維護性,該封裝的還是要封裝。這裡的核心代碼就兩行:

HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
           

可以看出,主要的工作由streamAllocation完成,我們來看看newStream()和connection()做了什麼工作:

public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    int connectTimeout = chain.connectTimeoutMillis();
    int readTimeout = chain.readTimeoutMillis();
    int writeTimeout = chain.writeTimeoutMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
      //找到一個可用連結
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }
           

可以看到最關鍵的一步是findHealthyConnection(),這個方法的主要作用是找到可用的連結,如果連接配接不可用,這個過程會一直持續哦。

/**
   * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
   * until a healthy connection is found.
   */
  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws IOException {
    while (true) {
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          connectionRetryEnabled);

      // If this is a brand new connection, we can skip the extensive health checks.如果是一個新連結,直接傳回就好
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }

      // Do a (potentially slow) check to confirm that the pooled connection is still good. If it
      // isn't, take it out of the pool and start again.
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {//判斷連接配接是否還是好的
        noNewStreams();//不是好的就移除連接配接池
        continue;//不是好的就一直持續
      }

      return candidate;
    }
  }
           

我們來看看你noNewStreams()做了什麼操作:

/** Forbid new streams from being created on the connection that hosts this allocation. */
  public void noNewStreams() {
    Socket socket;
    Connection releasedConnection;
    synchronized (connectionPool) {
      releasedConnection = connection;
      socket = deallocate(true, false, false);//noNewStreams,released,streamFinished核心方法
      if (connection != null) releasedConnection = null;
    }
    closeQuietly(socket);//關閉Socket
    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);//監聽回調
    }
  }
           

上面的關鍵代碼是deallocate():

private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
    assert (Thread.holdsLock(connectionPool));

    //這裡以noNewStreams為true,released為false,streamFinished為false為例
    if (streamFinished) {
      this.codec = null;
    }
    if (released) {
      this.released = true;
    }
    Socket socket = null;
    if (connection != null) {
      if (noNewStreams) {
        //noNewStreams是RealConnection的屬性,如果為true則這個連結不會建立新的Stream,一但設定為true就一直為true
        //搜尋整個源碼,這個該屬性設定地方如下:
        //evitAll:關閉和移除連接配接池中的所有連結,(如果連接配接空閑,即連接配接上的Stream數為0,則noNewStreams為true);
        //pruneAndGetAllocationCount:移除記憶體洩漏的連接配接,以及擷取連接配接Stream的配置設定數;
        //streamFailed:Stream配置設定失敗
        //綜上所述,這個屬性的作用是禁止無效連接配接建立新的Stream的
        connection.noNewStreams = true;
      }
      if (this.codec == null && (this.released || connection.noNewStreams)) {
        release(connection);//釋放Connection承載的StreamAllocations資源(connection.allocations)
        if (connection.allocations.isEmpty()) {
          connection.idleAtNanos = System.nanoTime();
        //connectionBecameIdle:通知線程池該連接配接是空閑連接配接,可以作為移除或者待移除對象
          if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
            socket = connection.socket();
          }
        }
        connection = null;//
      }
    }
    return socket;//傳回待關閉的Socket對象
  }
           

我們來看看findConnection()方法:

/**
   * Returns a connection to host a new stream. This prefers the existing connection if it exists,
   * then the pool, finally building a new connection.
   */
  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
      //排除異常情況
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");

      // Attempt to use an already-allocated connection. We need to be careful here because our
      // already-allocated connection may have been restricted from creating new streams.
      //這個方法與deallocate()方法作用一緻
      //如果連接配接不能建立Stream,則釋放資源,傳回待關閉的close Socket
      releasedConnection = this.connection;
      toClose = releaseIfNoNewStreams();
      //經過releaseIfNoNewStreams(),如果connection不為null,則連接配接可用
      if (this.connection != null) {
        // We had an already-allocated connection and it's good.
        //存在可使用的已配置設定連接配接,
        result = this.connection;
        //為null值,說明這個連接配接是有效的
        releasedConnection = null;
      }
      if (!reportedAcquired) {
        // If the connection was never reported acquired, don't report it as released!
        releasedConnection = null;
      }

      //沒有可用連接配接,去連接配接池中找
      if (result == null) {
        // Attempt to get a connection from the pool.
        //通過ConnectionPool,Address,StreamAllocation從連接配接池中擷取連接配接
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    closeQuietly(toClose);

    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
      // If we found an already-allocated or pooled connection, we're done.
      //找到一個已配置設定或者連接配接池中的連接配接,此過程結束,傳回
      return result;
    }
    
     //否則我們需要一個路由信心,這是一個阻塞操作
    // If we need a route selection, make one. This is a blocking operation.
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      newRouteSelection = true;
      routeSelection = routeSelector.next();
    }

    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");

      if (newRouteSelection) {
        // Now that we have a set of IP addresses, make another attempt at getting a connection from
        // the pool. This could match due to connection coalescing.
        //提供更加全面的路由資訊,再次從連接配接池中擷取連接配接
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {
          Route route = routes.get(i);
          Internal.instance.get(connectionPool, address, this, route);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }
        
      //實在是沒有找到,隻能生成新的連接配接
      if (!foundPooledConnection) {
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }

        // Create a connection and assign it to this allocation immediately. This makes it possible
        // for an asynchronous cancel() to interrupt the handshake we're about to do.
        route = selectedRoute;
        refusedStreamCount = 0;
        result = new RealConnection(connectionPool, selectedRoute);
        acquire(result, false);//添加Connection的StreamAllocation添加到connection.allocations集合中
      }
    }

    // If we found a pooled connection on the 2nd time around, we're done.
    //如果連接配接是從連接配接池中找到的,那麼說明連接配接是可複用的,不是新生的,新生成的連接配接是需要連接配接伺服器才能可用的
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }

    // Do TCP + TLS handshakes. This is a blocking operation.連接配接server
    result.connect(
        connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());//将路由資訊添加到routeDatabase中

    Socket socket = null;
    synchronized (connectionPool) {
      reportedAcquired = true;

      // Pool the connection.
      Internal.instance.put(connectionPool, result);//将新生成的連接配接放入連接配接池中

      // If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      //如果是HTTP2連接配接,由于HTTP2連接配接具有多路複用特性
      //是以,我們需要確定http2的多路複用特性
      if (result.isMultiplexed()) {
        //確定http2的多路複用特性,重複的連接配接将 被踢除
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

    eventListener.connectionAcquired(call, result);
    return result;
  }
           

上面的源碼比較長,加了注釋。我們來看看流程圖:

OkHttp3源碼詳解一、概述二、源碼分析三、總結

a)排除連接配接不可用的情況;

private Socket releaseIfNoNewStreams() {
    assert (Thread.holdsLock(connectionPool));
    RealConnection allocatedConnection = this.connection;
    if (allocatedConnection != null && allocatedConnection.noNewStreams) {
      return deallocate(false, false, true);
    }
    return null;
  }
           

這個方法是說如果狀态處于releaseIfNoNewStreams狀态,釋放該連接配接,否則,該連接配接可用

b)判斷連接配接是否可用

//經過releaseIfNoNewStreams(),如果connection不為null,則連接配接可用
      if (this.connection != null) {
        // We had an already-allocated connection and it's good.
        //存在可使用的已配置設定連接配接,
        result = this.connection;
        //為null值,說明這個連接配接是有效的
        releasedConnection = null;
      }
           

經過releaseIfNoNewStreams()檢查後,Connection不為空則連接配接可用

c)第一次連接配接池查找,沒有提供路由資訊

//通過ConnectionPool,Address,StreamAllocation從連接配接池中擷取連接配接
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
        }
           

如果找到,則将連接配接指派給result

d)周遊路由器進行二次查找

//提供更加全面的路由資訊,再次從連接配接池中擷取連接配接
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {
          Route route = routes.get(i);
          Internal.instance.get(connectionPool, address, this, route);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
           

e)如果還是沒有找到,則隻能建立新的連接配接了

result = new RealConnection(connectionPool, selectedRoute);
        acquire(result, false);//添加Connection的StreamAllocation添加到connection.allocations集合中
           

f)新的連接配接,連接配接伺服器

// Do TCP + TLS handshakes. This is a blocking operation.連接配接server
    result.connect(
        connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());//将路由資訊添加到routeDatabase中
           

g)新的連接配接放入線程池

// Pool the connection.
 Internal.instance.put(connectionPool, result);//将新生成的連接配接放入連接配接池中
           

h)如果連接配接是一個HTTP2連接配接,則需要確定多路複用的特性

//如果是HTTP2連接配接,由于HTTP2連接配接具有多路複用特性
      //是以,我們需要確定http2的多路複用特性
      if (result.isMultiplexed()) {
        //確定http2的多路複用特性,重複的連接配接将 被踢除
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
           

在Connectinterceptor中起到關鍵作用的就是ConnectionPool,我們來看看ConnectionPool連接配接池的源碼:

OkHttp3源碼詳解一、概述二、源碼分析三、總結
在目前版本,連接配接池是預設保持5個空閑連接配接的,這些空閑連接配接如果超過五分鐘不被使用,則會被連接配接池移除,這個兩個數值以後可能會改變,同時也是可以自定義修改的
  • RouteDatabase:路由記錄表,這是一個關于路由資訊的白名單和黑名單類,處于黑名單的路由資訊會被避免不必要的嘗試;
  • Deque:隊列,存放待複用的連接配接
  • ThreadPoolExecutor:線程池,用于支援線程池的clearup任務,清除idel線程

對于連接配接池我們聯想到的是,存、去、清除:

1)存

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }
           

可以看到,在存入連接配接池connections.add(connection)之前,可能需要執行連接配接池的清潔任務,連接配接存入連接配接池的操作很簡單,主要看一下clearup做了什麼:

long cleanup(long now) {
    int inUseConnectionCount = 0;
    int idleConnectionCount = 0;
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;

    // Find either a connection to evict, or the time that the next eviction is due.
    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();

        // If the connection is in use, keep searching.
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;//連接配接池中處于使用狀态的連接配接數
          continue;
        }

        idleConnectionCount++;//處于空閑狀态的連接配接數

        // If the connection is ready to be evicted, we're done.
        long idleDurationNs = now - connection.idleAtNanos;
        //尋找空閑最久的那個連接配接
        if (idleDurationNs > longestIdleDurationNs) {
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }

      //空閑最久的那個連接配接
      //如果空閑連接配接大于keepAliveDurationNs預設五分鐘
      //如果空閑連接配接數大于maxIdleConnections預設五個
      //則執行移除操作
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {
        // We've found a connection to evict. Remove it from the list, then close it below (outside
        // of the synchronized block).
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {
        // A connection will be ready to evict soon.
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
        // All connections are in use. It'll be at least the keep alive duration 'til we run again.
        return keepAliveDurationNs;
      } else {
        // No connections, idle or in use.
        cleanupRunning = false;
        return -1;
      }
    }

    closeQuietly(longestIdleConnection.socket());//關閉socket

    // Cleanup again immediately.
    return 0;
  }
           

這個方法根據兩個名額判斷是否移除空閑時間最長的連接配接,大于空閑值或者連接配接數超過最大值,則移除空閑時間最長的空閑連接配接,clearup方法的執行也依賴與另一個比較重要的方法:pruneAndGetAllocationCount(connection, now)該方法的作用是移除發生洩漏的StreamAllocation,統計連接配接中正在使用的StreamAllocation個數。

2)取

@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      //isEligible是判斷一個連接配接是否還能攜帶一個StreamAllocation,如果能則說明這個連接配接可用
      if (connection.isEligible(address, route)) {
        //将StreamAllocation添加到connection.allocations中
        streamAllocation.acquire(connection, true);
        return connection;
      }
    }
    return null;
  }
           

首先,判斷Address對應的Connection是否還能承載一個新的StreamAllocation,可以的話我們将這個StreamAllocation添加到connection.allocations中,最後傳回這個Connection。

3)移除

public void evictAll() {
    List<RealConnection> evictedConnections = new ArrayList<>();
    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();
        if (connection.allocations.isEmpty()) {
          connection.noNewStreams = true;
          evictedConnections.add(connection);
          i.remove();
        }
      }
    }

    for (RealConnection connection : evictedConnections) {
      closeQuietly(connection.socket());
    }
  }
           

關閉并移除連接配接池中的空閑連接配接。

(11)CallServerInterceptor

攔截器鍊最後的攔截器,使用HttpCodec完成最後的請求發送。

三、總結

okhttp是一個http+http2的用戶端,使用android+java的應用,整體分析完之後,再來看下面這個架構架構圖就清晰很多了

OkHttp3源碼詳解一、概述二、源碼分析三、總結

點關注,不迷路

好了各位,以上就是這篇文章的全部内容了,能看到這裡的人呀,都是人才。

我是suming,感謝各位的支援和認可,您的點贊、評論、收藏【一鍵三連】就是我創作的最大動力,我們下篇文章見!

如果本篇部落格有任何錯誤,請批評指教,不勝感激 !

要想成為一個優秀的安卓開發者,這裡有必須要掌握的知識架構,一步一步朝着自己的夢想前進!Keep Moving!

相關文章:

Retrofit2詳解和使用(一)

  • Retrofit2的介紹和簡單使用
OKHttp3的使用和詳解
  • OKHttp3的用法介紹和解析
OKHttp3源碼詳解
  • 從源碼角度解釋OKHttp3的關鍵流程和重要操作
RxJava2詳解(一)
  • 詳細介紹了RxJava的使用(基本建立、快速建立、延遲建立等操作符)
RxJava2詳解(二)
  • RxJava轉換、組合、合并等操作符的使用
RxJava2詳解(三)
  • RxJava延遲、do相關、錯誤處理等操作符的使用
RxJava2詳解(四)
  • RxJava過濾、其他操作符的使用
上述幾篇都是android開發必須掌握的,後續會完善其他部分!

繼續閱讀