OkHttp3源碼分析

重點

本文打算從三點來剖析OkHttp3:

  • 網(wǎng)絡(luò)請求的整理流程-會使用
  • 攔截器模式-易擴(kuò)展
  • 緩存和連接池-高性能

整體流程

我們在閱讀某一類源碼之前欧穴,首先要學(xué)會怎么使用骤肛,其次才是去了解內(nèi)部的實現(xiàn)原理,實現(xiàn)方案上有什么技巧也殖。

okhttp的一張流程圖:


image

官方用例

  1. get使用方式
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}
  1. post使用的方式
public static final MediaType JSON
    = MediaType.parse("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(JSON, json);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  Response response = client.newCall(request).execute();
  return response.body().string();
}

OkHttp 官方網(wǎng)址

請求過程

從上面的用例和流程圖可以總結(jié)出OkHttp的請求過程為:

  1. 創(chuàng)建OkHttpClient
  2. 創(chuàng)建Request
  3. 同步或者異步發(fā)出請求,并經(jīng)過Interceptors處理
  4. 得到Response

然后一步一步來分析

1. 創(chuàng)建OkHttpClient

通過new OkHttpClient()來創(chuàng)建一個OkHttpClient對象务热,當(dāng)然這個是簡單的寫法忆嗜,通常情況下,我們需要設(shè)置緩存策略崎岂、超時時長捆毫、攔截器等等〕甯剩看下源碼:

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

  OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;
    this.proxy = builder.proxy;
    this.protocols = builder.protocols;
    this.connectionSpecs = builder.connectionSpecs;
    this.interceptors = Util.immutableList(builder.interceptors);
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
    this.eventListenerFactory = builder.eventListenerFactory;
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;
    this.cache = builder.cache;
    this.internalCache = builder.internalCache;
    this.socketFactory = builder.socketFactory;

    boolean isTLS = false;
    for (ConnectionSpec spec : connectionSpecs) {
      isTLS = isTLS || spec.isTls();
    }

    if (builder.sslSocketFactory != null || !isTLS) {
      this.sslSocketFactory = builder.sslSocketFactory;
      this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
      X509TrustManager trustManager = systemDefaultTrustManager();
      this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }

    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool;
    this.dns = builder.dns;
    this.followSslRedirects = builder.followSslRedirects;
    this.followRedirects = builder.followRedirects;
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;
  }

第一眼看到這么多參數(shù)绩卤,是不是被嚇到了,源碼比較長江醇,但是它本身沒有特定的功能濒憋,Builder作為一個載體,記錄下各種初始化的信息陶夜。例如凛驮,緩存地址、超時的時長条辟、自定義攔截器等等黔夭。

這里使用了Builder設(shè)計模式,更加易讀羽嫡,關(guān)于Builder設(shè)計模式本姥,可以看看這篇文章

2. 創(chuàng)建Request

Request類是一個純粹的載體,封裝了一個請求的Header杭棵、Method婚惫、url和body。這里源碼就不貼出來了。

3. 發(fā)起請求

Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
response.body().string();

OkHttpClient實現(xiàn)了Call.Factory辰妙,負(fù)責(zé)創(chuàng)建新的Call鹰祸,Call是OkHttp抽象出一個滿足請求的模型。

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

上面是OkHttpClient創(chuàng)建Call的過程密浑,可以看到蛙婴,RealCall是真正請求網(wǎng)絡(luò)的類。

  1. 同步請求網(wǎng)絡(luò):execute()
 @Override 
 public Response execute() throws IOException {
    synchronized (this) {
    // 從這里可以看到尔破,一個call只能執(zhí)行一次街图,如果需要多次執(zhí)行,可以使用clone()方法懒构。
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }
  1. 異步請求網(wǎng)絡(luò):enqueue(callBack)
@Override 
public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

上面的同步和異步的網(wǎng)絡(luò)請求做了四件事情:

  1. 判斷是否執(zhí)行過餐济,每個call請求只能請求一次,如果需要重復(fù)執(zhí)行可以使用clone()方法
  2. 獲取分發(fā)器dispatcher胆剧,執(zhí)行execute()或者enqueue(new AsyncCall(callback))
  3. 調(diào)用getResponseWithInterceptorChain()方法絮姆,獲取網(wǎng)絡(luò)請求的結(jié)果,異步請求都在AsyncCall中執(zhí)行了秩霍。
  4. 最后通知dispatcher執(zhí)行完畢篙悯。

上面的四個步驟中涉及到了Dispatcher和AsyncCall兩個類,這里做個簡單的介紹:

  • Dispatcher
    Disaptcher是一個分發(fā)器铃绒,它持有線程池鸽照、異步任務(wù)隊列和同步任務(wù)隊列,會依照不同的策略執(zhí)行颠悬。
  • AsyncCall
    矮燎,AsyncCall是RealCall的內(nèi)部類,持有RealCall的引用赔癌,實質(zhì)上是一個Runnable诞外,對RealCall做了一層封裝。

攔截器

OkHttpClient中真正發(fā)出網(wǎng)絡(luò)請求灾票,解析返回結(jié)果的步驟是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);
    return chain.proceed(originalRequest);
  }

