OkHttp源碼深入解讀

簡介

目前在HTTP協(xié)議請求庫中,OKHttp應(yīng)當(dāng)是非常火的,使用也非常的簡單。網(wǎng)上有很多文章寫了關(guān)于OkHttp的特點(diǎn)以及使用,而本章主要帶領(lǐng)大家閱讀OkHttp的源碼,讓大家對OKhttp的工作原理有所了解,當(dāng)然源碼的代碼量是非常大的,這里我只是抓住主線和重點(diǎn)部分,至于細(xì)節(jié)部分,大家隨著我拋出的線來跟進(jìn)基本是沒什么問題的。這篇文章要干嘛,引用一句話:

read the fucking source code

目錄:
  • OkHttp介紹
  • 粗繪請求流程
  • RealCall方法execute
  • getResponseWithInterceptorChain調(diào)用鏈
  • RetryAndFollowUpInterceptor
  • ConnectInterceptor獲取連接
  • CallServerInterceptor網(wǎng)絡(luò)請求
  • RealConnection
  • StreamAllocation
  • HttpCodec(Http1Codec)
  • 同步/異步請求

OkHttp介紹:

特點(diǎn):

  • 支持連接同一地址的連接共享同一個socket(前提服務(wù)端支持)
  • 支持Socket連接池,減少請求延遲
  • 使用攔截器模式,將流程拆分
  • 透明的GZIP壓縮

粗繪請求流程

注意:這里我選擇OkHttp源碼版本是 3.8.0。為了方便大家能夠和文章同步,最好保持版本一致,我看過老版本和新的版本還是有點(diǎn)不同的。

官網(wǎng)給出的示例

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

我們就從這里入口,來一步一步的跟進(jìn)。

  1. 首先是創(chuàng)建一個OkHttpClient,Http請求工廠,也就是只要需要發(fā)Http請求,那都得找他。內(nèi)部當(dāng)然后很多的成員變量和方法,這里我們先不做介紹,等用到時再解釋。
  2. 我們繼續(xù)看client.newCall(request)。找到源碼
  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

很簡單,創(chuàng)建了一個RealCall,這里我就稱為一個請求。Request不說大家能理解,里面封裝了各種請求的信息。創(chuàng)建過程也很簡單,做一些成員變量賦值和初始化。

  RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    final EventListener.Factory eventListenerFactory = client.eventListenerFactory();

    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);

    // TODO(jwilson): this is unsafe publication and not threadsafe.
    this.eventListener = eventListenerFactory.create(this);
  }

這里注意retryAndFollowUpInterceptor;變量,后面會用到。

  1. 調(diào)用了RealCall的execute()方法并返回Response結(jié)果。

RealCall方法execute

前面我們知道了大致的請求流程,下面我們重點(diǎn)看

  @Override public Response execute() throws IOException {
    synchronized (this) {
      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. 首先我們發(fā)現(xiàn)try的前后調(diào)用了Dispatcher的方法:
client.dispatcher().executed(this);
client.dispatcher().finished(this);

分別是將Call加入到Dispatcher中的同步隊(duì)列中,結(jié)束后,移除隊(duì)列。

  1. 調(diào)用getResponseWithInterceptorChain獲取Response。

接下來我們就重點(diǎn)看getResponseWithInterceptorChain方法

getResponseWithInterceptorChain調(diào)用鏈

okhttp.png
  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);
  }

這里的代碼就很關(guān)鍵了,設(shè)計(jì)的很巧妙。可能有點(diǎn)繞,這里我講一下幾個關(guān)鍵的類。

Chain
  1. 獲取Request
  2. 執(zhí)行proceed
RealInterceptorChain

Chain的實(shí)現(xiàn)

  1. 包含了完成請求需要的類,包括StreamAllocation、HttpCodec、RealConnection、Request等。這里必要重要的就是可以實(shí)現(xiàn)了Chain的request()來獲取Request。
  2. 控制Interceptor的調(diào)用,調(diào)用Interceptor的攔截方法intercept后,就封裝下一個RealInterceptorChain并指定index。聲明下一個將要被調(diào)用的Interceptor。這部分邏輯主要在proceed方法中。我們看核心代碼
    // 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);

首先會獲取當(dāng)前index的Interceptor。然后執(zhí)行對應(yīng)的intercept

方法。同時出入的參數(shù)是新創(chuàng)建的RealInterceptorChain。而新創(chuàng)建的RealInterceptorChain對應(yīng)的index+1。如果執(zhí)行新創(chuàng)建的RealInterceptorChain的proceed方法,那么interceptors的第index+1個Interceptor的intercept會被執(zhí)行。依次循環(huán)下去。

