OKHttp源碼學(xué)習(xí)筆記:各攔截器分析

之前熟悉了OkHttp大概的調(diào)用 這次學(xué)習(xí)一下各攔截器的作用

貼一貼攔截器調(diào)用入口方法,方便梳理

//RealCall.Java
 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()));//橋攔截器?主要是用來(lái)加Header信息
    interceptors.add(new CacheInterceptor(client.internalCache()));//緩存攔截器
    interceptors.add(new ConnectInterceptor(client));//連接攔截器,用于建立連接
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());//僅在建立WebSocket時(shí)才會(huì)加的攔截器
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));//發(fā)送信息攔截器(之前的連接攔截器負(fù)責(zé)建立連接但是啥也沒(méi)干,這個(gè)攔截器負(fù)責(zé)給服務(wù)器發(fā)消息并接收返回)

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
   
    return chain.proceed(originalRequest);//開(kāi)啟鏈?zhǔn)秸{(diào)用
  }

那么接下來(lái)就是依次探討一下RetryAndFollowUpInterceptor,BridgeInterceptor,CacheInterceptor,ConnectInterceptor,CallServerInterceptor了

重連及重定向攔截器(RetryAndFollowUpInterceptor)

 @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    //申請(qǐng)網(wǎng)絡(luò)連接的關(guān)鍵類,具體在ConnectInterceptor會(huì)進(jìn)一步調(diào)用
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;

    int followUpCount = 0;
    Response priorResponse = null;
    //維護(hù)一個(gè)死循環(huán),該攔截器能實(shí)現(xiàn)重連和重定向的關(guān)鍵
    while (true) {
      if (canceled) {
        //取消則拋異常,諸如異步超時(shí)或者代碼主動(dòng)調(diào)用cancel方法都會(huì)改變布爾值
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        //前期工作已做好(主要是建立死循環(huán)和申請(qǐng)連接的StreamAllocation類 ),交給調(diào)用鏈調(diào)度給下一個(gè)攔截器處理
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;  //來(lái)到這里說(shuō)明請(qǐng)求服務(wù)器沒(méi)有問(wèn)題,啥異常都沒(méi)有出現(xiàn),服務(wù)器也有返回
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        //出現(xiàn)異常,是預(yù)料中的異常,調(diào)用recover方法看看能不能拯救一下(重連)
        //注意這里僅僅是判斷能不能救(重連),并沒(méi)有發(fā)起真正的重連
        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
          //判斷救不了了,直接拋異常,跳出死循環(huán)
          throw e.getFirstConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
       //出現(xiàn)異常,是預(yù)料中的異常,調(diào)用recover方法看看能不能拯救一下(重連)
        //注意這里僅僅是判斷能不能救(重連),并沒(méi)有發(fā)起真正的重連
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;  //判斷救不了了,直接拋異常,跳出死循環(huán)
        releaseConnection = false;
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        //假如出現(xiàn)預(yù)料外的錯(cuò)誤(作者都想不出來(lái)會(huì)有這種錯(cuò)誤未加捕獲的),釋放連接
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
     //如果先前已經(jīng)有一個(gè)response說(shuō)明先前已經(jīng)有過(guò)一次請(qǐng)求了,那么不是重連就是重定向
      if (priorResponse != null) {
        //標(biāo)記一下,給后面的followUpRequest做進(jìn)一步判斷(大概意思就是告訴后面這個(gè)請(qǐng)求有前科的,之前已經(jīng)請(qǐng)求過(guò)一次了)
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Request followUp;
      try {
         //綜合判斷假如是符合能重連或者重定向的條件返回一個(gè)不為空的Request,其他諸如請(qǐng)求成功,不符合重連和重定向條件的一律返回空
        //判斷是否要重連或重定向的關(guān)鍵函數(shù)
        followUp = followUpRequest(response, streamAllocation.route());
      } catch (IOException e) {
        streamAllocation.release();
        throw e;
      }

      if (followUp == null) {
        //返回request為空,那么嘗試釋放資源(可能是請(qǐng)求成功或者不符合重連重定向條件)
        streamAllocation.release();
        return response;
      }
      //來(lái)到這里說(shuō)明不是重連就是重定向了,關(guān)閉當(dāng)前response的輸入流
      closeQuietly(response.body());

      if (++followUpCount > MAX_FOLLOW_UPS) {
       //跟進(jìn)數(shù)量超過(guò)最大值直接拋異常, MAX_FOLLOW_UPS值為20,這里主要是防止不停重定向,重連最多也就一次
        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())) {
        //假如跟原來(lái)的連接不一樣,那么重新申請(qǐng)一個(gè)連接
        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;//賦值用于下個(gè)循環(huán)(重定向或重連)的請(qǐng)求
      priorResponse = response;//賦值用于下個(gè)循環(huán)判斷(主要用于重連判斷)
    }
  }

再看看判斷是否要重連或重定向的關(guān)鍵函數(shù) followUpRequest,這里根據(jù)返回ResposeCode有不同的判斷,這里選取其中的一個(gè)重連的情況分析

private Request followUpRequest(Response userResponse, Route route) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    int responseCode = userResponse.code();

    final String method = userResponse.request().method();
    switch (responseCode) {
    .....
      //前面省略若干其他狀態(tài)的處理代碼

      case HTTP_CLIENT_TIMEOUT:
        // 408's are rare in practice, but some servers like HAProxy use this response code. The
        // spec says that we may repeat the request without modifications. Modern browsers also
        // repeat the request (even non-idempotent ones.)
        if (!client.retryOnConnectionFailure()) {
          // The application layer has directed us not to retry the request.
          // 當(dāng)連接失敗是否進(jìn)行重連 值為false,則返回空Request
          // 該布爾值可在OkHttpClient初始化里設(shè)置
          return null;
        }

        if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
          return null;
        }

        if (userResponse.priorResponse() != null
            && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
          // 之前已經(jīng)請(qǐng)求過(guò)一次并且之前同樣是 超時(shí),說(shuō)明重連一次已經(jīng)失敗了,不再重連
          return null;
        }

        if (retryAfter(userResponse, 0) > 0) {
           //服務(wù)器有重連時(shí)間間隔,那么直接不重連了
          return null;
        }
        //符合重連條件,返回Request對(duì)象以進(jìn)行再一次請(qǐng)求
        return userResponse.request();
       ...........
       //前面省略若干其他狀態(tài)的處理代碼
        default:
        //默認(rèn)返回Null,例如服務(wù)器正常返回200,那么也沒(méi)必要跟進(jìn)了,直接返回Null
        return null;
    }
  }

