okhttp3源碼分析之攔截器

前言

本篇文章繼續經過源碼來探討okhttp的另一個重要知識點:攔截器,在上一篇文章咱們知道,在請求發送到服務器以前有一系列的攔截器對請求作了處理後才發送出去,在服務器返回響應以後,一樣的有一系列攔截器對響應作了處理後才返回給發起請求的調用者,可見,攔截器是okhttp的一個重要的核心功能,在分析各個攔截器功能的同時又會牽扯出okhttp的緩存機制、鏈接機制。java

本文源碼基於okhttp3.14.xgit

okhttp項目地址:okhttpgithub

攔截器的簡單使用

自定義一個攔截器須要實現Interceptor接口,接口定義以下:緩存

public interface Interceptor {
   
  //咱們須要實現這個intercept(chain)方法,在裏面定義咱們的攔截邏輯
  Response intercept(Chain chain) throws IOException;

  interface Chain {
      
     //返回Request對象
    Request request();

    //調用Chain的proceed(Request)方法處理請求,最終返回Response
    Response proceed(Request request) throws IOException;


    //若是當前是網絡攔截器,該方法返回Request執行後創建的鏈接
    //若是當前是應用攔截器,該方法返回null
    @Nullable Connection connection();

    //返回對應的Call對象
    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) } } 複製代碼

能夠看到Interceptor由兩部分組成:intercept(Chain)方法和內部接口Chain,下面是自定義一個攔截器的通用邏輯,以下:服務器

public class MyInterceptor implements Interceptor {   
    @Override
    public Response intercept(Chain chain) throws IOException {
        
        //一、經過傳進來的Chain獲取Request
        Request request = chain.request();
        
      	//二、 處理Request,邏輯本身寫
        //...
        
        //三、調用Chain的proceed(Request)方法處理請求,獲得Response
        Response response = chain.proceed(request);
     
        //四、 處理Response,邏輯本身寫
        //...
        
        //五、返回Response
        return response;
    }
}
複製代碼

上述就是一個攔截器的通用邏輯,首先咱們繼承Interceptor實現intercept(Chain)方法,完成咱們本身的攔截邏輯,即根據須要進行一、二、三、四、5步,不論是自定義攔截器仍是後面介紹的okhttp默認的攔截器大概都是這個模板實現,定義完攔截器後,咱們在構造OkhttpChient時就能夠經過addInterceptor(Interceptor)或addNetworkInterceptor(Interceptor)添加自定義攔截器,以下:cookie

OkHttpClient client = new OkHttpClient.Builder()
     .addInterceptor(new MyInterceptor())
     .build();

或

OkHttpClient client = new OkHttpClient.Builder()
     .addNetworkInterceptor(new MyInterceptor())
     .build();
複製代碼

這樣okhttp在鏈式調用攔截器處理請求時就會調用到咱們自定義的攔截器,那麼addInterceptor(Interceptor)和addNetworkInterceptor(Interceptor)有什麼不同呢?它們一個是添加應用攔截器,一個是添加網絡攔截器,主要是調用的時機不同,更多區別能夠參考官方WIKI文檔Okhttp-wiki 之 Interceptors 攔截器,當咱們平時作應用開發使用addInterceptor(Interceptor)就好了。網絡

上述是咱們自定義的攔截器,下面咱們來看看okhttp默認的攔截器都幹了什麼。併發

RealCall :: getResponseWithInterceptorChain()

在上一篇文章知道RealCall的getResponseWithInterceptorChain()是處理、發送請求而且返回響應的地方,咱們再看一遍getResponseWithInterceptorChain()方法的源碼,以下:異步

//RealCall.java
Response getResponseWithInterceptorChain() throws IOException {
    //新建一個List用來保存攔截器
    List<Interceptor> interceptors = new ArrayList<>();
    //添加咱們自定義的應用攔截器
    interceptors.addAll(client.interceptors());
    //添加負責重試重定向的攔截器
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //添加負責轉換請求響應的攔截器
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //添加負責緩存的攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //添加負責管理鏈接的攔截器
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {//沒有特殊要求,不使用WebSocket協議,WebSocket是什麼?自行百度
      //添加咱們自定義的網絡攔截器
      interceptors.addAll(client.networkInterceptors());
    }
    //添加負責發起請求獲取響應的攔截器
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //構造第一個Chain
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
       //調用Chain的proceed(Request)方法處理請求
      Response response = chain.proceed(originalRequest);
      //...
      //返回響應
      return response;
    }
    //...省略異常處理
  }
複製代碼

getResponseWithInterceptorChain()幹了三件事:一、添加攔截器到interceptors列表中;二、構造第一個Chain;三、調用Chain的proceed(Request)方法處理請求。下面分別介紹:socket

一、添加攔截器到interceptors列表中

除了添加咱們自定義的攔截器外,還添加了默認的攔截器,以下:

  • 一、RetryAndFollowUpInterceptor:負責失敗重試和重定向。
  • 二、BridgeInterceptor:負責把用戶構造的Request轉換爲發送給服務器的Request和把服務器返回的Response轉換爲對用戶友好的Response。
  • 三、CacheInterceptor:負責讀取緩存以及更新緩存。
  • 四、ConnectInterceptor:負責與服務器創建鏈接並管理鏈接。
  • 五、CallServerInterceptor:負責向服務器發送請求和從服務器讀取響應。

這幾個默認的攔截器是本文的重點,在後面會分別介紹。

二、構造第一個Chain

Chain是Interceptor的一個內部接口,它的實現類是RealInterceptorChain,咱們要對它的傳進來的前6個構造參數有個印象,以下:

public final class RealInterceptorChain implements Interceptor.Chain {
    
    //...
    
    public RealInterceptorChain(List<Interceptor> interceptors, Transmitter transmitter, @Nullable Exchange exchange, int index, Request request, Call call, int connectTimeout, int readTimeout, int writeTimeout) {
        this.interceptors = interceptors;//interceptors列表
        this.transmitter = transmitter;//Transmitter對象,後面會介紹
        this.exchange = exchange;//Exchange對象,後面會介紹
        this.index = index;//interceptor索性,用於獲取interceptors列表中的interceptor
        this.request = request;//請求request
        this.call = call;//Call對象
        //...
    }
    
    //...
}
複製代碼

在後面的攔截器中均可以經過Chain獲取這些傳進來的參數。咱們知道,爲了讓每一個攔截器都有機會處理請求,okhttp使用了責任鏈模式來把各個攔截器串聯起來,攔截器就是責任鏈的節點,而Chain就是責任鏈中各個節點之間的鏈接點,負責把各個攔截器鏈接起來。那麼是怎麼鏈接的?看下面的Chain的proceed方法。