總結(jié): RealInterceptorChain就是對請求中個中重要對象的封裝,執(zhí)行Interceptor的intercept

的調(diào)用,確定下一個RealInterceptorChain。保證所有的Interceptor依次執(zhí)行intercept

Interceptor

前面講到了RealInterceptorChain會執(zhí)行Interceptor的intercept方法,同時傳入下一個RealInterceptorChain。那么intercept方法究竟做了什么事呢,因?yàn)镮nterceptor的實(shí)現(xiàn)很多,這里我們挑一個系統(tǒng)的實(shí)現(xiàn)類看看,比如: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");
      }
    }

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

里面邏輯我們現(xiàn)在可能還看不懂,我們看中間最核心的一句話,。Response networkResponse = chain.proceed(requestBuilder.build());有沒有覺得頓時豁然開朗。realChain是傳入的參數(shù),而執(zhí)行proceed方法,就又回到了前面我們講RealInterceptorChain的流程。那前后RealInterceptorChain有什么區(qū)別呢?那就是index在不斷的增加,同時對應(yīng)的Interceptor也就不同。

那么Interceptor有什么用呢?

我們剛才只關(guān)注了中間的chain.proceed(requestBuilder.build());。而在此前后我們可以做很多的邏輯操作了,比如:

對Request進(jìn)行一些請求頭的判斷,處理和完善。對Response進(jìn)行一些處理,如在有g(shù)zip的情況下數(shù)據(jù)的處理等。

總結(jié):Interceptor這里我稱之為攔截器。Okhttp將請求的流程,從封裝請求頭,獲取連接,發(fā)請求數(shù)據(jù),讀請求數(shù)據(jù)等等。拆分成一個個Interceptor。每一個Interceptor有著自己單一的功能,而下層的Interceptor為上層的Interceptor服務(wù),有沒有覺得有點(diǎn)像我們的網(wǎng)絡(luò)TCP/IP的模型,哈哈。其實(shí)這種思想讓我們的請求變的更加清晰,并且擴(kuò)展性很好。每一層也就是Interceptor可以有自己的實(shí)現(xiàn)。同時我們可以定義自己的Interceptor。 而Interceptor的順序執(zhí)行就由RealInterceptorChain完成。

到這里我們就講了整個請求的大體執(zhí)行框架和模式。這部分一定要好好的理解,方便后面的學(xué)習(xí)。

RetryAndFollowUpInterceptor

這個攔截器用來做重連接和重定向的。其中邏輯有以下:

創(chuàng)建StreamAllocation
  @Override 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);
        ...省略剩余代碼

看到了吧new StreamAllocation了吧。這里第一個疑惑解決,StreamAllocation的創(chuàng)建地方。這里還要多講一個地方就是構(gòu)造參數(shù)ConnectionPool。我們看到是從OkHttpClient傳了的。而在OkHttpclient創(chuàng)建時候創(chuàng)建了ConnectionPool。

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
  ...省略
    public static final class Builder {
      public Builder() {
      ...省略
connectionPool = new ConnectionPool();
         ...省略

后面用到ConnectionPool大家就別再疑惑了。

創(chuàng)建StreamAllocation在這里,那當(dāng)然釋放也是在這類里:

失敗重連接
    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();
        }
      }

我們看到response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);是在一個無限循環(huán)中,如果出現(xiàn)異常,并且滿足重連接,就會再次調(diào)用。

重定向
      Request followUp = followUpRequest(response);

      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

ConnectInterceptor獲取連接

前面我們講了Interceptor的執(zhí)行流程。而getResponseWithInterceptorChain方法中添加了一些給定的Interceptor。如:RetryAndFollowUpInterceptor(這個是在創(chuàng)建RealCall時候創(chuàng)建的,前面有提醒大家注意)、BridgeInterceptor(上一節(jié)有講到,主要做一些請求頭和響應(yīng)數(shù)據(jù)的處理)、CacheInterceptor(看名稱知道,處理緩存)、ConnectInterceptor、CallServerInterceptor。按上一節(jié)的流程,這些Interceptor會依次被調(diào)用。這里我們要重點(diǎn)看最后兩個,首先是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 = 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ì)大家都喜歡看這種源碼,代碼量很少,簡潔。我們主要看中間三行代碼:

獲取StreamAllocation
StreamAllocation streamAllocation = realChain.streamAllocation();

而streamAllocation是RealInterceptorChain的成員變量,在構(gòu)造方法中賦值。這里我們就往前找,往直前的Interceport找,看誰構(gòu)造RealInterceptorChain傳遞了StreamAllocation。經(jīng)過我們的一個個查找,在RetryAndFollowUpInterceptor的intercept方法中找到:

獲取HttpCodec
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
獲取RealConnection
RealConnection connection = streamAllocation.connection();