其他狀態(tài)碼的流程也是相似的,都是根據(jù)返回Response信息進(jìn)行下一步處理,這里就不多敘述了

整體的調(diào)用流程就是攔截器維護(hù)一個(gè)死循環(huán),若是正常返回那么循環(huán)體只走一遍就直接返回;假如是超時(shí)等情況導(dǎo)致重連再請(qǐng)求一次,再走一次循環(huán)體無(wú)論成功與否都會(huì)直接跳出循環(huán),循環(huán)體走了兩遍;假如是重定向,最多允許重定向20次,即循環(huán)體走20次然后結(jié)束死循環(huá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) {
      //添加各種請(qǐng)求頭
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      //如果傳輸長(zhǎng)度不為-1,則表示完整傳輸
      if (contentLength != -1) {
         //設(shè)置頭信息 Content-Length
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        //如果傳輸長(zhǎng)度為-1,則表示分塊傳輸,自動(dòng)設(shè)置頭信息   
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }
    //如果沒(méi)有設(shè)置頭信息 Connection,則自動(dòng)設(shè)置為 Keep-Alive
    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.
    // 是否運(yùn)用GZIP傳輸
    //在 HTTP 傳輸時(shí)是支持 gzip 壓縮的,采用 gzip 壓縮后可以大幅減少傳輸內(nèi)容大小,這樣可以提高傳輸速 
    // 度,減少流量的使用。
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
   
    //查找之前有沒(méi)有訪問(wèn)保存的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());
    }

    //轉(zhuǎn)發(fā)下一個(gè)攔截器
    Response networkResponse = chain.proceed(requestBuilder.build());

    //保存服務(wù)器返回的cookie信息
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    //如果服務(wù)器傳輸有用到gzip壓縮,那么還要進(jìn)行解壓縮
    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();
  }