Interceptor是OkHttp最核心的一個東西浅乔,采用了責(zé)任鏈的設(shè)計模式,不要誤以為它只是負(fù)責(zé)攔截請求進(jìn)行一些額外的處理(例如cookie)铝条,實際上它把時機(jī)的網(wǎng)絡(luò)請求靖苇、緩存、透明壓縮等功能都統(tǒng)一了起來班缰,每一個功能只是一個Interceptor贤壁,它們再連接成為Interceptor.Chain,環(huán)環(huán)相扣埠忘,最終圓滿完成一次網(wǎng)絡(luò)請求脾拆。interceptor的順序也很重要馒索,比如負(fù)責(zé)網(wǎng)絡(luò)請求的Interceptor必須放在最后,負(fù)責(zé)緩存策略的Inteceptor需要放到前面名船。

/**
 * A concrete interceptor chain that carries the entire interceptor chain: all application
 * interceptors, the OkHttp core, all network interceptors, and finally the network caller.
 */
public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final RealConnection connection;
  private final int index;
  private final Request request;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }

  @Override public Connection connection() {
    return connection;
  }

  public StreamAllocation streamAllocation() {
    return streamAllocation;
  }

  public HttpCodec httpStream() {
    return httpCodec;
  }

  @Override public Request request() {
    return request;
  }

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

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }
}

RealInterceptorChain的主要作用是傳遞參數(shù)和一個一個執(zhí)行攔截器绰上,并且設(shè)定一些條件,例如渠驼,每個攔截器只能調(diào)用一次chain.proceed這個方法蜈块。

OkHttp攔截器分析

先列出之前在getResponseWithInterceptorChain方法中添加的各Interceptor,概括一下它們分別負(fù)責(zé)什么功能:

  • client.interceptors()用戶自定義的Interceptor迷扇,能攔截到所有的請求
  • RetryAndFollowUpInterceptor負(fù)責(zé)失敗重連和重定向相關(guān)
  • BridgeInterceptor負(fù)責(zé)配置請求的頭信息百揭,比如Keep-Alive、gzip蜓席、Cookie等可以優(yōu)化請求
  • CacheInterceptor負(fù)責(zé)緩存管理器一,使用DiskLruCache做本地緩存,CacheStrategy決定緩存策略
  • ConnectInterceptor開始與目標(biāo)服務(wù)器建立連接厨内,獲得RealConnection
  • client.networkInterceptors()用戶自定義的Interceptor祈秕,僅在生產(chǎn)網(wǎng)絡(luò)請求時生效
  • CallServerInterceptor向服務(wù)器發(fā)出一次網(wǎng)絡(luò)請求的地方。

RetryAndFollowUpInterceptor

public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

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

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

      Response response = null;
      boolean releaseConnection = true;
      try {
        response = ((RealInterceptorChain) chain).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.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Request followUp = followUpRequest(response);

      if (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()), callStackTrace);
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;
      priorResponse = response;
    }
  }
  • streamAllocation對象是在這個Interceptor中創(chuàng)建的
  • 在followUpRequest方法中判斷了是否需要重定向以及是否需要重連踢步,需要重連時會返回一個request
  • request為null說明不需要重連,則直接返回response丑掺,否則輪訓(xùn)重走網(wǎng)絡(luò)請求的流程。

BridgeInterceptor

public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

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

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

    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);
      responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }
  • BridgeInteceptor的Interceptor基本上都是添加請求的頭信息述雾,例如啟動是否使用長連接Keep-Alive街州,設(shè)置Cookie,啟動壓縮與解壓gzip等玻孟。

CacheInterceptor