HttpCodec和RealConnection這兩玩意干啥的,就現(xiàn)在這幾行代碼我們也看不出來,那就先不管。繼續(xù)看CallServerInterceptor

CallServerInterceptor網(wǎng)絡(luò)請求

這個就厲害了。看名稱,請求服務(wù)。那就是最核心的地方了。擼上代碼:

/** This is the last interceptor in the chain. It makes a network call to the server. */
public final class CallServerInterceptor implements Interceptor {
  private final boolean forWebSocket;

  public CallServerInterceptor(boolean forWebSocket) {
    this.forWebSocket = forWebSocket;
  }

  @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();
    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;
  }
}
1. 從RealInterceptorChain獲取相關(guān)對象
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

這些變量在上一節(jié)中已經(jīng)介紹,這里只是獲取一下。

2. 發(fā)送請求頭數(shù)據(jù)
httpCodec.writeRequestHeaders(request);
3. 發(fā)送請求體

這里會先判斷請求方法以及是否有請求體數(shù)據(jù)。如果有則發(fā)送。

4. 讀取響應(yīng)頭
responseBuilder = httpCodec.readResponseHeaders(false);
5. 封裝相應(yīng)內(nèi)容
    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();
    }
6. 判斷Connection頭部信息是否是close

如果請求頭或響應(yīng)頭的Connection值為close。則標(biāo)識改Connection為noNewStreams。標(biāo)識不會有新的流。

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

至于noNewStreams是用來控制,當(dāng)前的連接是否能再次被使用,我們后面會講到。


前面我們講了CallServerInterceptor的請求流程,但里面有很多的類我們還不清楚是怎么來的,以及干啥用的。接下來我們就講核心類的用法以及創(chuàng)建和使用流程。

RealConnection

這個類主要負(fù)責(zé)進(jìn)行Socket的操作(連接),獲取Socket的輸入輸出流并封裝。

建立Socket連接(connect)
    while (true) {
      try {
        if (route.requiresTunnel()) {
          connectTunnel(connectTimeout, readTimeout, writeTimeout);
        } else {
          connectSocket(connectTimeout, readTimeout);
        }
        establishProtocol(connectionSpecSelector);
        break;
      } catch (IOException e) {
        closeQuietly(socket);
        closeQuietly(rawSocket);
        socket = null;
        rawSocket = null;
        source = null;
        sink = null;
        handshake = null;
        protocol = null;
        http2Connection = null;

        if (routeException == null) {
          routeException = new RouteException(e);
        } else {
          routeException.addConnectException(e);
        }

        if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
          throw routeException;
        }
      }
    }

我們先簡單的看,直接連接socket的方式,大家可以看到是一個無限循環(huán),知道連接成功,或者指定的相關(guān)異常拋出則跳出循環(huán)。具體哪些異常可以看connectionSpecSelector.connectionFailed(e)內(nèi)部的實(shí)現(xiàn)。

接下來我們就具體看連接socket的方法connectSocket(connectTimeout, readTimeout)

  private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    rawSocket.setSoTimeout(readTimeout);
    try {
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
      ce.initCause(e);
      throw ce;
    }

    // The following try/catch block is a pseudo hacky way to get around a crash on Android 7.0
    // More details:
    // https://github.com/square/okhttp/issues/3245
    // https://android-review.googlesource.com/#/c/271775/
    try {
      source = Okio.buffer(Okio.source(rawSocket));
      sink = Okio.buffer(Okio.sink(rawSocket));
    } catch (NullPointerException npe) {
      if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
        throw new IOException(npe);
      }
    }
  }

邏輯很清晰,就是建立socket連接,然后封裝輸入輸出流。

  1. 建立socket連接
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);

進(jìn)行連接,我們查看實(shí)現(xiàn):

  public void connectSocket(Socket socket, InetSocketAddress address,
      int connectTimeout) throws IOException {
    socket.connect(address, connectTimeout);
  }

這就到了我們熟悉的Socket連接了。

  1. 封裝輸入輸出流
      source = Okio.buffer(Okio.source(rawSocket));
      sink = Okio.buffer(Okio.sink(rawSocket));
獲取并封裝Socket輸入輸出流

我們看一下Okio.source方法:

  /**
   * Returns a source that reads from {@code socket}. Prefer this over {@link
   * #source(InputStream)} because this method honors timeouts. When the socket
   * read times out, the socket is asynchronously closed by a watchdog thread.
   */
  public static Source source(Socket socket) throws IOException {
    if (socket == null) throw new IllegalArgumentException("socket == null");
    AsyncTimeout timeout = timeout(socket);
    Source source = source(socket.getInputStream(), timeout);
    return timeout.source(source);
  }

