源碼分析:Android Okhttp源碼淺析(二)

承接的Okhttp的框架分析。

“源碼分析:Android Okhttp源碼淺析(一)”

我們知道了,攔截器都是鏈式調用的。
當有請求發送時,每個攔截器都會處理請求;然後,扔給下一個攔截器來處理,直到返回結果;
然後,把結果,再一次的扔到上一個的攔截器來處理結果。最後把Response處理完成的Response返回。

看下官方圖
在這裏插入圖片描述

下面,我們就看下每個攔截器的作用。

  • 我們自己的應用攔截器
  • RetryAndFollowUpInterceptor
  • BridgeInterceptor
  • CacheInterceptor
  • ConnectInterceptor
  • NetworkInterceptors
  • 我們自己的networkInterceptors
  • CallServerInterceptor

自己的應用攔截器

我們自己的攔截器放到後面。先看下系統的攔截器

RetryAndFollowUpInterceptor

@Override public Response intercept(Chain chain) throws IOException {
	//拿到請求配置的Request對象。
    Request request = chain.request();
	//創建StreamAllocation。這個在後面的攔截器會用到
    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 {
		//調用下一個RealInterceptorChain的proceed()方法
		//獲取下一個攔截器的響應
        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;
    }
  }

就是,當路由錯誤,或者連接異常後進行重試。

BridgeInterceptor

@Override 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");
      }
    }
	//host信息
    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
		//設置Keep-Alive。保持連接
      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;
		//使用gzip壓縮
      requestBuilder.header("Accept-Encoding", "gzip");
    }
	//添加cookie
    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());
	//接收cookie
    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)) {
		//當返回的結果是gzip的話,就去解壓
      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();
  }

到這裏可以看出,BridgeInterceptor攔截器的作用就是處理header頭信息,cookie。在請求之前和響應之後,處理一些頭信息(使用gzip進行壓縮,解壓等)。

CacheInterceptor

從名字判斷,這個攔截器是處理Http請求的緩存策略的。下面具體看下

 @Override public Response intercept(Chain chain) throws IOException {
	//根據Request獲取緩存
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
	//【1】緩存策略,通過請求跟獲取的緩存來判斷。下面分析
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
	//通過緩存策略,得到的獲取網絡請求
    Request networkRequest = strategy.networkRequest;
	//通過緩存策略,得到的獲取緩存響應
    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.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
	//如果沒有網絡的請求,又沒有緩存。直接返回504
    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.
	//如果得到了網絡請求,緩存也不爲null。就合併網絡跟緩存
    if (cacheResponse != null) {
		//如果是獲取的數據沒有修改(304)
      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 (HttpHeaders.hasBody(response)) {
		//通過maybeCache來判斷是否需要緩存
      CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
      response = cacheWritingResponse(cacheRequest, response);
    }

    return response;
  }

這個主要是使用緩存策略判斷,是否使用緩存還是使用網絡的請求。
這裏,我們發現如果請求不可用/緩存可用的話,直接返回緩存,不會調用下一個攔截器。

我們發現裏面的判斷都是跟CacheStrategy類有很大的關係,我們看下new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get()這個方法。

public CacheStrategy get() {
		//拿到CacheStrategy
      CacheStrategy candidate = getCandidate();
		
      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        // We're forbidden from using the network and the cache is insufficient.
        return new CacheStrategy(null, null);
      }

      return candidate;
    }

...

/** Returns a strategy to use assuming the request can use the network. */
    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();
		//請求頭noCache或者包含"If-Modified-Since"等等情況
      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);
    }

這個裏面其實就是根據緩存的各種情況,來判斷緩存是否可用。返回緩存策略。

  • cacheResponse緩存爲null的話,直接使用網絡請求。
  • https並且cacheResponse.handshake() == null的直接使用網絡請求
  • isCacheable(),緩存數據的相應碼來判斷,是否直接使用網絡請求
  • 通過請求頭裏是否包含(“If-None-Match”/“If-None-Match”)來判斷是否使用網絡請求

通過上面一些列判斷來返回緩存策略CacheStrategy類

CacheInterceptor來根據這個緩存策略,來決定使用緩存還是執行網絡的請求。這個攔截器主要就是通過緩存的數據跟新的網絡請求來判斷,是否執行新的網絡請求。

ConnectInterceptor

可以看到這個攔截器裏面的代碼非常少。

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 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 httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
	//獲取一個鏈接
    RealConnection connection = streamAllocation.connection();
	//調用下一個攔截器
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

這個主要是:

  • 1,獲取StreamAllocation對象
  • 2,通過StreamAllocation獲取HttpCodec
  • 3,通過StreamAllocation獲取一個鏈接
  • 調用下一個攔截器

我們發現所有的操作都集中到StreamAllocation裏了,先看下StreamAllocation#newStream()方法

StreamAllocation#newStream()方法

public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {	
	//設置我們配置的超時時間
    int connectTimeout = client.connectTimeoutMillis();
    int readTimeout = client.readTimeoutMillis();
    int writeTimeout = client.writeTimeoutMillis();
	//是否鏈接重試
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
		//在連接池中獲取一個連接或者創建一個新的連接
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
      HttpCodec resultCodec = resultConnection.newCodec(client, this);

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

通過findHealthyConnection()方法找到一個新的連接。

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws IOException {
    while (true) {
		//調用findConnection方法獲取
      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;
    }
  }

這個方法,主要是通過**findConnection()**方法一個連接。然後,檢查連接的可用性,不可用就關閉。

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
    Route selectedRoute;
    synchronized (connectionPool) {
    

      // Attempt to use an already-allocated connection.
      RealConnection allocatedConnection = this.connection;
      if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
        return allocatedConnection;
      }

      // Attempt to get a connection from the pool.
      Internal.instance.get(connectionPool, address, this, null);
      if (connection != null) {
        return connection;
      }

      selectedRoute = route;
    }

    // If we need a route, make one. This is a blocking operation.
    if (selectedRoute == null) {
      selectedRoute = routeSelector.next();
    }

    RealConnection result;
    synchronized (connectionPool) {

      Internal.instance.get(connectionPool, address, this, selectedRoute);
      if (connection != null) return connection;

      route = selectedRoute;
      refusedStreamCount = 0;
		//創建一個新的連接
      result = new RealConnection(connectionPool, selectedRoute);
		//放到連接池?
      acquire(result);
    }

    // Do TCP + TLS handshakes. This is a blocking operation.
	//核心操作,建立連接
    result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      // 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.
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

    return result;
  }

我們發現,這裏是聯網的核心。獲取一個連接,並建立連接。
到這裏streamAllocation.newStream()方法就分析完了,有點長

這裏是在連接池中找一個可用的連接或者重新建一個連接。調用下一個攔截器

CallServerInterceptor

@Override public Response intercept(Chain chain) throws IOException {
	//這個就是上個攔截器創建出來的
    HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
    StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
    Request request = chain.request();

    long sentRequestMillis = System.currentTimeMillis();
	//寫入請求頭信息
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
	//如果有請求體,寫入請求體
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
     
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      // Write the request body, unless an "Expect: 100-continue" expectation failed.
      if (responseBuilder == null) {
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      }
    }
	
	//結束請求
    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();
	//websocket 或者獲取的code是101。沒有響應體
    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;
  }

這裏的操作就是:

  • 寫入請求頭,請求體
  • 結束請求
  • 獲取請求頭,請求體,發送時間,接收時間等

參考

https://square.github.io/okhttp/interceptors/
https://www.jianshu.com/p/4510ae14dbe9

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章