Android每週一輪子:OkHttp

前言

Okhttp

前兩篇的文章講解了Volley,HttpURLConnection,今天是對於OKHttp的分析,分析完成將會分析OKIO和retrofit,試圖經過這一系列分析,來對Android的網絡庫的實現有足夠充分的瞭解,固然這裏的分析徹底是脫離了對於項目的具體針對性實踐,所以在一些細節上會有說欠缺,這也將會是接下來源碼分析的下一步,從項目中的應用和實踐優化做爲出發點。java

基礎使用

  • 建立OKHttpClient和構造請求
OkHttpClient client = new OkHttpClient.Builder()
        .readTimeout(30, TimeUnit.SECONDS)
        .build();

Request request = new Request.Builder()
        .header("User-Agent", "OkHttp Headers.java")
        .url("http://www.baidu.com")
        .build();
複製代碼
  • 發起異步請求
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, okhttp3.Response response) throws IOException {
    }
});
複製代碼
  • 發起同步請求
okhttp3.Response response = client.newCall(request).execute();
複製代碼

OkHttp的使用首先經過OkHttp的生成器來根據咱們本身的配置建立一個OKHttpClient,而後構造一個請求,設置請求的header,url,請求的body,OkHttp提供了同步和異步兩種請求執行方式。這裏能夠根據本身的需求選擇一個合適的方式。web

實現分析

按照Android每週一輪子的寫做風格,基礎使用做爲一個引子,幫助咱們迅速的切入框架的執行流程,快速的理清整個調用鏈路,瞭解該框架的實現,此處咱們仍是延續這種方式。針對上面的使用流程,逐步分析。設計模式

  • newCall
public Call newCall(Request request) {
  return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製代碼
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  RealCall call = new RealCall(client, originalRequest, forWebSocket);
  call.eventListener = client.eventListenerFactory().create(call);
  return call;
}
複製代碼

根據建立的請求,構造一個RealCall實例,同時爲該RealCall對象設置事件監聽器。在EventListener中,定義了許多的函數,能夠監控到網絡請求的整個生命週期,包括DNS開始查找,DNS查找結束,鏈接的開始創建,鏈接創建失敗等等。緩存

  • enqueue

在鏈接創建以後,在異步請求中,將會調用enqueue方法。bash

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

在該方法中,會調用OkHttpClient的Dispatcher的enqueue方法。在建立OkHttpClient的時候,但開發者未設定Dispatcher時,會默認建立一個Dispatcher。這裏按照默認的實現代碼進行分析。websocket

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

加入到Dispatchr之中以後,對於請求進行判斷,判斷是否超過了最大請求數目,是否超過了單個host共享下的請求數目,若是超過了則將其加入到準備執行隊列之中。若是請求數目過多的時候,將其放置在一個準備隊列之中。對於ArrayDeque的數據結構解釋文末。網絡請求執行,是經過線程池來實現的,對於線程池的建立和具體的執行問題,將在文末具體分析。cookie

在Dispatcher中用來管理請求的數據結構,分別爲正在執行的異步請求隊列,正在準備的異步請求隊列,正在執行的同步請求隊列。網絡

Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
複製代碼
  • execute

對於請求的具體執行過程,在AsyncCall的execute方法中。數據結構

final class AsyncCall extends NamedRunnable {

  @Override protected void execute() {
    boolean signalledCallback = false;
    try {
      Response response = getResponseWithInterceptorChain();
         .....
    } catch (IOException e) {
      ......
    } finally {
      client.dispatcher().finished(this);
    }
  }
}
複製代碼

這裏首先得到響應結果,在得到響應結果的時候,可能會出現一些異常狀況,這裏會catch到異常,會回調事件監聽器的一些回調函數。對於獲取請求響應結果的核心調用就是getResponseWithInterceptorChain經過層層責任鏈的執行來得到最終的請求結果。框架

  • getResponseWithInterceptorChain
Response getResponseWithInterceptorChain() throws IOException {
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(retryAndFollowUpInterceptor);
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));

  Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
      originalRequest, this, eventListener, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());

  return chain.proceed(originalRequest);
}
複製代碼

構建一個Interceptor列表,在其中添加用戶設置的攔截器,框架自身的緩存,網絡鏈接等等鏈接器,而後根據這一系列的攔截器構建出一個Interceptor.Chain實例對象,以後調用其processed方法。

  • 責任鏈的執行(proceed)

proceed的方法是網絡請求執行的核心

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

  calls++;

  //若是已經有存在的流,肯定進入的請求可使用它
  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 (this.httpCodec != null && calls > 1) {
    throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
        + " must call proceed() exactly once");
  }

  // 調用該鏈中的下一個攔截器
  RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
      connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
      writeTimeout);
  Interceptor interceptor = interceptors.get(index);
  Response response = interceptor.intercept(next);

//確認攔截器是否調用了chain的proceed方法。
  if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
    throw new IllegalStateException("network interceptor " + interceptor
        + " must call proceed() exactly once");
  }

  // 判斷響應是否爲空,爲空拋出異常
  if (response == null) {
    throw new NullPointerException("interceptor " + interceptor + " returned null");
  }

  //判斷響應體是否爲空,拋出異常返回內容體爲空
  if (response.body() == null) {
    throw new IllegalStateException(
        "interceptor " + interceptor + " returned a response with no body");
  }

  return response;
}
複製代碼

