【源碼解析】OkHttp的工做原理

引言

OkHttp做爲優秀的網絡請求框架,已經獲得了廣大Android開發者的承認。對於它的使用方法,你們也是很是的熟悉。例如同步同步請求、異步請求等,均可以使用很簡潔的邏輯來實現。因爲OkHttp已經封裝了繁瑣複雜的請求邏輯,開發者只須要使用其提供的API就能輕鬆的實現網絡請求,這使得開發者能將更多的精力放到業務開發上,提升了工做效率。html

可是,做爲一位有追求的Android開發者,不能一味的衣來伸手飯來張口。雖然不至於要本身動手,豐衣足食,可是咱們也要作到知其然更知其因此然,從優秀的基礎框架中學習其設計思想、架構思想,這對咱們的成長也是有很是大的幫助的。設計模式

下面咱們就以OkHttp爲例,從源碼的角度對其總體流程進行分析,以便能從中學習到其設計思路與工做原理。緩存

總體架構

下面是OkHttp發起請求與數據響應的流程圖(參考拆輪子系列:拆 OkHttp這篇文章畫的)。bash

總體來講,是OkHttpClient經過newCall方法,進而觸發RealCall的execute(同步)、enquene(異步)方法,通過一系列的interceptors(攔截器),最後通過IO操做發起請求獲得Response(響應數據)。markdown

下面針對上述OkHttp的總體工做流程,從源碼的角度分析其中的原理,咱們首先從同步請求提及。cookie

同步請求

OkHttp的同步請求示例以下所示。網絡

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url(url)
    .build();

Response response = client.newCall(request).execute();
return response.body().string();
複製代碼

OkHttpClient有個內部類Builder,它的build方法能夠生成一個OkHttpClient實例。但OkHttpClient提供了一個構造函數,讓咱們可使用默認的配置來建立OkHttpClient實例。架構

OkHttpClient實例的newCall方法接收一個Request對象,該對象由Request的內部類Builder構造出來。框架

public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false);
}
複製代碼

newCall方法中經過RealCall又調用了newRealCall方法,並返回RealCall對象。也就是說,實際上執行的是RealCallexecute方法。異步

public Response execute() throws IOException {
    synchronized (this) { // 1
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
    }
    captureCallStackTrace();
    timeout.enter();
    eventListener.callStart(this);
    try {
        client.dispatcher().executed(this);
        Response result = getResponseWithInterceptorChain(); // 2
        if (result == null) throw new IOException("Canceled");
        return result;
    } catch (IOException e) {
        e = timeoutExit(e);
        eventListener.callFailed(this, e);
        throw e;
    } finally {
        client.dispatcher().finished(this);
    }
}
複製代碼

一、每個RealCall對象的execute方法只能執行一次,屢次執行會拋出IllegalStateException異常。

二、經過執行getResponseWithInterceptorChain方法同步返回了Response對象。從上面的總體架構可知,getResponseWithInterceptorChain方法是OkHttp發起網絡請求的重點部分,咱們接着往下面看。

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

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

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

getResponseWithInterceptorChain方法中的往List中加了不少攔截器,最後經過構造了RealInterceptorChain對象,並執行它的proceed方法返回一個Response對象。該方法的工做流程以下圖所示。

這就是所謂的責任鏈模式,每個節點負責本身的一部分工做,最後組裝成一個完整的請求對象發送網絡請求並返回Response。對於其中的每個節點(攔截器),咱們經過源碼進一步分析其中的細節。

RealInterceptorChain

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
    RealConnection connection) throws IOException {
  ...省略
  
  //生成下一個節點
  RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
      connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
      writeTimeout);
  Interceptor interceptor = interceptors.get(index);
  Response response = interceptor.intercept(next);

  ...省略

  return response;
}
複製代碼

RealInterceptorChainproceed方法主要作了兩個操做:

一、生成了下一個節點對象next,類型爲RealInterceptorChain