該攔截器主要負(fù)責(zé)添加請(qǐng)求頭,同時(shí)在服務(wù)器返回時(shí)保存必要的服務(wù)器信息例如cookie等,如果服務(wù)器有采用gzip傳輸還會(huì)先解壓得到原始數(shù)據(jù)再進(jìn)行返回

緩存攔截器(CacheInterceptor)

OKHttp默認(rèn)是沒(méi)有緩存的,除非我們?cè)贠kHttpClient初始化設(shè)置,基本用法如下

//緩存文件夾
File cacheFile = new File(getExternalCacheDir().toString(),"cache");
//緩存大小為10M
int cacheSize = 10 * 1024 * 1024;
//創(chuàng)建緩存對(duì)象
Cache cache = new Cache(cacheFile,cacheSize);//從這里也能看出緩存是直接存到本地存儲(chǔ)(SD卡)上的

OkHttpClient client = new OkHttpClient.Builder()
        .cache(cache)//設(shè)置緩存
        .build();

在設(shè)置了Cache后 緩存攔截器才算有用武之地,接下來(lái)慣例分析下緩存攔截器具體流程

@Override public Response intercept(Chain chain) throws IOException {
    //是否有設(shè)置緩存,有?那么從本地存儲(chǔ)查找是否有該請(qǐng)求的緩存
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    //這個(gè)get方法是判斷最后該請(qǐng)求是用緩存還是網(wǎng)絡(luò)請(qǐng)求的關(guān)鍵,之前的本地存儲(chǔ)并沒(méi)有在時(shí)間上判斷緩存是否可用(例如這個(gè)緩存是一年前得了,那肯定是無(wú)效的),該函數(shù)內(nèi)做進(jìn)一步判斷(當(dāng)然還有其他必要的判斷)
    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) {
      //本地緩存有,但是不符合某個(gè)條件,舍棄了
      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.
    //又沒(méi)網(wǎng)絡(luò)請(qǐng)求又沒(méi)緩存,直接返回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) {
      //通過(guò)new CacheStrategy.Factory類get方法得出網(wǎng)絡(luò)請(qǐng)求為空,同時(shí)越過(guò)上面代碼來(lái)到這里也說(shuō)明了緩存不為空,那么直接返回緩存
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      //拋給下一個(gè)攔截器進(jìn)行網(wǎng)絡(luò)連接
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        //假如緩存不為空同時(shí)服務(wù)器返回并無(wú)修改,進(jìn)行合并后關(guān)閉新的網(wǎng)絡(luò)請(qǐng)求Response里的輸入流
        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();
       //同時(shí)更新緩存
        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.
        //用戶有設(shè)置緩存,把本次網(wǎng)絡(luò)請(qǐng)求緩存到本地.同時(shí)返回給上一級(jí)攔截器
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      //檢測(cè)Http的Method參數(shù),諸如POST,DELETE等不需要緩存的請(qǐng)求直接移除
      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 對(duì)象,先前分析由RetryAndFollowUpInterceptor生成并傳遞
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //根據(jù)請(qǐng)求類型判斷在申請(qǐng)連接時(shí)是否要對(duì)連接的健全性進(jìn)行檢查
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);//關(guān)鍵核心
    //獲取連接
    RealConnection connection = streamAllocation.connection();
    //向下傳遞
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