還得繼續(xù)扒:看source的重載方法:

  private static Source source(final InputStream in, final Timeout timeout) {
    if (in == null) throw new IllegalArgumentException("in == null");
    if (timeout == null) throw new IllegalArgumentException("timeout == null");

    return new Source() {
      @Override public long read(Buffer sink, long byteCount) throws IOException {
        if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
        if (byteCount == 0) return 0;
        try {
          timeout.throwIfReached();
          Segment tail = sink.writableSegment(1);
          int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
          int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
          if (bytesRead == -1) return -1;
          tail.limit += bytesRead;
          sink.size += bytesRead;
          return bytesRead;
        } catch (AssertionError e) {
          if (isAndroidGetsocknameError(e)) throw new IOException(e);
          throw e;
        }
      }

      @Override public void close() throws IOException {
        in.close();
      }

      @Override public Timeout timeout() {
        return timeout;
      }

      @Override public String toString() {
        return "source(" + in + ")";
      }
    };
  }

來了,這下清晰了,對輸入流做了包裝。既然是輸入,就只有read方法而么有write方法。而讀的邏輯就是講InputStream的數(shù)據(jù)存放到Buffer中。

到這里Okio.source(rawSocket)我們清楚了,把輸入流封裝成Source。read方法將數(shù)據(jù)讀入到Buffer中。我們接下來繼續(xù)看Okio.buffer(Okio.source(rawSocket))外面這個方法:

  public static BufferedSource buffer(Source source) {
    return new RealBufferedSource(source);
  }

有點(diǎn)粗暴,就是new了個RealBufferedSource。而它又是啥玩意呢,它實(shí)現(xiàn)了BufferedSource。這分明就是個裝飾模式嘛。在原有Source的基礎(chǔ)上,多了一些方法。如:readInt、skip等等。那還有啥用呢,我們看read方法:

  @Override public long read(Buffer sink, long byteCount) throws IOException {
    if (sink == null) throw new IllegalArgumentException("sink == null");
    if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
    if (closed) throw new IllegalStateException("closed");

    if (buffer.size == 0) {
      long read = source.read(buffer, Segment.SIZE);
      if (read == -1) return -1;
    }

    long toRead = Math.min(byteCount, buffer.size);
    return buffer.read(sink, toRead);
  }

這里進(jìn)行了方法的覆寫,數(shù)據(jù)先讀到Buffer里,然后再寫到sink里。

至于sink = Okio.buffer(Okio.sink(rawSocket));我就不講了,模式一樣。只是一個負(fù)責(zé)讀,一個負(fù)責(zé)寫。

StreamAllocation

協(xié)調(diào)Connections、Streams、Calls之間的關(guān)系。包括控制RealConnection的創(chuàng)建,釋放,狀態(tài)的管理。

一個RealConnection中可以包含多個StreamAllocation,默認(rèn)為1個。

findHealthyConnection

這個方法就比較重要了。找到一個健康可用的RealConnection,通過閱讀這個類,我們可以把上面說的幾個類的關(guān)系搞清楚。先源碼:

/**
 * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
 * until a healthy connection is found.
 */
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
    int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws IOException {
  while (true) {
    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;
  }
}

首選進(jìn)入一個死循環(huán),直到獲取一個健康的可用的RealConnection或者有異常拋出。


findConnection》》》》》》》》》》》》》》》》Start

第一步調(diào)用到findConnection方法,我們查看該方法