三、調用Chain的proceed(Request)方法處理請求

實際是RealInterceptorChain的proceed(Request)方法,以下:

public final class RealInterceptorChain implements Interceptor.Chain {
    
    //...
    
    @Override 
    public Response proceed(Request request) throws IOException {
        return proceed(request, transmitter, exchange);
    }

    public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange) throws IOException {

        //index不能越界
        if (index >= interceptors.size()) throw new AssertionError();

        //...

        //再新建一個Chain,這裏注意index加1,
        RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
                                                             index + 1, request, call, connectTimeout, readTimeout, writeTimeout);

        //獲取interceptors列表中的下一個攔截器
        Interceptor interceptor = interceptors.get(index);

        //調用下一個攔截器的intercept(Chain)方法,傳入剛纔新建的RealInterceptorChain,返回Response
        Response response = interceptor.intercept(next);

        //...

        //返回響應
        return response;
    }
}
複製代碼

proceed方法裏面首先會再新建一個Chain而且index + 1做爲構造參數傳了進去,而後經過index從interceptors列表中獲取了一個攔截器,接着就會調用攔截器的intercept方法,並把剛剛新建的Chain做爲參數傳給攔截器,咱們再回顧一下上面所講的攔截器intercept方法的模板,intercept方法處理完Request邏輯後,會再次調用傳入的Chain的proceed(Request)方法,這樣又會重複Chain的proceed方法中的邏輯,因爲index已經加1了,因此此次Chain就會經過index獲取下一個攔截器,並調用下一個攔截器的intercept(Chain)方法,而後如此循環重複下去,這樣就把每一個攔截器經過一個個Chain鏈接起來,造成一條鏈,把Request沿着鏈傳遞下去,直到請求被處理,而後返回Response,響應一樣的沿着鏈傳遞上去,以下:

從上圖可知,當沒有自定義攔截器時,責任鏈首節點就是RetryAndFollowUpInterceptor,尾節點就是CallServerInterceptor,Request按照攔截器的順序正向處理,Response則逆向處理,每一個攔截器都有機會處理Request和Response,一個完美的責任鏈模式的實現。

知道了getResponseWithInterceptorChain()的總體流程後,下面分別介紹各個默認攔截器的功能。

RetryAndFollowUpInterceptor

在自定義攔截器的時候就講過,Interceptor的intercept(Chain)方法就是攔截器的攔截實現,RetryAndFollowUpInterceptor的intercept(Chain)方法以下:

//RetryAndFollowUpInterceptor.java
@Override 
public Response intercept(Chain chain) throws IOException {

    //獲取Request
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    //獲取Transmitter
    Transmitter transmitter = realChain.transmitter();

    //重定向次數
    int followUpCount = 0;
    Response priorResponse = null;
    
    //一個死循環
    while (true) {
        
        //調用Transmitter的prepareToConnect方法,作好鏈接創建的準備
        transmitter.prepareToConnect(request);

        if (transmitter.isCanceled()) {
            throw new IOException("Canceled");
        }
        
        Response response;
        boolean success = false;
        try {
            //調用proceed方法,裏面調用下一個攔截器BridgeInterceptor的intercept方法
            response = realChain.proceed(request, transmitter, null);
            success = true;
        }catch (RouteException e) {//出現RouteException異常
            //調用recover方法檢測鏈接是否能夠繼續使用
            if (!recover(e.getLastConnectException(), transmitter, false, request)) {
                throw e.getFirstConnectException();
            }
            continue;
        } catch (IOException e) {//出現IOException異常,和服務端創建鏈接失敗
            boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
            //調用recover方法檢測鏈接是否能夠繼續使用
            if (!recover(e, transmitter, requestSendStarted, request)) throw e;
            continue;
        } finally {//出現其餘未知異常
            if (!success) {
                //調用Transmitter的exchangeDoneDueToException()方法釋放鏈接
                transmitter.exchangeDoneDueToException();
            }
        }
        
        //執行到這裏,沒有出現任何異常,鏈接成功, 響應返回

        //...

        //根據響應碼來處理請求頭
        Request followUp = followUpRequest(response, route);

        //followUp爲空,不須要重定向,直接返回Response
        if (followUp == null) {
            //...
            return response;
        }

        //followUp不爲空,須要重定向

        //...

        //MAX_FOLLOW_UPS值爲20,重定向次數不能大於20次
        if (++followUpCount > MAX_FOLLOW_UPS) {
            throw new ProtocolException("Too many follow-up requests: " + followUpCount);
        }

        //以重定向後的Request再次重試
        request = followUp;
        priorResponse = response;
    }
}
複製代碼

RetryAndFollowUpInterceptor的intercept(Chain)方法中主要是失敗重試和重定向的邏輯,該方法流程以下:

一、首先獲取Transmitter類;

二、而後進入一個死循環,先調用Transmitter的prepareToConnect方法,準備創建鏈接;(鏈接真正的創建在ConnectInterceptor中)

三、接着調用Chain的proceed方法,繼續執行下一個攔截器BridgeInterceptor的intercept方法:

​ 3.一、若是在請求的過程當中拋出RouteException異常或IOException異常,就會調用recover方法檢測鏈接是否能夠繼續使用,若是不能夠繼續使用就拋出異常,整個過程結束,不然就再次重試,這就是失敗重試;

​ 3.二、若是在請求的過程當中拋出除了3.1以外的異常,就會調用Transmitter的exchangeDoneDueToException()方法釋放鏈接,整個過程結束。

四、沒有任何異常拋出,當響應Response返回後,就會調用followUpRequest方法,裏面根據返回的Response的響應碼來決定是否須要重定向(構造followUp請求),若是不須要重定向,就直接返回Response,若是須要重定向,那麼以重定向後的Request再次重試,重定向次數不能大於20次。

一、Transmitter

在整個方法的流程中出現了一個Transmitter,這裏介紹一下,它是okhttp中應用層和網絡層的橋樑,管理同一個Cal的全部鏈接、請求、響應和IO流之間的關係,它在RealCall建立後就被建立了,以下:

//RealCall.java
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    //建立RealCall
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    //建立Transmitter,賦值給call的transmitter字段
    call.transmitter = new Transmitter(client, call);
    return call;
}
複製代碼

建立後,在構造節點Chain時做爲參數傳了進去,在getResponseWithInterceptorChain方法中有講到,因此在intercept方法中它能夠經過chain.transmitter()得到,它的整個生命週期貫穿了全部攔截器,在接下來的ConnectInterceptor和CallServerInterceptor中你均可以見到它的身影,咱們看一下它的主要成員,以下:

public final class Transmitter {
    
    private final OkHttpClient client;//OkHttpClient大管家
    private final RealConnectionPool connectionPool;//鏈接池,管理着鏈接
    public RealConnection connection;//本次鏈接對象
    private ExchangeFinder exchangeFinder;//負責鏈接的建立
    private @Nullable Exchange exchange;//負責鏈接IO流讀寫
    private final Call call;//Call對象
    
    //...
    
    public Transmitter(OkHttpClient client, Call call) {
        this.client = client;
        this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
        this.call = call;
        this.eventListener = client.eventListenerFactory().create(call);
        this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
    }

    
    public void prepareToConnect(Request request) {
        if (this.request != null) {
            if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
                return; // Already ready.
            }
           //...
        }
        this.request = request;
        //建立ExchangeFinder
        this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()), call, eventListener);
    }
    
}
複製代碼

在Transmitter中client和call咱們都認識,剩下的RealConnectionPool、RealConnection、ExchangeFinder、Exchange都和okhttp的鏈接機制有關,都會在ConnectInterceptor中介紹,Transmitter就是負責管理它們之間的關係。這裏咱們只要記住,Transmitter的prepareToConnect方法中主要是建立了一個ExchangeFinder,爲在ConnectInterceptor中鏈接的創建作了一個準備。

BridgeInterceptor

BridgeInterceptor的intercept(Chain)方法以下:

//BridgeInterceptor.java
@Override 
public Response intercept(Chain chain) throws IOException {
    
    //獲取Request
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
  
    //下面都是根據須要爲Request的header添加或移除一些信息
    
    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        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");
    }

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

    //調用proceed方法,裏面調用下一個攔截器CacheInterceptor的intercept方法
    Response networkResponse = chain.proceed(requestBuilder.build());

    //返回Response後
    //下面都是根據須要爲Response的header添加或移除一些信息
    
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    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")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }
複製代碼

BridgeInterceptor中的邏輯是在全部默認攔截器中是最簡單,它主要就是對Request或Response的header作了一些處理,把用戶構造的Request轉換爲發送給服務器的Request,還有把服務器返回的Response轉換爲對用戶友好的Response。例如,對於Request,當開發者沒有添加Accept-Encoding時,它會自動添加Accept-Encoding : gzip,表示客戶端支持使用gzip;對於Response,當Content-Encoding是gzip方式而且客戶端是自動添加gzip支持時,它會移除Content-Encoding、Content-Length,而後從新解壓縮響應的內容。

CacheInterceptor

CacheInterceptor的intercept(Chain)方法以下:

//CacheInterceptor.java
@Override
public Response intercept(Chain chain) throws IOException {
    
    //根據Request獲得Cache中緩存的Response,Cache是什麼,後面介紹
    Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;

    long now = System.currentTimeMillis();

    //建立緩存策略:網絡、緩存、或二者都使用,CacheStrategy是什麼,後面介紹
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //獲得networkRequest
    Request networkRequest = strategy.networkRequest;
    //獲得cacheResponse,cacheResponse等於上面的cacheCandidate
    Response cacheResponse = strategy.cacheResponse;

    //...

    //這個Response緩存無效,close掉它
    if (cacheCandidate != null && cacheResponse == null) {
        closeQuietly(cacheCandidate.body()); 
    }

    //一、networkRequest爲null且cacheResponse爲null:表示強制使用緩存,可是沒有緩存,因此構造狀態碼爲504,body爲空的Response
    if (networkRequest == null && cacheResponse == null) {
        return new Response.Builder()
            .request(chain.request())
            .protocol(Protocol.HTTP_1_1)
            .code(504)//狀態碼504
            .message("Unsatisfiable Request (only-if-cached)")
            .body(Util.EMPTY_RESPONSE)
            .sentRequestAtMillis(-1L)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();
    }

   //二、networkRequest爲null但cacheResponse不爲null:表示強制使用緩存,而且有緩存,因此直接返回緩存的Response
    if (networkRequest == null) {
        return cacheResponse.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .build();
    }

    Response networkResponse = null;
    try {
        //networkRequest不爲null,因此能夠發起網絡請求,調用chain.proceed(Request),裏面調用下一個攔截器BridgeInterceptor的intercept方法,會返回網絡請求獲得的networkResponse
        networkResponse = chain.proceed(networkRequest);
       
    } finally {
        //發起網絡請求出現IO異常或其餘異常的處理
        //...
    }

    //三、networkRequest不爲null且cacheResponse不爲null:由於cacheResponse不爲null,因此根據網絡請求獲得的networkResponse和緩存的cacheResponse作比較,來決定是否更新cacheResponse
    if (cacheResponse != null) {
        if (networkResponse.code() == HTTP_NOT_MODIFIED) {//HTTP_NOT_MODIFIED等於304,304表示服務器緩存有更新,因此客戶端要更新cacheResponse
            //下面根據networkResponse更新(從新構造)cacheResponse
            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();
            //更新cacheResponse在本地緩存
            cache.trackConditionalCacheHit();
            cache.update(cacheResponse, response);
            return response;
        } else {//不須要更新cacheResponse,close掉它
            closeQuietly(cacheResponse.body());
        }
    }

    //四、networkRequest不爲null但cacheResponse爲null:cacheResponse爲null,沒有緩存使用,因此從networkResponse讀取網絡響應,構造Response準備返回
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    //把Response緩存到Cache中
    if (cache != null) {
        if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
            //cacheResponse爲null,因此這裏是第一次緩存,把Response緩存到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.
            }
        }
    }

    //返回Response
    return response;
}
複製代碼

CacheInterceptor的intercept(Chain)裏面定義了okhttp的緩存機制,咱們先來了解兩個類:Cache和CacheStrategy,這樣才能看懂intercept(Chain)裏面的邏輯。

一、Cache - 緩存實現

Cache是okhttp中緩存的實現,內部使用了DiskLruCache,以下:

public final class Cache implements Closeable, Flushable {
    //...
    //內部都是經過DiskLruCache實現
    final DiskLruCache cache;
    