連接攔截器的代碼看起來(lái)貌似是各攔截器中代碼最小的,其實(shí)代碼量是5個(gè)攔截器中最龐大的,這里的關(guān)鍵在于 streamAllocation.newStream(client, chain, doExtensiveHealthChecks).就是這里面獲取網(wǎng)絡(luò)連接,那么我們就跟進(jìn)去看看

//StreamAllocation.java
 public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    int connectTimeout = chain.connectTimeoutMillis();
    int readTimeout = chain.readTimeoutMillis();
    int writeTimeout = chain.writeTimeoutMillis();
    int pingIntervalMillis = client.pingIntervalMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();
    try {
      //根據(jù)條件返回一個(gè)符合條件且健全的連接
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);//關(guān)鍵核心
     //同時(shí)返回Http層面處理類(個(gè)人理解這類只對(duì)外暴露Http層面的方法和變量,細(xì)化操作)
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

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

//StreamAllocation.java
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
      boolean doExtensiveHealthChecks) throws IOException {
      //維護(hù)一個(gè)死循環(huán)
      while (true) { 
       //找到符合條件的連接
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);//關(guān)鍵核心

      // If this is a brand new connection, we can skip the extensive health checks.
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          //沒(méi)請(qǐng)求過(guò)一次說(shuō)明是新連接,直接返回
          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.
      //否則對(duì)連接進(jìn)行健全性檢查
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        noNewStreams();
        //不健康的,那么繼續(xù)死循環(huán),直到找到可用的健全的連接為止
        continue;
      }

      return candidate;
     }
  }

從findHealthyConnection方法中我們可以很直觀地看到申請(qǐng)連接的表面流程:死循環(huán),返回后對(duì)候選鏈接做健全性檢查,不合格就繼續(xù)申請(qǐng),直到找到可用連接為止
那么具體申請(qǐng)連接的流程我們?cè)俑M(jìn)去看看

//StreamAllocation.java
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    Socket toClose;
    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. We need to be careful here because our
      // already-allocated connection may have been restricted from creating new streams.
    
      releasedConnection = this.connection;
      toClose = releaseIfNoNewStreams();
      if (this.connection != null) {
       //如果連接不為空,說(shuō)明之前就有申請(qǐng)一個(gè)了,直接賦值
        result = this.connection;
        releasedConnection = null;
      }
      if (!reportedAcquired) {
        // If the connection was never reported acquired, don't report it as released!
        releasedConnection = null;
      }

      if (result == null) {
        //沒(méi)有現(xiàn)成連接,那么從連接池申請(qǐng)
        Internal.instance.get(connectionPool, address, this, null);//從連接池申請(qǐng)并賦值到該類的connection變量
        if (connection != null) {
           //從連接池找到了可用連接
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    closeQuietly(toClose);
    
    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
      // If we found an already-allocated or pooled connection, we're done.
      return result;
    }

    // If we need a route selection, make one. This is a blocking operation.
    //看看是否需要選定一個(gè)路由,沒(méi)有的話需要選擇一個(gè)路由,并重新從連接池取連接
    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      newRouteSelection = true;
      routeSelection = routeSelector.next();
    }

    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");

      if (newRouteSelection) {
        // Now that we have a set of IP addresses, make another attempt at getting a connection from
        // the pool. This could match due to connection coalescing.
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {
          Route route = routes.get(i);
          //根據(jù)路由,主機(jī)地址等相關(guān)信息在連接池中找出符合條件的連接
          Internal.instance.get(connectionPool, address, this, route);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }

      if (!foundPooledConnection) {
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }

        //沒(méi)有現(xiàn)成的連接,連接池也沒(méi)有復(fù)用連接
        route = selectedRoute;
        refusedStreamCount = 0;
        //那么直接新建一個(gè)連接
        result = new RealConnection(connectionPool, selectedRoute);
        acquire(result, false);
      }
    }

    // If we found a pooled connection on the 2nd time around, we're done.
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }

    //開(kāi)始握手建立連接,這是一個(gè)阻塞操作
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);//關(guān)鍵核心
    routeDatabase().connected(result.route());
    

    Socket socket = null;
    synchronized (connectionPool) {
      reportedAcquired = true;

      // 連接已建立,那么存進(jìn)連接池準(zhǔn)備復(fù)用
      Internal.instance.put(connectionPool, result);
      //假如該RealConnection有多路連接(跟進(jìn)去發(fā)現(xiàn)是Http2相關(guān),下面這段可暫時(shí)無(wú)視),那么關(guān)掉多余的socket連接
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

    eventListener.connectionAcquired(call, result);
    return result;
  }