/**
 * Returns a connection to host a new stream. This prefers the existing connection if it exists,
 * then the pool, finally building a new connection.
 */
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled) throws IOException {
  Route selectedRoute;
  synchronized (connectionPool) {
    if (released) throw new IllegalStateException("released");
    if (codec != null) throw new IllegalStateException("codec != null");
    if (canceled) throw new IOException("Canceled");

    // 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) {
    if (canceled) throw new IOException("Canceled");

    // Now that we have an IP address, make another attempt at getting a connection from the pool.
    // This could match due to connection coalescing.
    Internal.instance.get(connectionPool, address, this, selectedRoute);
    if (connection != null) return connection;

    // Create a connection and assign it to this allocation immediately. This makes it possible
    // for an asynchronous cancel() to interrupt the handshake we're about to do.
    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;
}
  1. 先判斷當(dāng)前的RealConnection allocatedConnection = this.connection;判斷當(dāng)前連接是否已存在,如果存在且沒有標(biāo)記noNewStreams,則直接返回該連接

  2. 到連接池中尋找匹配的連接Internal.instance.get(connectionPool, address, this, null);。這里Internal.instance是一個抽象類中的靜態(tài)變量,那在哪里實(shí)現(xiàn)的呢。我們看到OkHttpClient類。類中第三個static關(guān)鍵字就是instance的實(shí)現(xiàn)

      static {
        Internal.instance = new Internal() {
          @Override public void addLenient(Headers.Builder builder, String line) {
            builder.addLenient(line);
          }
                @Override public RealConnection get(ConnectionPool pool, Address address,
              StreamAllocation streamAllocation, Route route) {
            return pool.get(address, streamAllocation, route);
          }
          ...省略代碼
        }
    

    這里我們就知道其實(shí)最終調(diào)用到了ConnectionPool的get方法。我們查看

      /**
       * Returns a recycled connection to {@code address}, or null if no such connection exists. The
       * route is null if the address has not yet been routed.
       */
      @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
        assert (Thread.holdsLock(this));
        for (RealConnection connection : connections) {
          if (connection.isEligible(address, route)) {
            streamAllocation.acquire(connection);
            return connection;
          }
        }
        return null;
      }
    

    內(nèi)部就是循環(huán)遍歷connections,找到匹配的Connection。至于如何判斷,大家查看方法的實(shí)現(xiàn)即可。如果找到則直接返回,否則進(jìn)入下一步

  3. 前面在調(diào)用ConnectionPool.get方法時候Route參數(shù)為空,這一步就是獲取一個Route然后再次查找。如果成功就返回

  4. 經(jīng)過上面三個步驟后,說明已經(jīng)沒有可用的Connection。那么就得創(chuàng)建一個,

          result = new RealConnection(connectionPool, selectedRoute);
          acquire(result);
    
  5. 創(chuàng)建完后調(diào)用acquire,這個是干啥的呢

      public void acquire(RealConnection connection) {
        assert (Thread.holdsLock(connectionPool));
        if (this.connection != null) throw new IllegalStateException();
    
        this.connection = connection;
        connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
      }
    

    把當(dāng)前的StreamAllocation添加到RealConnection。這和我們前面說到的一個RealConnection可能對應(yīng)多個StreamAllocation。

  6. 開始進(jìn)行Socket連接

    result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
    routeDatabase().connected(result.route());
    
  7. 將RealConnection添加到connectionPool中

    Internal.instance.put(connectionPool, result);
    

到這里findHealthyConnection執(zhí)行完畢,結(jié)果獲取一個可用的RealConnection。

findConnection《《《《《《《《《《《《《《《《《《《《End


繼續(xù)findHealthyConnection的代碼:

      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;

這里就是檢查RealConnection是否正常可用。也就是做個體檢isHealthy,如果發(fā)現(xiàn)返回False,那么標(biāo)記這個RealConnection的noNewStreams為true。此變量標(biāo)記為true后,代碼后面就不要從使用這個RealConnection。何以得知呢?

看到前面第2步,從ConnectionPool調(diào)用get方法尋找合適的RealConnection,有一句判斷,前面我們沒有講,這里我跟蹤一下:

  @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.isEligible(address, route)) {
        streamAllocation.acquire(connection);
        return connection;
      }
    }
    return null;
  }

看到connection.isEligible(address, route)這句話,我們進(jìn)入:

public boolean isEligible(Address address, @Nullable Route route) {
  // If this connection is not accepting new streams, we're done.
  if (allocations.size() >= allocationLimit || noNewStreams) return false;

  // If the non-host fields of the address don't overlap, we're done.
  if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
  .../省略代碼

看到noNewStreams了吧, 現(xiàn)在知道他的用處了吧。還有allocations.size() >= allocationLimit,控制一個RealConnection可以被多少個StreamAllocation持有,這下都清楚了吧。

HttpCodec(Http1Codec)

Http請求和響應(yīng)的編解碼抽象HttpCodec

這是一個接口,定義了編解碼的抽象方法。

public interface HttpCodec {
  /**
   * The timeout to use while discarding a stream of input data. Since this is used for connection
   * reuse, this timeout should be significantly less than the time it takes to establish a new
   * connection.
   */
  int DISCARD_STREAM_TIMEOUT_MILLIS = 100;

  /** Returns an output stream where the request body can be streamed. */
  Sink createRequestBody(Request request, long contentLength);

  /** This should update the HTTP engine's sentRequestMillis field. */
  void writeRequestHeaders(Request request) throws IOException;

  /** Flush the request to the underlying socket. */
  void flushRequest() throws IOException;

  /** Flush the request to the underlying socket and signal no more bytes will be transmitted. */
  void finishRequest() throws IOException;

  /**
   * Parses bytes of a response header from an HTTP transport.
   *
   * @param expectContinue true to return null if this is an intermediate response with a "100"
   *     response code. Otherwise this method never returns null.
   */
  Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;

  /** Returns a stream that reads the response body. */
  ResponseBody openResponseBody(Response response) throws IOException;

  /**
   * Cancel this stream. Resources held by this stream will be cleaned up, though not synchronously.
   * That may happen later by the connection pool thread.
   */
  void cancel();
}

主要就是針對Request和Response的處理。將我們傳入的請求Request編碼成Http的協(xié)議請求,將響應(yīng)解碼成Response。

針對HTTP/1.1的實(shí)現(xiàn)Http1Codec

前面講了HttpCodec的抽象方法。這里就是實(shí)現(xiàn),Http協(xié)議也有多個版本,也就對應(yīng)不同的實(shí)現(xiàn)。這里我們就看現(xiàn)在常用的Http/1.1。

而Http1Codec的創(chuàng)建在ConnectInterceptor中。

HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);