    //有一個InternalCache實現,都調用了Cache中的方法
    final InternalCache internalCache = new InternalCache() {

        @Override public @Nullable Response get(Request request) throws IOException {
            return Cache.this.get(request);
        }

        @Override public @Nullable CacheRequest put(Response response) throws IOException {
            return Cache.this.put(response);
        }

        @Override public void remove(Request request) throws IOException {
            Cache.this.remove(request);
        }

        @Override public void update(Response cached, Response network) {
            Cache.this.update(cached, network);
        }

        @Override public void trackConditionalCacheHit() {
            Cache.this.trackConditionalCacheHit();
        }

        @Override public void trackResponse(CacheStrategy cacheStrategy) {
            Cache.this.trackResponse(cacheStrategy);
        }
    }
    
    //能夠經過下面兩個構造函數構造一個Cache
    
    public Cache(File directory, long maxSize) {
        this(directory, maxSize, FileSystem.SYSTEM);
    }

    Cache(File directory, long maxSize, FileSystem fileSystem) {
        this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
    }

    //下面是主要方法
    
    @Nullable Response get(Request request) {
      	//...
    }

    @Nullable CacheRequest put(Response response) {
       //...
    }

    void remove(Request request) throws IOException {
        //...
    }

    void update(Response cached, Response network) {
      //...
    }
    
    synchronized void trackConditionalCacheHit() {
       //...
    }

    synchronized void trackResponse(CacheStrategy cacheStrategy) {
       //...
    }

    @Override public void flush() throws IOException {
       //...
    }

    @Override public void close() throws IOException {
        //...
    }
}
複製代碼

Cache中有一個內部實現類InternalCache,見名知意,它是okhttp內部使用的,它實現了InternalCache接口,接口中的方法都和Cache中的方法同名,並且這個實現類的全部方法都是調用了Cache中相應的方法,也就是說InternalCache的方法實現和Cache相應的方法同樣,但Cache和InternalCache不同的是,Cache比InternalCache多了一些方法供外部調用如flush()、 close()等,提供了更多對緩存的控制,而InternalCache中的方法都只是緩存的基本操做,如get、put、remove、update等方法,這些方法的邏輯都是基於Cache中的DiskLruCache實現,詳情能夠看DiskLruCache的原理實現。

要知道,okhttp默認是不使用緩存,也就是Cache爲null,若是要使用緩存,咱們須要自行配置,經過下面方法使用okhttp的緩存機制:

//緩存的路徑
File cacheDir = new File(Constant.PATH_NET_CACHE);
//這裏經過帶有兩個參數的構造函數構造一個Cache
Cache cache = new Cache(cacheDir, 1024 * 1024 * 10);//緩存的最大尺寸10M

//而後設置給OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
    .cache(cache)
    .build();

複製代碼

經過上面全局設置後,Cache和InternalCache都不會爲null,由於在建立Cache時InternalCache也一塊兒建立了,okhttp的緩存機制就會生效。

咱們先回到CacheInterceptor的intercept方法,它首先一開始就要判斷cache是否等於null,那麼CacheInterceptor的cache在哪裏來的呢?是在構造函數中,以下:

public final class CacheInterceptor implements Interceptor {
    final @Nullable InternalCache cache;

    public CacheInterceptor(@Nullable InternalCache cache) {
        this.cache = cache;
    }
    //...
}
複製代碼

可用看到它是InternalCache實例,在 getResponseWithInterceptorChain()中添加攔截器時就經過client爲這個InternalCache賦值了,以下:

//RealCall.java
Response getResponseWithInterceptorChain() throws IOException {
    //...
    //添加負責緩存的攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //...
}
複製代碼

注意到new CacheInterceptor(client.internalCache()),因此咱們看client的internalCache方法,以下:

//OkHttpClient.java
@Nullable InternalCache internalCache() {
    return cache != null ? cache.internalCache : internalCache;
  }
複製代碼

cache就是上面全局設置的cache實例,因此不爲null,返回cache中的internalCache實例,這樣CacheInterceptor中就持有internalCache實例。

二、CacheStrategy - 緩存策略

CacheStrategy是okhttp緩存策略的實現,okhttp緩存策略遵循了HTTP緩存策略,所以瞭解okhttp緩存策略前須要有HTTP緩存相關基礎:HTTP 協議緩存機制詳解,瞭解了HTTP緩存策略後,咱們再來看CacheStrategy,以下:

public final class CacheStrategy {

    //CacheStrategy兩個主要的成員變量:networkRequest、cacheResponse
    public final @Nullable Request networkRequest;
    public final @Nullable Response cacheResponse;

    CacheStrategy(Request networkRequest, Response cacheResponse) {
        this.networkRequest = networkRequest;
        this.cacheResponse = cacheResponse;
    }
    
    //...
    
    //經過工廠模式建立CacheStrategy
    public static class Factory {
        final long nowMillis;
        final Request request;
        final Response cacheResponse;

        public Factory(long nowMillis, Request request, Response cacheResponse) {
            this.nowMillis = nowMillis;
            this.request = request;
            this.cacheResponse = cacheResponse;
            //...
        }

        public CacheStrategy get() {
            CacheStrategy candidate = getCandidate();
            //...
            return candidate;
        }
        
         //...
    }
}
複製代碼

CacheStrategy是經過工廠模式建立的,它有兩個主要的成員變量:networkRequest、cacheResponse,CacheInterceptor的intercept方法經過CacheStrategy的networkRequest和cacheResponse的組合來判斷執行什麼策略,networkRequest是否爲空決定是否請求網絡,cacheResponse是否爲空決定是否使用緩存,networkRequest和cacheResponse的4種組合和對應的緩存策略以下:

  • 一、networkRequest爲null且cacheResponse爲null:沒有緩存使用,又不進行網絡請求,構造狀態碼爲504的Response。
  • 二、networkRequest爲null但cacheResponse不爲null:有緩存使用,且緩存在有效期內,因此直接返回緩存的Response。
  • 三、networkRequest不爲null且cacheResponse不爲null:有緩存使用,但緩存在客戶端的判斷中表示過時了,因此請求服務器進行決策,來決定是否使用緩存的Response。
  • 四、networkRequest不爲null但cacheResponse爲null:沒有緩存使用,因此直接使用服務器返回的Response

networkRequest和cacheResponse在建立CacheStrategy時經過構造參數賦值,那麼CacheStrategy在那裏被建立呢?當調用CacheStrategy.Factory(long, Request, Response).get()時就會返回一個CacheStrategy實例,因此CacheStrategy在Factory的get方法中被建立,咱們來看Factory的get方法,以下:

//CacheStrategy.Factory
public CacheStrategy get() {
    CacheStrategy candidate = getCandidate();
    //...
    return candidate;
}
複製代碼

能夠看到CacheStrategy經過Factory的getCandidate方法建立,getCandidate方法以下:

//CacheStrategy.Factory
private CacheStrategy getCandidate() {
    //一、沒有Response緩存,直接進行網絡請求
    if (cacheResponse == null) {
        return new CacheStrategy(request, null);
    }

    //二、若是TLS握手信息丟失,直接進行網絡請求
    if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
    }

    //三、根據Response狀態碼,Expired和Cache-Control的no-Store進行判斷Response緩存是否可用
    if (!isCacheable(cacheResponse, request)) {
        //Response緩存不可用,直接進行網絡請求
        return new CacheStrategy(request, null);
    }

    
    //得到Request的緩存控制字段CacheControl
    CacheControl requestCaching = request.cacheControl();
    //四、根據Request中的Cache-Control的noCache和header是否設置If-Modified-Since或If-None-Match進行判斷是否可使用Response緩存
    if (requestCaching.noCache() || hasConditions(request)) {
        //不可使用Response緩存,直接進行網絡請求
        return new CacheStrategy(request, null);
    }
    
 	//走到這裏表示Response緩存可用

    //得到Response的緩存控制字段CacheControl
    CacheControl responseCaching = cacheResponse.cacheControl();

    //得到該Response已經緩存的時長
    long ageMillis = cacheResponseAge();
    //得到該Response能夠緩存的時長
    long freshMillis = computeFreshnessLifetime();

    if (requestCaching.maxAgeSeconds() != -1) 
        //通常取max-age
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
    }

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

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

	//五、判斷緩存是否過時,決定是否使用Response緩存:Response已經緩存的時長 < max-stale + max-age
    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\"");
        }
        //5.一、緩存沒有過時,直接使用該Response緩存
        return new CacheStrategy(null, builder.build());
    }

   //5.二、緩存過時了,判斷是否設置了Etag或Last-Modified等標記
    String conditionName;
    String conditionValue;
    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 {
        //緩存沒有設置Etag或Last-Modified等標記,因此直接進行網絡請求
        return new CacheStrategy(request, null);
    }

	//緩存設置了Etag或Last-Modified等標記,因此添加If-None-Match或If-Modified-Since請求頭,構造請求,交給服務器判斷緩存是否可用
    Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
    Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

    Request conditionalRequest = request.newBuilder()
        .headers(conditionalRequestHeaders.build())
        .build();
    //networkRequest和cacheResponse都不爲null
    return new CacheStrategy(conditionalRequest, cacheResponse);
}
複製代碼

getCandidate()方法中根據HTTP的緩存策略決定networkRequest和cacheResponse的組合,從getCandidate()方法中咱們能夠看到HTTP的緩存策略分爲兩種:

  • 一、強制緩存:客戶端參與決策決定是否繼續使用緩存,客戶端第一次請求數據時,服務端返回了緩存的過時時間:Expires或Cache-Control,當客戶端再次請求時,就判斷緩存的過時時間,沒有過時就能夠繼續使用緩存,不然就不使用,從新請求服務端。
  • 二、對比緩存:服務端參與決策決定是否繼續使用緩存,客戶端第一次請求數據時,服務端會將緩存標識:Last-Modified/If-Modified-Since、Etag/If-None-Match和數據一塊兒返回給客戶端 ,當客戶端再次請求時,客戶端將緩存標識發送給服務端,服務端根據緩存標識進行判斷,若是緩存尚未更新,可使用,則返回304,表示客戶端能夠繼續使用緩存,不然客戶端不能繼續使用緩存,只能使用服務器返回的新的響應。

並且強制緩存優先於對比緩存,咱們再貼出來自HTTP 協議緩存機制詳解的一張圖,它很好的解釋了getCandidate()方法中1~5步驟流程,以下:

三、緩存機制

咱們再回到CacheInterceptor的intercept方法,它的1~4步驟就是CacheStrategy的networkRequest和cacheResponse的4種組合狀況,都有詳細的註釋,每一種組合對應一種緩存策略,而緩存策略又是基於getCandidate()方法中寫死的HTTP緩存策略,再結合okhttp本地緩存的實現Cache,咱們得出結論:okhttp的緩存機制 = Cache緩存實現 + 基於HTTP的緩存策略,整個流程圖以下:

瞭解了okhttp的緩存機制後,咱們接着下一個攔截器ConnectInterceptor。

ConnectInterceptor

ConnectInterceptor的intercept(Chain)方法以下:

//ConnectInterceptor.java
@Override
public Response intercept(Chain chain) throws IOException {
   
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    //獲取Transmitter
    Transmitter transmitter = realChain.transmitter();
	
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //一、新建一個Exchange
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);

    //調用proceed方法,裏面調用下一個攔截器CallServerInterceptor的intercept方法
    //這裏調用的proceed方法是帶有三個參數的,它傳進了Request、Transmitter和剛剛新建的Exchange
    return realChain.proceed(request, transmitter, exchange);
}
複製代碼

ConnectInterceptor的intercept(Chain)方法很簡潔,裏面定義了okhttp的鏈接機制,它首先獲取Transmitter,而後經過Transmitter的newExchange方法建立一個Exchange,把它傳到下一個攔截器CallServerInterceptor,Exchange是什麼?Exchange負責從建立的鏈接的IO流中寫入請求和讀取響應,完成一次請求/響應的過程,在CallServerInterceptor中你會看到它真正的做用,這裏先忽略。因此註釋1的newExchange方法是鏈接機制的主要邏輯實現,咱們繼續看Transmitter的newExchange方法,以下:

//Transmitter.java
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {

    //...省略異常處理

    //一、經過ExchangeFinder的find方法找到一個ExchangeCodec
    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);

    //建立Exchange,並把ExchangeCodec實例codec傳進去,因此Exchange內部持有ExchangeCodec實例
    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);

    //...
    
    return result;
}
複製代碼

重點是註釋1,ExchangeFinder對象早在RetryAndFollowUpInterceptor中經過Transmitter的prepareToConnect方法建立,它的find方法是鏈接真正建立的地方,ExchangeFinder是什麼?ExchangeFinder就是負責鏈接的建立,把建立好的鏈接放入鏈接池,若是鏈接池中已經有該鏈接,就直接取出複用,因此ExchangeFinder管理着兩個重要的角色:RealConnection、RealConnectionPool,下面講解一下RealConnectionPool和RealConnection,有助於鏈接機制的理解。

一、RealConnection - 鏈接實現

鏈接的真正實現,實現了Connection接口,內部利用Socket創建鏈接,以下:

public interface Connection {
    //返回這個鏈接使用的Route
    Route route();

    //返回這個鏈接使用的Socket
    Socket socket();