具體的找連接流程就是先找找有沒(méi)有之前就用過(guò)的連接(例如重連或重定向的情況),沒(méi)有看看連接池有沒(méi)有復(fù)用的連接,我們?cè)倏纯碦ealConnection類的connect方法

public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
      EventListener eventListener) {
 .....
 //前面省略若干代碼
    while (true) {
      try {
 
        if (route.requiresTunnel()) {
          //假如是通過(guò)http端口做https請(qǐng)求
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break;
          }
        } else {
          //建立socket連接,關(guān)鍵核心
          connectSocket(connectTimeout, readTimeout, call, eventListener);
        }
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
        eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
        break;
      } catch (IOException e) {
        ....
        //省略若干代碼
      }
    }
    .....
    //省略若干代碼
  }

private void connectSocket(int connectTimeout, int readTimeout, Call call,
      EventListener eventListener) 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);

    eventListener.connectStart(call, route.socketAddress(), proxy);
    rawSocket.setSoTimeout(readTimeout);
    try {
      //建立socket連接
      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 {
      //通過(guò)Okio與Socket建立輸入輸出流
      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);
      }
    }
  }

至此完成了返回連接的過(guò)程,從代碼也能看出OkHttp3直接用Socket配合Okio輸入輸出流,大家都知道Http本身就是基于Tcp,Tcp用Socket做的,相當(dāng)于OkHttp3重做了一遍Http,至于為什么要重做,我個(gè)人認(rèn)為有兩點(diǎn)原因:
1.不滿意原有JAVAIO 流,用OKIO替代
2.取出底層的Socket連接,用于連接池復(fù)用