二、調用了當前攔截器的intercept方法,並將next對象傳遞給該攔截器。從上述流程圖中,咱們認定當前攔截器是RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor

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) { //這裏死循環的意思是會一直重試,直到遇到return或者拋出異常後纔會跳出
    if (canceled) {
      streamAllocation.release();
      throw new IOException("Canceled");
    }

    Response response;
    boolean releaseConnection = true;
    try {
      response = realChain.proceed(request, streamAllocation, null, null);
      releaseConnection = false;
    } catch (RouteException e) {
      ...
      continue;
    } catch (IOException e) {
      ...
      continue;
    } finally {
      ...
    }

    ...

    Request followUp;
    try {
      followUp = followUpRequest(response, streamAllocation.route());
    } catch (IOException e) {
      streamAllocation.release();
      throw e;
    }

    if (followUp == null) { //不須要重試,直接返回Response對象
      streamAllocation.release();
      return response;
    }

    closeQuietly(response.body());

    if (++followUpCount > MAX_FOLLOW_UPS) { //重試次數加1,重試次數大於最大次數,釋放資源
      streamAllocation.release();
      throw new ProtocolException("Too many follow-up requests: " + followUpCount);
    }

    //繼續重試,直到重試次數大於最大重試次數
    request = followUp;
    priorResponse = response;
  }
}
複製代碼

RetryAndFollowUpInterceptor,從它的名字也能看出來,其主要目的是爲失敗重試和重定向而工做的。因此它的intercept方法主要利用了while循環進行屢次的請求重試,只有當重試次數大於最大重試次數時纔會跳出while循環。

BridgeInterceptor

RetryAndFollowUpInterceptorintercept方法將沿着責任鏈,從而執行到了BridgeInterceptorintercept方法。

public Response intercept(Chain chain) throws IOException {
  Request userRequest = chain.request();
  Request.Builder requestBuilder = userRequest.newBuilder(); //構造了一個新的Request.Builder對象

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

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

BridgeInterceptor的攔截方法要作的事情並很少,主要目的是判斷網絡請求Request對象header對象的字段爲空時,構造一個默認對象,並且在網絡數據返回後,將返回的Response對象進一步的封裝並返回。

CacheInterceptor

BridgeInterceptorintercept方法又將網絡請求繼續分發,它的下一個攔截器則是CacheInterceptor。從CacheInterceptor的名字與其註釋可知,該攔截器的主要功能是獲取緩存的Response以及將網絡請求返回Response存儲到緩存中。

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

  ...省略

  //沒有網絡,則使用緩存
  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());
    }
  }

  // 有緩存,若是返回的Response和緩存比對後沒有改變,則返回緩存
  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) { //緩存改變了,則從新寫入新的Response到緩存
    if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
      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;
}
複製代碼

從上面的interceptor方法中能夠看出,該方法主要作了如下事情。

一、判斷是否須要返回緩存。(通常無網絡時會返回緩存)

二、有網絡的狀況下,Request請求正常發送,判斷返回的Response內容是否有更新。沒有更新,則返回緩存內容;有更新,則返回新的Response,並將新的Response內容寫入到緩存中。

ConnectInterceptor

CacheInterceptor攔截器的下一個責任鏈節點是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, chain, doExtensiveHealthChecks);
  RealConnection connection = streamAllocation.connection();

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

ConnectInterceptor攔截器的intercept方法主要負責創建鏈接,也就是建立了HttpCodec對象。HttpCodec是一個接口類,有兩個實現類,分別爲Http1CodecHttp2CodecHttp1Codec使用的是Http1.0版本的協議發送網絡請求,而Http2Codec使用的是Http2.0版本的協議發送網絡請求。從HttpCodec接口方法和其實現邏輯來看,其中主要封裝了Java的IO操做,經過stream字節流參與網絡請求的發送與接收過程。

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

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

  ...省略
  return response;
}
複製代碼

CallServerInterceptor做爲最後一個攔截器,其主要利用了HttpCodecreadResponseHeaders方法獲取Response數據,並將Response返回。

至此咱們分析完了OkHttp中關於同步強求的總體流程。其中特別重要的是OkHttp中的攔截器分層原理,也就是所謂的責任鏈設計模式。OkHttp的請求會將通過攔截器一層層的分發,直到有攔截器將Response進行返回。而返回的Response也會傳遞到以前的每個攔截器,每個攔截器對該Response進行加工封裝,最後造成一個統一的Response對象返回。