    //若是是HTTPS,返回TLS握手信息用於創建鏈接,不然返回null
    @Nullable Handshake handshake();

    //返回應用層使用的協議,Protocol是一個枚舉,如HTTP1.一、HTTP2
    Protocol protocol();
}

public final class RealConnection extends Http2Connection.Listener implements Connection {

    public final RealConnectionPool connectionPool;
    //路由
    private final Route route;
    //內部使用這個rawSocket在TCP層創建鏈接
    private Socket rawSocket;
    //若是沒有使用HTTPS,那麼socket == rawSocket,不然這個socket == SSLSocket
    private Socket socket;
    //TLS握手
    private Handshake handshake;
    //應用層協議
    private Protocol protocol;
    //HTTP2鏈接
    private Http2Connection http2Connection;
    //okio庫的BufferedSource和BufferedSink,至關於javaIO的輸入輸出流
    private BufferedSource source;
    private BufferedSink sink;


    public RealConnection(RealConnectionPool connectionPool, Route route) {
        this.connectionPool = connectionPool;
        this.route = route;
    }


    public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
        //...
    }

    //...
}
複製代碼

RealConnection中有一個connect方法,外部能夠調用該方法創建鏈接,connect方法以下:

//RealConnection.java
public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
    if (protocol != null) throw new IllegalStateException("already connected");

    RouteException routeException = null;
    List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);

    //路由選擇
    if (route.address().sslSocketFactory() == null) {
      if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
        throw new RouteException(new UnknownServiceException(
            "CLEARTEXT communication not enabled for client"));
      }
      String host = route.address().url().host();
      if (!Platform.get().isCleartextTrafficPermitted(host)) {
        throw new RouteException(new UnknownServiceException(
            "CLEARTEXT communication to " + host + " not permitted by network security policy"));
      }
    } else {
      if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
        throw new RouteException(new UnknownServiceException(
            "H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"));
      }
    }

    //開始鏈接
    while (true) {
      try {
        if (route.requiresTunnel()) {//若是是通道模式,則創建通道鏈接
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break;
          }
        } else {//一、不然進行Socket鏈接,大部分是這種狀況
          connectSocket(connectTimeout, readTimeout, call, eventListener);
        }
        //創建HTTPS鏈接
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
        break;
      }
      //...省略異常處理

    if (http2Connection != null) {
      synchronized (connectionPool) {
        allocationLimit = http2Connection.maxConcurrentStreams();
      }
    }
  }
複製代碼

咱們關注註釋1,通常會調用connectSocket方法創建Socket鏈接,connectSocket方法以下:

//RealConnection.java
private void connectSocket(int connectTimeout, int readTimeout, Call call, EventListener eventListener) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    //根據代理類型的不一樣建立Socket
    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    eventListener.connectStart(call, route.socketAddress(), proxy);
    rawSocket.setSoTimeout(readTimeout);
    try {
        //一、創建Socket鏈接
        Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    }
    //...省略異常處理

    try {
        //得到Socket的輸入輸出流
        source = Okio.buffer(Okio.source(rawSocket));
        sink = Okio.buffer(Okio.sink(rawSocket));
    } 
     //...省略異常處理
}
複製代碼

咱們關注註釋1,Platform是okhttp中根據不一樣Android版本平臺的差別實現的一個兼容類,這裏就不細究,Platform的connectSocket方法最終會調用rawSocket的connect()方法創建其Socket鏈接,創建Socket鏈接後,就能夠經過Socket鏈接得到輸入輸出流source和sink,okhttp就能夠從source讀取或往sink寫入數據,source和sink是BufferedSource和BufferedSink類型,它們是來自於okio庫,它是一個封裝了java.io和java.nio的庫,okhttp底層依賴這個庫讀寫數據,Okio好在哪裏?詳情能夠看這篇文章Okio好在哪

二、RealConnectionPool - 鏈接池

鏈接池,用來管理鏈接對象RealConnection,以下:

public final class RealConnectionPool {

    //線程池
    private static final Executor executor = new ThreadPoolExecutor(
        0 /* corePoolSize */,
        Integer.MAX_VALUE /* maximumPoolSize */, 
        60L /* keepAliveTime */, 
        TimeUnit.SECONDS,
        new SynchronousQueue<>(), 
        Util.threadFactory("OkHttp ConnectionPool", true));
 
    boolean cleanupRunning;
    //清理鏈接任務,在executor中執行
    private final Runnable cleanupRunnable = () -> {
        while (true) {
            //調用cleanup方法執行清理邏輯
            long waitNanos = cleanup(System.nanoTime());
            if (waitNanos == -1) return;
            if (waitNanos > 0) {
                long waitMillis = waitNanos / 1000000L;
                waitNanos -= (waitMillis * 1000000L);
                synchronized (RealConnectionPool.this) {
                    try {
                        //調用wait方法進入等待
                        RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
                    } catch (InterruptedException ignored) {
                    }
                }
            }
        }
    };

    //雙端隊列,保存鏈接
    private final Deque<RealConnection> connections = new ArrayDeque<>();

    void put(RealConnection connection) {
        if (!cleanupRunning) {
            cleanupRunning = true;
            //使用線程池執行清理任務
            executor.execute(cleanupRunnable);
        }
        //將新建鏈接插入隊列
        connections.add(connection);
    }

    long cleanup(long now) {
        //...
    }

    //...
}
複製代碼

RealConnectionPool 在內部維護了一個線程池,用來執行清理鏈接任務cleanupRunnable,還維護了一個雙端隊列connections,用來緩存已經建立的鏈接。要知道建立一次鏈接要經歷TCP握手,若是是HTTPS還要經歷TLS握手,握手的過程都是耗時的,因此爲了提升效率,就須要connections來對鏈接進行緩存,從而能夠複用;還有若是鏈接使用完畢,長時間不釋放,也會形成資源的浪費,因此就須要cleanupRunnable定時清理無用的鏈接,okhttp支持5個併發鏈接,默認每一個鏈接keepAlive爲5分鐘,keepAlive就是鏈接空閒後,保持存活的時間。

當咱們第一次調用RealConnectionPool 的put方法緩存新建鏈接時,若是cleanupRunnable還沒執行,它首先會使用線程池執行cleanupRunnable,而後把新建鏈接放入雙端隊列,cleanupRunnable中會調用cleanup方法進行鏈接的清理,該方法返回如今到下次清理的時間間隔,而後調用wiat方法進入等待狀態,等時間到了後,再次調用cleanup方法進行清理,就這樣往復循環。咱們來看一下cleanup方法的清理邏輯:

//RealConnectionPool.java
long cleanup(long now) {
    
    int inUseConnectionCount = 0;//正在使用鏈接數
    int idleConnectionCount = 0;//空閒鏈接數
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;

    synchronized (this) {
        //遍歷全部鏈接,記錄空閒鏈接和正在使用鏈接各自的數量
        for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
            RealConnection connection = i.next();

            //若是該鏈接還在使用,pruneAndGetAllocationCount種經過引用計數的方式判斷一個鏈接是否空閒
            if (pruneAndGetAllocationCount(connection, now) > 0) {
                //使用鏈接數加1
                inUseConnectionCount++;
                continue;
            }
            
            //該鏈接沒有在使用

            //空閒鏈接數加1
            idleConnectionCount++;

            //記錄keepalive時間最長的那個空閒鏈接
            long idleDurationNs = now - connection.idleAtNanos;
            if (idleDurationNs > longestIdleDurationNs) {
                longestIdleDurationNs = idleDurationNs;
                //這個鏈接極可能被移除,由於空閒時間太長
                longestIdleConnection = connection;
            }
        }
        
        //跳出循環後

        //默認keepalive時間keepAliveDurationNs最長爲5分鐘,空閒鏈接數idleConnectionCount最大爲5個
        if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) {//若是longestIdleConnection的keepalive時間大於5分鐘 或 空閒鏈接數超過5個
            //把longestIdleConnection鏈接從隊列清理掉
            connections.remove(longestIdleConnection);
        } else if (idleConnectionCount > 0) {//若是空閒鏈接數小於5個 而且 longestIdleConnection鏈接還沒到期清理
            //返回該鏈接的到期時間,下次再清理
            return keepAliveDurationNs - longestIdleDurationNs;
        } else if (inUseConnectionCount > 0) {//若是沒有空閒鏈接 且 全部鏈接都還在使用
            //返回keepAliveDurationNs,5分鐘後再清理
            return keepAliveDurationNs;
        } else {
            // 沒有任何鏈接,把cleanupRunning復位
            cleanupRunning = false;
            return -1;
        }
    }

    //把longestIdleConnection鏈接從隊列清理掉後,關閉該鏈接的socket,返回0,當即再次進行清理
    closeQuietly(longestIdleConnection.socket());

    return 0;
}
複製代碼

從cleanup方法得知,okhttp清理鏈接的邏輯以下:

一、首先遍歷全部鏈接,記錄空閒鏈接數idleConnectionCount和正在使用鏈接數inUseConnectionCount,在記錄空閒鏈接數時,還要找出空閒時間最長的空閒鏈接longestIdleConnection,這個鏈接是頗有可能被清理的;

二、遍歷完後,根據最大空閒時長和最大空閒鏈接數來決定是否清理longestIdleConnection,

​ 2.一、若是longestIdleConnection的空閒時間大於最大空閒時長 或 空閒鏈接數大於最大空閒鏈接數,那麼該鏈接就會被從隊列中移除,而後關閉該鏈接的socket,返回0,當即再次進行清理;

​ 2.二、若是空閒鏈接數小於5個 而且 longestIdleConnection的空閒時間小於最大空閒時長即還沒到期清理,那麼返回該鏈接的到期時間,下次再清理;

​ 2.三、若是沒有空閒鏈接 且 全部鏈接都還在使用,那麼返回默認的keepAlive時間,5分鐘後再清理;

​ 2.四、沒有任何鏈接,idleConnectionCount和inUseConnectionCount都爲0,把cleanupRunning復位,等待下一次put鏈接時,再次使用線程池執行cleanupRunnable。

瞭解了RealConnectionPool和RealConnection後,咱們再回到ExchangeFinder的find方法,這裏是鏈接建立的地方。

三、鏈接建立(鏈接機制)

ExchangeFinder的fing方法以下:

//ExchangeFinder.java
public ExchangeCodec find( OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
   //...
    try {
        
      //調用findHealthyConnection方法,返回RealConnection
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,  writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
        
      return resultConnection.newCodec(client, chain);  
    }
    //...省略異常處理
  }

 
  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks) throws IOException {
     //一個死循環
    while (true) {
        
       //調用findConnection方法,返回RealConnection
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);
        
	  //...

        //判斷鏈接是否可用
        if (!candidate.isHealthy(doExtensiveHealthChecks)) {
            candidate.noNewExchanges();
            continue;
        }

      return candidate;
    }
  
複製代碼

ExchangeFinder的find方法會調用findHealthyConnection方法,裏面會不斷調用findConnection方法,直到找到一個可用的鏈接返回。ExchangeFinder的findConnection方法以下:

//ExchangeFinder.java
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;//返回結果,可用的鏈接
    Route selectedRoute = null;
    RealConnection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
       if (transmitter.isCanceled()) throw new IOException("Canceled");
      hasStreamFailure = false; .

	 //一、嘗試使用已經建立過的鏈接,已經建立過的鏈接可能已經被限制建立新的流
      releasedConnection = transmitter.connection;
      //1.一、若是已經建立過的鏈接已經被限制建立新的流,就釋放該鏈接(releaseConnectionNoEvents中會把該鏈接置空),並返回該鏈接的Socket以關閉
      toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
          ? transmitter.releaseConnectionNoEvents()
          : null;

        //1.二、已經建立過的鏈接還能使用,就直接使用它看成結果、
        if (transmitter.connection != null) {
            result = transmitter.connection;
            releasedConnection = null;
        }

        //二、已經建立過的鏈接不能使用
        if (result == null) {
            //2.一、嘗試從鏈接池中找可用的鏈接,若是找到,這個鏈接會賦值先保存在Transmitter中
            if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
                //2.二、從鏈接池中找到可用的鏈接
                foundPooledConnection = true;
                result = transmitter.connection;
            } else if (nextRouteToTry != null) {
                selectedRoute = nextRouteToTry;
                nextRouteToTry = null;
            } else if (retryCurrentRoute()) {
                selectedRoute = transmitter.connection.route();
            }
        }
    }
	closeQuietly(toClose);
    
	//...
    
    if (result != null) {
        //三、若是在上面已經找到了可用鏈接,直接返回結果
        return result;
    }
    
    //走到這裏沒有找到可用鏈接

    //看看是否須要路由選擇,多IP操做
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
        newRouteSelection = true;
        routeSelection = routeSelector.next();
    }
    List<Route> routes = null;
    synchronized (connectionPool) {
        if (transmitter.isCanceled()) throw new IOException("Canceled");

        //若是有下一個路由
        if (newRouteSelection) {
            routes = routeSelection.getAll();
            //四、這裏第二次嘗試從鏈接池中找可用鏈接
            if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, false)) {
                //4.一、從鏈接池中找到可用的鏈接
                foundPooledConnection = true;
                result = transmitter.connection;
            }
        }

        //在鏈接池中沒有找到可用鏈接
        if (!foundPooledConnection) {
            if (selectedRoute == null) {
                selectedRoute = routeSelection.next();
            }

           //五、因此這裏新建立一個鏈接,後面會進行Socket鏈接
            result = new RealConnection(connectionPool, selectedRoute);
            connectingConnection = result;
        }
    }

    // 4.二、若是在鏈接池中找到可用的鏈接,直接返回該鏈接
    if (foundPooledConnection) {
        eventListener.connectionAcquired(call, result);
        return result;
    }

    //5.一、調用RealConnection的connect方法進行Socket鏈接,這個在RealConnection中講過
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener);
    
    connectionPool.routeDatabase.connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
        connectingConnection = null;
        //若是咱們剛剛建立了同一地址的多路複用鏈接,釋放這個鏈接並獲取那個鏈接
        if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
            result.noNewExchanges = true;
            socket = result.socket();
            result = transmitter.connection;
        } else {
            //5.二、把剛剛新建的鏈接放入鏈接池
            connectionPool.put(result);
            //5.三、把剛剛新建的鏈接保存到Transmitter的connection字段
            transmitter.acquireConnectionNoEvents(result);
        }
    }
    
    closeQuietly(socket);
    eventListener.connectionAcquired(call, result);
    
    //5.四、返回結果
    return result;
}
複製代碼