我們繼續(xù)跟蹤到StreamAllocation的newStream方法

HttpCodec resultCodec = resultConnection.newCodec(client, this);

繼續(xù)進(jìn)入:

  public HttpCodec newCodec(
      OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {
      return new Http2Codec(client, streamAllocation, http2Connection);
    } else {
      socket.setSoTimeout(client.readTimeoutMillis());
      source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
      return new Http1Codec(client, streamAllocation, source, sink);
    }
  }

兩個參數(shù)我們已經(jīng)很熟悉了。重點(diǎn)看到了new Http1Codec(client, streamAllocation, source, sink),在這里創(chuàng)建了Http1Codec。而傳遞的參數(shù)source、sink前面我們已經(jīng)介紹了。在連接Socket后進(jìn)行輸入輸出的封裝。

Http1Codec核心方法實(shí)現(xiàn)

我們在介紹CallServerInterceptor的intercept方法時候,只是粗略的講了下流程。這里我們將一下和Http1Codec相關(guān)的方法。

  • 發(fā)送請求頭

    httpCodec.writeRequestHeaders(request);
    

    看到實(shí)現(xiàn)

      /**
       * Prepares the HTTP headers and sends them to the server.
       *
       * <p>For streaming requests with a body, headers must be prepared <strong>before</strong> the
       * output stream has been written to. Otherwise the body would need to be buffered!
       *
       * <p>For non-streaming requests with a body, headers must be prepared <strong>after</strong> the
       * output stream has been written to and closed. This ensures that the {@code Content-Length}
       * header field receives the proper value.
       */
      @Override public void writeRequestHeaders(Request request) throws IOException {
        String requestLine = RequestLine.get(
            request, streamAllocation.connection().route().proxy().type());
        writeRequest(request.headers(), requestLine);
      }
    

    requestLine就是HTTP的起始行,內(nèi)部大家可以自己查看。然后看到writeRequest

方法:

  /** Returns bytes of a request header for sending on an HTTP transport. */
  public void writeRequest(Headers headers, String requestLine) throws IOException {
    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
    sink.writeUtf8(requestLine).writeUtf8("\r\n");
    for (int i = 0, size = headers.size(); i < size; i++) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n");
    }
    sink.writeUtf8("\r\n");
    state = STATE_OPEN_REQUEST_BODY;
  }

邏輯就很簡單了,遍歷Headers,將請求頭寫入到sink中。

  • 發(fā)送請求體
      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();
      }

這里會先判斷,如果后請求體的話就發(fā)送。看到createRequestBody方法。

  @Override public Sink createRequestBody(Request request, long contentLength) {
    if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
      // Stream a request body of unknown length.
      return newChunkedSink();
    }

    if (contentLength != -1) {
      // Stream a request body of a known length.
      return newFixedLengthSink(contentLength);
    }

    throw new IllegalStateException(
        "Cannot stream a request body without chunked encoding or a known content length!");
  }

確定發(fā)送的請求數(shù)據(jù)大小是否確定,然后返回對應(yīng)的Sink實(shí)現(xiàn)。接下來看到request.body().writeTo(bufferedRequestBody);

的實(shí)現(xiàn)。我們看到request.body()返回是RequestBody,一個抽象類,定義請求體的方法。看到自帶的實(shí)現(xiàn)有FormBody、MultipartBody。我們挑一個看FormBody。看到writeTo方法:

  @Override public void writeTo(BufferedSink sink) throws IOException {
    writeOrCountBytes(sink, false);
  }

繼續(xù)看:

  /**
   * Either writes this request to {@code sink} or measures its content length. We have one method
   * do double-duty to make sure the counting and content are consistent, particularly when it comes
   * to awkward operations like measuring the encoded length of header strings, or the
   * length-in-digits of an encoded integer.
   */
  private long writeOrCountBytes(@Nullable BufferedSink sink, boolean countBytes) {
    long byteCount = 0L;

    Buffer buffer;
    if (countBytes) {
      buffer = new Buffer();
    } else {
      buffer = sink.buffer();
    }

    for (int i = 0, size = encodedNames.size(); i < size; i++) {
      if (i > 0) buffer.writeByte('&');
      buffer.writeUtf8(encodedNames.get(i));
      buffer.writeByte('=');
      buffer.writeUtf8(encodedValues.get(i));
    }

    if (countBytes) {
      byteCount = buffer.size();
      buffer.clear();
    }

    return byteCount;
  }