public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

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

    // 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;
  }
  • 首先根據(jù)request從cache中取response
  • 將request和respnse傳入CacheStrategy根據(jù)緩存策略(比如僅適用網(wǎng)絡(luò)加載唆缴,僅適用緩存,緩存時效等)得到有策略處理后的networkRequestcacheResponse
  • 若緩存策略要求僅從緩存中加載黍翎,且緩存未命中面徽,則本次請求失敗
  • 若緩存策略不要求僅從網(wǎng)絡(luò)獲取數(shù)據(jù),則直接返回緩存內(nèi)容
  • 以上條件不滿足匣掸,則把獲得response的任務(wù)交給下一個Chain趟紊,開始執(zhí)行網(wǎng)絡(luò)請求。
  • 得到網(wǎng)絡(luò)請求結(jié)果后碰酝,如果已經(jīng)有緩存了霎匈,則用最新的網(wǎng)絡(luò)數(shù)據(jù)更新緩存。
  • 最后將本次請求的結(jié)果response根據(jù)cacheRequest寫入緩存

ConnectInterceptor

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.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
  • 主要為下一步最終進(jìn)行網(wǎng)絡(luò)請求做鋪墊送爸,這里獲得了HttpCodecRealConnection铛嘱,然后將這些參數(shù)傳入下一個Chain暖释。
  • newStream方法中會去先嘗試從RealConnectionPool中尋找已經(jīng)存在的連接,若未命中則創(chuàng)建一個連接并與服務(wù)器握手對接墨吓。
  • 在完成連接后會將Socket對象通過Okio封裝成BufferedSource和BufferedSink球匕,并將兩者傳入HttpCodec,在下一步網(wǎng)絡(luò)請求時會用到帖烘。
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));

CallServerInterceptor

public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return what
      // we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      } else if (!connection.isMultiplexed()) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
        // being reused. Otherwise we're still obligated to transmit the request body to leave the
        // connection in a consistent state.
        streamAllocation.noNewStreams();
      }
    }

    httpCodec.finishRequest();

    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }
  • 可以發(fā)現(xiàn)具體的實現(xiàn)都交給了HttpCodec亮曹,它是對Http協(xié)議操作的一種抽象,針對HTTP/1.1與HTTP2有Http1CodecHttp2Codec兩種實現(xiàn)蚓让。
  • 方法的命名都是read和write乾忱,因為HttpCodec中最后的請求和響應(yīng)是由上一步封裝的BufferedSource和BufferedSink來完成的,sink負(fù)責(zé)輸出流历极,將寫入的數(shù)據(jù)交由socket發(fā)出窄瘟,source負(fù)責(zé)輸入流,從socket中讀取響應(yīng)數(shù)據(jù)趟卸。

OkHttp緩存相關(guān)

  • 緩存策略CacheStrategy
  • 連接池RealConnectPool

緩存策略