這個findConnection方法就是整個ConnectInterceptor的核心,咱們忽略掉多IP操做和多路複用(HTTP2),假設如今咱們是第一次請求,鏈接池和Transmitter中沒有該鏈接,因此跳過一、二、3,直接來到5,建立一個新的鏈接,而後把它放入鏈接池和Transmitter中;接着咱們用同一個Call進行了第二次請求,這時鏈接池和Transmitter中有該鏈接,因此就會走一、二、3,若是Transmitter中的鏈接還可用就返回,不然從鏈接池獲取一個可用鏈接返回,因此整個鏈接機制的大概過程以下:

Transmitter中的鏈接和鏈接池中的鏈接有什麼區別?咱們知道每建立一個Call,就會建立一個對應的Transmitter,一個Call能夠發起屢次請求(同步、異步),不一樣的Call有不一樣的Transmitter,鏈接池是在建立OkhttpClient時建立的,因此鏈接池是全部Call共享的,即鏈接池中的鏈接全部Call均可以複用,而Transmitter中的那個鏈接只是對應它相應的Call,只能被本次Call的全部請求複用。

瞭解了okhttp的鏈接機制後,咱們接着下一個攔截器CallServerInterceptor。

CallServerInterceptor

CallServerInterceptor的intercept(Chain)方法以下:

//CallServerInterceptor.java
@Override
public Response intercept(Chain chain) throws IOException {
    
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    //獲取Exchange
    Exchange exchange = realChain.exchange();
    //獲取Request
    Request request = realChain.request();

	//經過Exchange的writeRequestHeaders(request)方法寫入請求的header
    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
        //...
        if (responseBuilder == null) {
            //經過okio寫入請求的body
            if (request.body().isDuplex()) {
                exchange.flushRequest();
                BufferedSink bufferedRequestBody = Okio.buffer(
                    exchange.createRequestBody(request, true));
                request.body().writeTo(bufferedRequestBody);
            } else {
                BufferedSink bufferedRequestBody = Okio.buffer(
                    exchange.createRequestBody(request, false));
                request.body().writeTo(bufferedRequestBody);
                bufferedRequestBody.close();
            }
        } else {
           //...
        }
    } else {
      exchange.noRequestBody();
    }

    //...
    
    //下面開始獲取網絡請求返回的響應
    
    //經過Exchange的readResponseHeaders(boolean)方法讀取響應的header
    if (responseBuilder == null) {
        responseBuilder = exchange.readResponseHeaders(false);
    }
    
    //獲取響應後,經過Builder模式構造Response
    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    //...
    
    //構造Response的body
    if (forWebSocket && code == 101) {
        //構造一個空的body的Response
        response = response.newBuilder()
            .body(Util.EMPTY_RESPONSE)
            .build();
    } else {
        //經過Exchange的openResponseBody(Response)方法讀取響應的body,而後經過響應的body繼續構造Response
        response = response.newBuilder()
            .body(exchange.openResponseBody(response))
            .build();
    }
    
    //...

    //返回響應Response
    return response;
  }
複製代碼

在ConnectInterceptor中咱們已經創建了鏈接,鏈接到了服務器,獲取了輸入輸出流,因此CallServerInterceptor的intercept(Chain)方法邏輯就是把請求發送到服務器,而後獲取服務器的響應,以下:

一、發送請求:

​ 1.一、經過Exchange的writeRequestHeaders(request)方法寫入請求的header;

​ 1.二、若是請求的body不爲空,經過okio寫入請求的body。

二、獲取響應:

​ 2.一、經過Exchange的readResponseHeaders(boolean)方法讀取響應的header;

​ 2.二、經過Exchange的openResponseBody(Response)方法讀取響應的body。

這個發送獲取的過程經過Exchange進行,前面已經講過它在ConnectInterceptor中建立,在process方法中傳進來,因此這裏能夠經過Chain獲取Exchange,Exchange它是負責從IO流中寫入請求和讀取響應,完成一次請求/響應的過程,它內部的讀寫都是經過一個ExchangeCodec類型的codec來進行,而ExchangeCodec內部又是經過Okio的BufferedSource和BufferedSink進行IO讀寫,這個過程在上一篇文章已經分析過了,這裏不在累述。

結語

結合上一篇文章,咱們對okhttp已經有了一個深刻的瞭解,首先,咱們會在請求的時候初始化一個Call的實例,而後執行它的execute()方法或enqueue()方法,內部最後都會執行到getResponseWithInterceptorChain()方法,這個方法裏面經過攔截器組成的責任鏈,依次通過用戶自定義普通攔截器、重試攔截器、橋接攔截器、緩存攔截器、鏈接攔截器和用戶自定義網絡攔截器和訪問服務器攔截器等攔截處理過程,來獲取到一個響應並交給用戶。okhttp的請求流程、緩存機制和鏈接機制是當中的重點,在閱讀源碼的過程當中也學習到不少東西,下一次就來分析它的搭檔Retrofit。

相關文章
相關標籤/搜索