這里就清晰了,將請求體(Form表單)遍歷的寫出。

  • 讀取響應(yīng)頭
    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

進(jìn)入到實(shí)現(xiàn):

  @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
    if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
      throw new IllegalStateException("state: " + state);
    }

    try {
      StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());

      Response.Builder responseBuilder = new Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          .headers(readHeaders());

      if (expectContinue && statusLine.code == HTTP_CONTINUE) {
        return null;
      }

      state = STATE_OPEN_RESPONSE_BODY;
      return responseBuilder;
    } catch (EOFException e) {
      // Provide more context if the server ends the stream before sending a response.
      IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
      exception.initCause(e);
      throw exception;
    }
  }

首先讀取起始行statusLine。我們進(jìn)入到實(shí)現(xiàn)source.readUtf8LineStrict()看怎么讀取的

long newline = indexOf((byte) '\n', 0, scanLength);   
if (scanLength < Long.MAX_VALUE
        && request(scanLength) && buffer.getByte(scanLength - 1) == '\r'
        && request(scanLength + 1) && buffer.getByte(scanLength) == '\n') {
      return buffer.readUtf8Line(scanLength); // The line was 'limit' UTF-8 bytes followed by \r\n.
    }

這里先時候去到\n的位置。然后看到,起始行的結(jié)束是否是\r\n。最后讀取并返回。StatusLine.parse就是解析得到Http的Method、Code、Message。下面讀取響應(yīng)頭:

      Response.Builder responseBuilder = new Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          .headers(readHeaders());

先將起始行封裝到responseBuilder中,然后readHeaders()讀取響應(yīng)頭。

  /** Reads headers or trailers. */
  public Headers readHeaders() throws IOException {
    Headers.Builder headers = new Headers.Builder();
    // parse the result headers until the first blank line
    for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) {
      Internal.instance.addLenient(headers, line);
    }
    return headers.build();
  }

這里就是一行行讀取響應(yīng)頭,然后添加到Headers中。細(xì)節(jié)大家跟蹤到方法內(nèi)部查看即可。

  • 封裝響應(yīng)體
    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();
    }

先判斷響應(yīng)頭,101啥意思呢:服務(wù)器將遵從客戶的請求轉(zhuǎn)換到另外一種協(xié)議(HTTP 1.1新)。所以就不用管響應(yīng)體了。我們重點(diǎn)看到httpCodec.openResponseBody(response)

  @Override public ResponseBody openResponseBody(Response response) throws IOException {
    Source source = getTransferStream(response);
    return new RealResponseBody(response.headers(), Okio.buffer(source));
  }

兩個步驟:

  • 第一步getTransferStream(response)
  private Source getTransferStream(Response response) throws IOException {
    if (!HttpHeaders.hasBody(response)) {
      return newFixedLengthSource(0);
    }

    if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
      return newChunkedSource(response.request().url());
    }

    long contentLength = HttpHeaders.contentLength(response);
    if (contentLength != -1) {
      return newFixedLengthSource(contentLength);
    }

    // Wrap the input stream from the connection (rather than just returning
    // "socketIn" directly here), so that we can control its use after the
    // reference escapes.
    return newUnknownLengthSource();
  }

主要判斷響應(yīng)頭Transfer-Encoding來確定響應(yīng)體的數(shù)據(jù)大小是否確定,如果是chunked則是分塊傳輸,則沒有Content-Length。否則可以確定響應(yīng)體大小。然后返回不同的Source實(shí)現(xiàn)。

  • 第二步 new RealResponseBody(response.headers(), Okio.buffer(source)
public final class RealResponseBody extends ResponseBody {
  private final Headers headers;
  private final BufferedSource source;

  public RealResponseBody(Headers headers, BufferedSource source) {
    this.headers = headers;
    this.source = source;
  }

  @Override public MediaType contentType() {
    String contentType = headers.get("Content-Type");
    return contentType != null ? MediaType.parse(contentType) : null;
  }

  @Override public long contentLength() {
    return HttpHeaders.contentLength(headers);
  }

  @Override public BufferedSource source() {
    return source;
  }
}

這個就是做了一個封裝,沒什么別的邏輯。


到此為止整個Http的發(fā)送和響應(yīng)就介紹完畢了。

同步/異步請求

先來一張流程圖:

Call.png

文章的開始我們發(fā)送http請求直接使用的同步請求

Response response = client.newCall(request).execute();

這樣比較粗暴,我們還需要開啟線程。如此OkHttp當(dāng)然也就提供了異步調(diào)用方法。

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {

    }
});