責任鏈的處理函數執行的操做大體爲對於一些狀態進行判斷,而後取其中的攔截器構造一個攔截鏈實例,而後執行攔截器的攔截方法,在其攔截方法中還會繼續調用建立的新的RealChain方法的proceed方法,經過這種遞歸的方式來將數據進行層層包裝處理,最終將數據丟回。對於其中的每個攔截器在完成整個網絡請求的過程發揮了關鍵的做用。接下來從第一個攔截器開始逐層次進行分析。

RetryAndFollowUpInterceptor

這個攔截器能夠將其從失敗和有必要的重定向中恢復。多少重定向和受權須要嘗試,Chrome會跟隨21次重定向,火狐,curl,wget會跟隨20次,Safari會跟隨16次,Http1.0推薦5次,這裏則爲20次。

經過一個While true死循環來進行重試操做,在這裏創建StreamAllocation,而後建立新的攔截器鏈實例,而後調用其processed方法,等待返回結果回來。該攔截器在最外層,接下來的攔截器返回的響應結果,最終都會返回到這裏被處理。

@Override public Response intercept(Chain chain) throws IOException {

    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

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

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
           .....
      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
     
      //從失敗的路由中恢復
        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
      
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }
        

      Request followUp = followUpRequest(response, streamAllocation.route());

      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()), call, eventListener, callStackTrace);
        this.streamAllocation = streamAllocation;
      } 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

應用代碼和網絡代碼之間的橋樑,用來根據用戶的一個請求構建一個網絡請求,最後,根據網絡響應來構建一個用戶響應。根據設置的一些頭部參數進行相應的處理。好比GZIP壓縮問題等等。

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

//Gzip 壓縮轉換
  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);
    String contentType = networkResponse.header("Content-Type");
    responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
  }

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

做爲應用層和網絡層的橋樑,其主要目的是在請求到達網絡層時,對網絡請求進行包裝和在網絡請求的響應結果回來時,對響應結果進行包裝,轉到用戶層。

CacheInterceptor

用來檢測緩存中是否有數據,有檢測無變化返回,不然網絡請求後存放。

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

  long now = System.currentTimeMillis();
  //獲取請求的cache策略
  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()); 
  }

  // 若是咱們既沒法進行網絡請求又無緩存,返回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 (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());
    }
  }

  // 獲取請求響應後,根據cache狀態,對cache內容進行相應的處理
  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();

      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;
}
複製代碼

ConnectInterceptor

根據傳遞的數據,尋找並創建一個健康的鏈接

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

  boolean doExtensiveHealthChecks = !request.method().equals("GET");
  HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);

//獲取一個健康鏈接
  RealConnection connection = streamAllocation.connection();

  return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
複製代碼

尋找一個健康的鏈接,而後將鏈接傳遞給下一個攔截器進行處理。

CallServerInterceptor

數據的寫入過程,也就是發起網絡請求和Server進行交互的過程,而後返回請求數據,這個時候再是層層的返回調用棧,將數據倒回去,而後進行層層的處理。

@Override 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();

//請求Event記錄
  realChain.eventListener().requestHeadersStart(realChain.call());
  httpCodec.writeRequestHeaders(request);
  realChain.eventListener().requestHeadersEnd(realChain.call(), request);

  Response.Builder responseBuilder = null;
  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    
    if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
      httpCodec.flushRequest();
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(true);
    }

    if (responseBuilder == null) {
 
  //開始寫入網絡請求
      realChain.eventListener().requestBodyStart(realChain.call());
      long contentLength = request.body().contentLength();
      CountingSink requestBodyOut =
          new CountingSink(httpCodec.createRequestBody(request, contentLength));
      BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
      request.body().writeTo(bufferedRequestBody);
      bufferedRequestBody.close();

      realChain.eventListener()
          .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
    } else if (!connection.isMultiplexed()) {
      streamAllocation.noNewStreams();
    }
  }

//完成網絡請求
  httpCodec.finishRequest();

//讀取響應內容
  if (responseBuilder == null) {
    realChain.eventListener().responseHeadersStart(realChain.call());
    responseBuilder = httpCodec.readResponseHeaders(false);
  }

//獲取響應體
  Response response = responseBuilder
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

//根據Response Code作相應的處理
  int code = response.code();
  if (code == 100) {
    responseBuilder = httpCodec.readResponseHeaders(false);

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

    code = response.code();
  }

  realChain.eventListener()
          .responseHeadersEnd(realChain.call(), response);

//Code值101是協議升級代碼,這裏首個判斷爲向websocket的升級
  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();
  }

  return response;
}
複製代碼

OKHttp實現原理分析

小結

本篇文章算不上對於源碼的深度剖析,大概仍是停留在表層的代碼邏輯調用,對於其中用到的設計模式,各類技術,和其相比於其它網絡庫的優點所在,這裏暫時都沒有作分析,因爲本週時間比較近,因此對於性能,優點特徵,將會在接下來的一篇深度展開分析。同時下一篇也將會做爲對OkIO分析的一個引子。

相關文章
相關標籤/搜索