呼叫服務(wù)器攔截器(CallServerInterceptor)

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

    realChain.eventListener().requestHeadersStart(realChain.call());
    //寫(xiě)入請(qǐng)求頭信息,內(nèi)部寫(xiě)入到RealBufferSink中的緩沖區(qū)(并未射出到服務(wù)器的輸出流)
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    // 100-continue用于客戶端在發(fā)送POST數(shù)據(jù)給服務(wù)器前,征詢服務(wù)器情況,看服務(wù)器是否處理POST的數(shù)據(jù)。
    //如果不處理,客戶端則不上傳POST數(shù)據(jù),如果處理,則POST上傳數(shù)據(jù)。在現(xiàn)實(shí)應(yīng)用中,通過(guò)在POST大數(shù)據(jù)時(shí),才會(huì)使用100-continue協(xié)議。
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        //請(qǐng)求頭確實(shí)有100-continue,那么把緩沖區(qū)的輸出內(nèi)容全部射出給服務(wù)器
        httpCodec.flushRequest();
        realChain.eventListener().responseHeadersStart(realChain.call());
        //根據(jù)服務(wù)器返回信息構(gòu)建ResponseBuilder
        //這里假如是代碼100返回Null,其他返回均不為Null
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        //服務(wù)器返回100,那么開(kāi)始那么就把請(qǐng)求體加上
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        //請(qǐng)求體寫(xiě)入緩沖區(qū)
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      } 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();
      }
    }
    //再次射出到與服務(wù)器的輸出流里,至此請(qǐng)求已發(fā)送完畢
    httpCodec.finishRequest();//跟 httpCodec.flushRequest()一樣的效果

    if (responseBuilder == null) {
      //之前的分析過(guò)狀態(tài)碼100該類是空的,那么要再讀一次構(gòu)建responseBuilder用來(lái)構(gòu)建真正的Response
      realChain.eventListener().responseHeadersStart(realChain.call());
      //獲取從服務(wù)器讀取頭信息構(gòu)建responseBuilder
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    //計(jì)劃構(gòu)建實(shí)際的Response
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();
   
    int code = response.code();
    if (code == 100) {
       //服務(wù)器居然還返回100,那么再讀一次構(gòu)建新的ResponseBuilder
      responseBuilder = httpCodec.readResponseHeaders(false);
      //這下構(gòu)建的ResponseBuilder基本上能構(gòu)建有實(shí)際信息返回的Response了
      response = responseBuilder
              .request(request)
              .handshake(streamAllocation.connection().handshake())
              .sentRequestAtMillis(sentRequestMillis)
              .receivedResponseAtMillis(System.currentTimeMillis())
              .build();

      code = response.code();
    }

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

    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 {
      //構(gòu)建帶實(shí)際返回信息的Response
      // 其實(shí)在之前 httpCodec.finishRequest()中服務(wù)器收到請(qǐng)求立刻就進(jìn)行處理并返回?cái)?shù)據(jù)給客戶端的流里面了,但是客戶端還沒(méi)有通過(guò)輸入流獲取流里面的信息
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))//包裝一個(gè)輸入流,供后面從流讀取數(shù)據(jù)
          .build();
    }
    //假如服務(wù)器或客戶端并不希望建立長(zhǎng)連接,那么這里先把noStream標(biāo)記為true,后面一旦請(qǐng)求到返回?cái)?shù)據(jù)就把連接斷掉
    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());
    }
   //返回Response給上層
    return response;
  }

看到這里其實(shí)我們發(fā)現(xiàn)即使到了最后一個(gè)攔截器也并沒(méi)有從輸入流讀取數(shù)據(jù)(盡管服務(wù)器早已把數(shù)據(jù)輸入到流里面了),僅僅相當(dāng)于打開(kāi)了輸入流的入口,那么真正從輸入流讀取數(shù)據(jù)是在哪呢
這里以我以前發(fā)起的一個(gè)異步網(wǎng)絡(luò)請(qǐng)求為例,代碼如下

   NetWorkUtil.postNetWork(jsonObject, Constants.getBaseUrl(), new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                      ...
                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                       String stringGson = response.body().string();
                       ...
                    }
                });

我們跳進(jìn)去這個(gè)response.body().string()方法看看

//ResponseBody.java
public final String string() throws IOException {
    BufferedSource source = source();
    try {
      //判斷用什么樣的字符集類型解析數(shù)據(jù)
      Charset charset = Util.bomAwareCharset(source, charset());
      return source.readString(charset);//從輸入流讀取數(shù)據(jù)并根據(jù)字符集類型轉(zhuǎn)換成字符串
    } finally {
     //關(guān)閉socket輸入流,清空緩沖區(qū)
      Util.closeQuietly(source);
    }
  }

總結(jié)

通過(guò)學(xué)習(xí)大概了解了OkHttp3的基本調(diào)用流程,發(fā)現(xiàn)內(nèi)部原來(lái)很多東西跟自己所想的完全不一樣,也學(xué)到了
很多,其實(shí)OkHttp3還有很多需要探討的東西,本文也只是描繪了OkHttp3個(gè)各攔截器的流程,具體還有很多細(xì)節(jié)需要深挖例如OkIo的內(nèi)部實(shí)現(xiàn),連接池具體是咋操作的等等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容