調(diào)用也是非常的方便的。整個請求OkHttp會幫我們開啟線程,并完成Http請求。接下來我們就分析這塊的流程。

newCall方法就不介紹了,我們看到enqueue方法。

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
  • 先判斷是否已經(jīng)executed。防止多次調(diào)用
  • 將任務(wù)加入到隊(duì)里中
client.dispatcher().enqueue(new AsyncCall(responseCallback));

我們進(jìn)入到enqueue方法。

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }
  • 先判斷當(dāng)前正在請求的數(shù)量是否大于最大請求數(shù),同一個主機(jī)的請求是否超過限制
  • 如果超過限制,則將AsyncCall任務(wù)放到readyAsyncCalls(準(zhǔn)備任務(wù))隊(duì)列中。
  • 如果沒有超過限制,加入到runningAsyncCalls(運(yùn)行)隊(duì)列中,并直接調(diào)度執(zhí)行。

這里我們先看幾個變量runningAsyncCalls、readyAsyncCalls:

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

這個寫的很清楚,異步就緒隊(duì)列、異步運(yùn)行隊(duì)列,同步運(yùn)行隊(duì)列。runningSyncCalls這個在前面同步調(diào)用的時候有涉及。剩下兩個變量就在這里體現(xiàn)。整體的思路就是,異步調(diào)用先判斷請求數(shù)量是否超限,如果沒有直接交給線程池執(zhí)行;超限就先放到準(zhǔn)備隊(duì)列中。

我們在看到executorService().execute(call);進(jìn)入到executorService()方法:

  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

這里很明顯了一個單例的實(shí)現(xiàn),方法上加上了synchronized關(guān)鍵字。ThreadPoolExecutor是創(chuàng)建一個線程池。

這里有朋友要問了,當(dāng)請求數(shù)量超限制我們只看到了把任務(wù)放到準(zhǔn)備隊(duì)列中,那啥時候被調(diào)用呢?這里大家先別著急,后面會講到。

到此我們已經(jīng)把client.dispatcher().enqueue(new AsyncCall(responseCallback));

enqueue這個方法干了什么事講清楚了。這里還設(shè)計(jì)一個類AsyncCall。我們看看:

  final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      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) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

光看這里看不出啥門道,我們得看看父類NamedRunnable

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

嘿!就是個Runnable,run里就做了兩個事

  • 設(shè)置線程名稱
  • 進(jìn)行execute()調(diào)用

然后我們回到AsyncCall看到execute的實(shí)現(xiàn):

Response response = getResponseWithInterceptorChain();

有沒有覺得很眼熟,這個不就是我們前面講同步調(diào)用的時候,通過這個方法完成的請求。

請求成功進(jìn)行CallBack回調(diào)

responseCallback.onResponse(RealCall.this, response)

失敗或者發(fā)生異常也回調(diào)

responseCallback.onFailure(RealCall.this, e);

上面我們就把異步調(diào)用的發(fā)起和回調(diào)講清楚了,前面我們還有個問題就是準(zhǔn)備隊(duì)列的任務(wù)啥時候被執(zhí)行。

準(zhǔn)備就緒隊(duì)列任務(wù)的調(diào)度

我們還是看到AsyncCall的execute方法,真正的執(zhí)行調(diào)用時在這個方法中,我們看到最后的finally塊

finally {
        client.dispatcher().finished(this);
      }

跟蹤到Dispatcher的finish方法

  /** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

看到重載的方法:

  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

這里先把call從隊(duì)列中移除。然后判斷promoteCalls,這里我們知道是true,所以重點(diǎn)看到if (promoteCalls) promoteCalls();這段代碼:

  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }
  • 還是先判斷正在請求的數(shù)量是否超限制
  • 遍歷readyAsyncCalls,找到符合條件的請求(同一個主機(jī)的請求數(shù)量是否超限制)。如果找到就從readyAsyncCalls中移除,然后加入到runningAsyncCalls。然后通過線程池獲進(jìn)行調(diào)度執(zhí)行。

前面我們將到同步調(diào)用的時候,RealCall的execute()方法的開始有client.dispatcher().executed(this);

方法的結(jié)束finally調(diào)用了client.dispatcher().finished(this);。然后調(diào)用了Dispatcher的finished(runningSyncCalls, call, false);方法。這里和異步調(diào)用的區(qū)別就是最后一個參數(shù)為false。

到這里整個同步調(diào)用和異步調(diào)用我們就串聯(lián)起來了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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