異步請求

OkHttp的異步請求示例以下所示。

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url(url)
    .build();

okHttpClient.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        //請求失敗的回調
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        //請求成功的回調
    }
});
複製代碼

異步請求調用了okHttpClient.newCall方法,從上面的同步請求分析能夠知道,okHttpClient.newCall返回一個RealCall對象,也就是說異步請求其實調用的是RealCall的enqueue方法。

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

client.dispatcher返回一個Dispatcher對象。該對象的功能如其名字同樣,它會將請求進行管理並分發,其中的enqueue方法以下所示。

void enqueue(AsyncCall call) {
  synchronized (this) {
    readyAsyncCalls.add(call);
  }
  promoteAndExecute();
}
複製代碼

enqueue方法將AsyncCall對象加入了readyAsyncCalls隊列,而後執行promoteAndExecute方法。

private boolean promoteAndExecute() {
  assert (!Thread.holdsLock(this));

  List<AsyncCall> executableCalls = new ArrayList<>();
  boolean isRunning;
  synchronized (this) {
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall asyncCall = i.next();

      if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
      if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.

      i.remove();
      executableCalls.add(asyncCall);
      runningAsyncCalls.add(asyncCall);
    }
    isRunning = runningCallsCount() > 0;
  }

  for (int i = 0, size = executableCalls.size(); i < size; i++) {
    AsyncCall asyncCall = executableCalls.get(i);
    asyncCall.executeOn(executorService());
  }

  return isRunning;
}
複製代碼

promoteAndExecute方法執行時,會比較readyAsyncCalls隊列中的請求對象個數是否大於maxRequests的值,若是readyAsyncCalls隊列的請求對象個數小於maxRequests,則將這些請求對象加入到executableCalls列表中,而後遍歷每個AsyncCall對象,執行它的executeOn方法。

void executeOn(ExecutorService executorService) {
  assert (!Thread.holdsLock(client.dispatcher()));
  boolean success = false;
  try {
    executorService.execute(this);
    success = true;
  } catch (RejectedExecutionException e) {
    InterruptedIOException ioException = new InterruptedIOException("executor rejected");
    ioException.initCause(e);
    eventListener.callFailed(RealCall.this, ioException);
    responseCallback.onFailure(RealCall.this, ioException);
  } finally {
    if (!success) {
      client.dispatcher().finished(this); // This call is no longer running!
    }
  }
}
複製代碼

executeOn方法執行的邏輯是:經過ExecutorService(線程池)執行每個AsyncCall請求對象,因此相應的AsyncCall對象的run方法會被執行,而run方法調用了execute方法。

protected void execute() {
    boolean signalledCallback = false;
    timeout.enter();
    try {
      Response response = getResponseWithInterceptorChain();
      if (retryAndFollowUpInterceptor.isCanceled()) {
        signalledCallback = true;
        responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
      } else {
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);
      }
    } catch (IOException e) {
      e = timeoutExit(e);
      if (signalledCallback) {
        // Do not signal the callback twice!
        Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
      } else {
        eventListener.callFailed(RealCall.this, e);
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
      client.dispatcher().finished(this);
    }
  }
}
複製代碼

execute方法中,經過getResponseWithInterceptorChain方法返回Response對象。這裏的getResponseWithInterceptorChain方法執行過程在同步請求時已經分析完了,這裏再也不重複說明。

至此,異步請求流程也已經分析完畢。和同步請求流程相對比,異步請求流程比同步流程多了一步也就是將請求對象進行分發並放到線程池中去執行,至於攔截器分層、發起網絡請求、數據返回流程等都是同樣的。

總結

OkHttp做爲一個優秀的網絡請求庫,其主要運用責任鏈模式、分層思想、單一職責思想等設計模式與思想,經過每一層攔截器對請求Request和返回Response進行封裝。隱藏了繁瑣複雜的網絡請求流程,提供簡潔的API供開發者調用。經過源碼解析,咱們瞭解並學習了其中的設計原理與總體流程,作到了知其然也知其因此然。

相關文章
相關標籤/搜索