緩存策略主要是是CacheStrategy這個類蹄葱,依賴于本地緩存CacheHttp Header緩存配置。
Cache使用了DiskLruCache作為緩存容器锄列,以request.url作為key來存儲和讀取response图云,這是一種很常見的緩存方式。
Http HEader使用Http協(xié)議中約定的Cache-Control邻邮、Expires竣况、ETag、Last-Modified筒严、Date`等字段和服務(wù)端交互丹泉,由這些字段信息決定是否使用緩存,關(guān)于這些字段的含義可以查看Http協(xié)議中的定義鸭蛙。

public final class CacheStrategy {
  /** The request to send on the network, or null if this call doesn't use the network. */
  public final @Nullable Request networkRequest;

  /** The cached response to return or validate; or null if this call doesn't use a cache. */
  public final @Nullable Response cacheResponse;

  CacheStrategy(Request networkRequest, Response cacheResponse) {
    this.networkRequest = networkRequest;
    this.cacheResponse = cacheResponse;
  }

  /** Returns true if {@code response} can be stored to later serve another request. */
  public static boolean isCacheable(Response response, Request request) {
    // Always go to network for uncacheable response codes (RFC 7231 section 6.1),
    // This implementation doesn't support caching partial content.
    switch (response.code()) {
      case HTTP_OK:
      case HTTP_NOT_AUTHORITATIVE:
      case HTTP_NO_CONTENT:
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_NOT_FOUND:
      case HTTP_BAD_METHOD:
      case HTTP_GONE:
      case HTTP_REQ_TOO_LONG:
      case HTTP_NOT_IMPLEMENTED:
      case StatusLine.HTTP_PERM_REDIRECT:
        // These codes can be cached unless headers forbid it.
        break;

      case HTTP_MOVED_TEMP:
      case StatusLine.HTTP_TEMP_REDIRECT:
        // These codes can only be cached with the right response headers.
        // http://tools.ietf.org/html/rfc7234#section-3
        // s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
        if (response.header("Expires") != null
            || response.cacheControl().maxAgeSeconds() != -1
            || response.cacheControl().isPublic()
            || response.cacheControl().isPrivate()) {
          break;
        }
        // Fall-through.

      default:
        // All other codes cannot be cached.
        return false;
    }

    // A 'no-store' directive on request or response prevents the response from being cached.
    return !response.cacheControl().noStore() && !request.cacheControl().noStore();
  }

  public static class Factory {
    final long nowMillis;
    final Request request;
    final Response cacheResponse;

    /** The server's time when the cached response was served, if known. */
    private Date servedDate;
    private String servedDateString;

    /** The last modified date of the cached response, if known. */
    private Date lastModified;
    private String lastModifiedString;

    /**
     * The expiration date of the cached response, if known. If both this field and the max age are
     * set, the max age is preferred.
     */
    private Date expires;

    /**
     * Extension header set by OkHttp specifying the timestamp when the cached HTTP request was
     * first initiated.
     */
    private long sentRequestMillis;

    /**
     * Extension header set by OkHttp specifying the timestamp when the cached HTTP response was
     * first received.
     */
    private long receivedResponseMillis;

    /** Etag of the cached response. */
    private String etag;

    /** Age of the cached response. */
    private int ageSeconds = -1;

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

      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }

    /**
     * Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
     */
    public CacheStrategy get() {
      CacheStrategy candidate = getCandidate();

      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        // 禁止使用網(wǎng)絡(luò)請求摹恨,并且沒有渠道cache
        return new CacheStrategy(null, null);
      }

      return candidate;
    }

    /** 假設(shè)可以使用網(wǎng)絡(luò),返回一個策略. */
    private CacheStrategy getCandidate() {
      // No cached response.
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      // 緩存結(jié)果缺少握手信息娶视,直接走網(wǎng)絡(luò)請求流程.
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }

      // 根據(jù)當(dāng)前緩存Response的code值晒哄,http header等信息判斷本次緩存是否過期,是否可用肪获,如果不可用則直接走網(wǎng)絡(luò)請求流程
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }
 //本次請求header包含`no-cache`,`If-Modified-Since`,`If-None-Match`等字段直接走網(wǎng)絡(luò)請求
      
      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }

      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;
      CacheControl responseCaching = cacheResponse.cacheControl();
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }

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

    /**
     * Returns the number of milliseconds that the response was fresh for, starting from the served
     * date.
     */
    private long computeFreshnessLifetime() {
      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.maxAgeSeconds() != -1) {
        return SECONDS.toMillis(responseCaching.maxAgeSeconds());
      } else if (expires != null) {
        long servedMillis = servedDate != null
            ? servedDate.getTime()
            : receivedResponseMillis;
        long delta = expires.getTime() - servedMillis;
        return delta > 0 ? delta : 0;
      } else if (lastModified != null
          && cacheResponse.request().url().query() == null) {
        // As recommended by the HTTP RFC and implemented in Firefox, the
        // max age of a document should be defaulted to 10% of the
        // document's age at the time it was served. Default expiration
        // dates aren't used for URIs containing a query.
        long servedMillis = servedDate != null
            ? servedDate.getTime()
            : sentRequestMillis;
        long delta = servedMillis - lastModified.getTime();
        return delta > 0 ? (delta / 10) : 0;
      }
      return 0;
    }

    /**
     * Returns the current age of the response, in milliseconds. The calculation is specified by RFC
     * 2616, 13.2.3 Age Calculations.
     */
    private long cacheResponseAge() {
      long apparentReceivedAge = servedDate != null
          ? Math.max(0, receivedResponseMillis - servedDate.getTime())
          : 0;
      long receivedAge = ageSeconds != -1
          ? Math.max(apparentReceivedAge, SECONDS.toMillis(ageSeconds))
          : apparentReceivedAge;
      long responseDuration = receivedResponseMillis - sentRequestMillis;
      long residentDuration = nowMillis - receivedResponseMillis;
      return receivedAge + responseDuration + residentDuration;
    }

    /**
     * Returns true if computeFreshnessLifetime used a heuristic. If we used a heuristic to serve a
     * cached response older than 24 hours, we are required to attach a warning.
     */
    private boolean isFreshnessLifetimeHeuristic() {
      return cacheResponse.cacheControl().maxAgeSeconds() == -1 && expires == null;
    }

    /**
     * Returns true if the request contains conditions that save the server from sending a response
     * that the client has locally. When a request is enqueued with its own conditions, the built-in
     * response cache won't be used.
     */
    private static boolean hasConditions(Request request) {
      return request.header("If-Modified-Since") != null || request.header("If-None-Match") != null;
    }
  }
}
  • 首先在Factory方法中獲取它所依賴的參數(shù)寝凌,就是我們之前提到過的Cache中渠道的緩存和Http Header配置信息。
  • 然后進(jìn)入比較核心的方法getCandidate孝赫,在這里會根據(jù)之前拿到的依賴參數(shù)通過各種if判斷返回不同的CacheStrategy對象
  • 本職上其實返回不同的networkRequestcacheResponse硫兰,這樣上層只需要關(guān)注這兩個參數(shù)就知道下一步該如何做處理,復(fù)雜的判斷都封裝到了CacheStrategy對外透明寒锚,具體判斷過程在代碼中做了注釋劫映。

連接池

okHttp利用連接池來復(fù)用連接违孝,避免反復(fù)握手建立連接,并且具備在合適的時候揮手連接的能力泳赋,這也是okhttp設(shè)計出彩的地方之一雌桑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市祖今,隨后出現(xiàn)的幾起案子校坑,更是在濱河造成了極大的恐慌,老刑警劉巖千诬,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耍目,死亡現(xiàn)場離奇詭異,居然都是意外死亡徐绑,警方通過查閱死者的電腦和手機(jī)邪驮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來傲茄,“玉大人毅访,你說我怎么就攤上這事∨陶ィ” “怎么了喻粹?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長草巡。 經(jīng)常有香客問我守呜,道長,這世上最難降的妖魔是什么山憨? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任查乒,我火速辦了婚禮,結(jié)果婚禮上萍歉,老公的妹妹穿的比我還像新娘。我一直安慰自己档桃,他們只是感情好枪孩,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著藻肄,像睡著了一般蔑舞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嘹屯,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天攻询,我揣著相機(jī)與錄音,去河邊找鬼州弟。 笑死钧栖,一個胖子當(dāng)著我的面吹牛低零,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拯杠,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼掏婶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了潭陪?” 一聲冷哼從身側(cè)響起雄妥,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎依溯,沒想到半個月后老厌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡黎炉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年枝秤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拜隧。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡宿百,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出洪添,到底是詐尸還是另有隱情垦页,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布干奢,位于F島的核電站痊焊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏忿峻。R本人自食惡果不足惜薄啥,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逛尚。 院中可真熱鬧垄惧,春花似錦、人聲如沸绰寞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽滤钱。三九已至觉壶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間件缸,已是汗流浹背铜靶。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留他炊,地道東北人争剿。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓已艰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秒梅。 傳聞我的和親對象是個殘疾皇子旗芬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容

  • OkHttp3的使用 1、創(chuàng)建OkHttpClient捆蜀;2疮丛、創(chuàng)建Request請求對象;3辆它、OkHttpClien...
    Samuel_Tom閱讀 307評論 0 0
  • 在OkHttp3中誊薄,其靈活性很大程度上體現(xiàn)在可以 intercept 其任意一個環(huán)節(jié),而這個優(yōu)勢便是okhttp3...
    Jdqm閱讀 40,667評論 7 71
  • 看了有一段時間的OkHttp3的源碼了锰茉。今天動筆開始寫一寫呢蔫,本篇文章只是簡單的寫一下OkHttp3的一個過程。(以...
    lonamessi閱讀 613評論 0 1
  • 公公來給我看孩子了飒筑。剛開始我還有些懷疑他究竟能不能給我?guī)Ш煤⒆悠酰吘顾囊簧谄牌诺恼疹櫹拢呀?jīng)過成了筷子遞不到手...
    z咩咩羊閱讀 756評論 13 15
  • 剛才看到一對祖孫协屡,奶奶背著一個小包俏脊,孫女背著書包,穿著拉丁舞的裙子肤晓,估計是剛剛上完舞蹈課回來爷贫。孫女看到了奶奶,離得...
    佚名的小行星閱讀 126評論 0 0