okhttp源碼分析(四)-ConnectInterceptor過濾器

1.okhttp源碼分析(一)——基本流程(超詳細(xì))
2.okhttp源碼分析(二)——RetryAndFollowUpInterceptor過濾器
3.okhttp源碼分析(三)——CacheInterceptor過濾器
4.okhttp源碼分析(四)——ConnectInterceptor過濾器
5.okhttp源碼分析(五)——CallServerInterceptor過濾器

前言

前一篇博客分析了CacheInterceptor過濾器,這篇博客主要分析下一個(gè)過濾器ConnectInterceptor捏萍。其實(shí)分析了OkHttp看了這么多代碼太抓,學(xué)習(xí)的不僅僅是OkHttp的處理邏輯和思路,從OkHttp的編程規(guī)范和命名規(guī)則其實(shí)也可以學(xué)習(xí)很多令杈,就像過濾器走敌,慢慢會(huì)發(fā)現(xiàn)每個(gè)過濾器的名字準(zhǔn)確的定位了每個(gè)過濾器的任務(wù)。

ConnectInterceptor正如名字所示逗噩,是OkHttp中負(fù)責(zé)和服務(wù)器建立連接的過濾器掉丽,其實(shí)到這里已經(jīng)可以慢慢意識(shí)到OkHttp已經(jīng)和Android已有的網(wǎng)絡(luò)框架Volley跌榔,Android-async-http等的不同,Volley的底層是提供HttpStack的接口捶障,利用策略模式僧须,這里對(duì)版本進(jìn)行了判斷,大于等于2.3則創(chuàng)建HttpURLConnection,小于則創(chuàng)建HttpClientStack项炼,也就是說實(shí)際上與服務(wù)器建立連接的是利用Google提供的兩種連接服務(wù)器的類(具體可以看我原來分析過的Volley源碼系列)担平。也就是說Volley開發(fā)的層次面到此也就結(jié)束了。但是這里OkHttp的不同點(diǎn)就很明顯锭部,OkHttp沒有單純的直接使用上面提到的Google提供的現(xiàn)有的HttpURLConnection等類來直接建立連接暂论,而是專門使用一個(gè)過濾器用于建立連接,也就是說在建立連接的層面也有開發(fā)和優(yōu)化(Okio)拌禾。我的理解取胎,從開發(fā)歸屬層面來說其實(shí)Okhttp是更深層次的,將建立網(wǎng)絡(luò)請(qǐng)求優(yōu)化一直衍生到Socket連接的湃窍。

分析

1.宏觀流程

一樣的配方闻蛀,先從大體流程上對(duì)這個(gè)過濾器進(jìn)行理解,當(dāng)然就是看這個(gè)過濾器的intercept方法您市。出乎意料的是這個(gè)過濾器單從這個(gè)方法來看沒有成噸的方法和代碼行觉痛,不需要我們做過多的刪減。

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

    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //建立HttpCodec
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    //獲取連接
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

可以看到這里流程上看很簡(jiǎn)單:

1.建立HttpCodec對(duì)象茵休。
2.調(diào)用streamAllocation.connection()獲取連接秧饮。

所以大體的流程上來看可以看出這個(gè)過濾器的作用就是來建立連接的。

2.過程細(xì)節(jié)

(1)HttpCodec

這里第一步是HttpCodec的建立過程泽篮。所以理所當(dāng)然首先要了解一下HttpCodec是個(gè)什么東西。

/** Encodes HTTP requests and decodes HTTP responses. */
public interface HttpCodec {
  int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
  Sink createRequestBody(Request request, long contentLength);
  void writeRequestHeaders(Request request) throws IOException;
  void flushRequest() throws IOException;
  void finishRequest() throws IOException;
  Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;
  ResponseBody openResponseBody(Response response) throws IOException;
  void cancel();
}

不出意外這是個(gè)借口柑船,不得不說Okhttp面向接口編程的思想體現(xiàn)的真的很好帽撑。這里我特意把這個(gè)類的注釋留了下來,通過注釋我們知道了這個(gè)接口的作用是編碼和解碼HTTP響應(yīng)HTTP請(qǐng)求观游。順便看一個(gè)方法其實(shí)也可以看出個(gè)大概查描。

(2)streamAllocation.newStream

public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    int connectTimeout = chain.connectTimeoutMillis();
    int readTimeout = chain.readTimeoutMillis();
    int writeTimeout = chain.writeTimeoutMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
      //建立HttpCodec
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

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

接下來就要看這個(gè)HttpCodec的建立過程丹诀,可以看到這里其實(shí)就兩步。

1.findHealthyConnection找到一條“健康”的連接
2.建立HttpCodec

這里先看你findHealthyConnection這個(gè)方法及塘,一開始我是很懵的,何為“健康”锐极。

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws IOException {
    while (true) { //循環(huán)查找一個(gè)鏈接
      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;
    }
  }

這里的流程是這樣的笙僚,其實(shí)從方法層面上來看流程還是比較好理解的。

RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,connectionRetryEnabled);

1).while循環(huán)遍歷尋找一個(gè)連接灵再,既然是遍歷就會(huì)發(fā)現(xiàn)OkHttp中是存在連接池的概念的肋层,這也是OkHttp中的一個(gè)特有的優(yōu)化亿笤。

synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }

2)日常線程安全措施,如果建立的連接candidate是新建立的(新的當(dāng)然還沒有用過栋猖,所以successCount=0)净薛,直接返回,不再需要后面的“健康檢查”蒲拉。這里的線程安全當(dāng)然是保證當(dāng)兩個(gè)線程同事進(jìn)行檢查的時(shí)候發(fā)生的情況肃拜,保證線程安全。

//如果這條連接不健康
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        //禁止這條連接
        noNewStreams();
        continue;
      }

3)進(jìn)行安全檢查雌团,如果不健康了燃领,禁止這條連接,繼續(xù)執(zhí)行循環(huán)辱姨,繼續(xù)在連接池中查找能用的連接柿菩。
4)返回得到的連接。

這里在來詳細(xì)看一下1)個(gè)步驟雨涛,也就是查找健康的連接這個(gè)過程findConnection枢舶。

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      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) {
        //如果當(dāng)前connection不為空可以直接使用
        // We had an already-allocated connection and it's good.
        result = this.connection;
        releasedConnection = null;
      }
      if (!reportedAcquired) {
        // If the connection was never reported acquired, don't report it as released!
        releasedConnection = null;
      }

      //當(dāng)前這個(gè)connection不能使用,嘗試從連接池里面獲取一個(gè)請(qǐng)求
      if (result == null) {
        // Attempt to get a connection from the pool.
        //Internal是一個(gè)抽象類替久,instance是在OkHttpClient中實(shí)現(xiàn)的凉泄,get方法實(shí)現(xiàn)的時(shí)候從pool的get方法
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    closeQuietly(toClose);
    //釋放一條連接,回調(diào)
    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
    //如果找到復(fù)用的,則使用這條連接蚯根,回調(diào)
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
      //找到一條可復(fù)用的連接
      // 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.
    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.
        //遍歷RooteSelector
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {
          Route route = routes.get(i);
          Internal.instance.get(connectionPool, address, this, route);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }

      if (!foundPooledConnection) {
        //沒找到則創(chuàng)建一條
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }

        // 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, false);
      }
    }

    // If we found a pooled connection on the 2nd time around, we're done.
    //如果第二次找到了可以復(fù)用的,則返回
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }

    // Do TCP + TLS handshakes. This is a blocking operation.
    // 建立連接,開始握手
    result.connect(
        connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
    //將這條路由從錯(cuò)誤緩存中清除
    routeDatabase().connected(result.route());

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

      // Pool the connection.
      //將這個(gè)請(qǐng)求加入連接池
      Internal.instance.put(connectionPool, result);

      // If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      // 如果是多路復(fù)用颅拦,則合并
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

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

這里我們一步一步看蒂誉。首先這里需要提前這個(gè)函數(shù)體中的相關(guān)變量,便于后面對(duì)過程的理解距帅。

    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    Socket toClose;

foundPooledConnection對(duì)應(yīng)是否在連接池中找到Connection右锨。
result對(duì)應(yīng)找到的可用的連接。
seletedRoute對(duì)應(yīng)找到的路由碌秸。
releasedConnection對(duì)應(yīng)可以釋放的連接绍移、
toClose對(duì)應(yīng)需要關(guān)閉的連接。
下面開始看流程:

      releasedConnection = this.connection;
      toClose = releaseIfNoNewStreams();
      if (this.connection != null) {
        //如果當(dāng)前connection不為空可以直接使用
        // We had an already-allocated connection and it's good.
        result = this.connection;
        releasedConnection = null;
      }

這里如果當(dāng)前的StreamAllocation持有的Connection先賦值給releasedConnection讥电,執(zhí)行releaseIfNoNewStreams()方法獲得需要關(guān)閉的Socket蹂窖。如果當(dāng)前的Connection不為空,則非常棒(注釋...),暫且將這個(gè)連接賦值個(gè)result恩敌,將releasedConnection賦值為空瞬测。這里看一下releaseIfNoNewStreams()方法。

/**
   * Releases the currently held connection and returns a socket to close if the held connection
   * restricts new streams from being created. With HTTP/2 multiple requests share the same
   * connection so it's possible that our connection is restricted from creating new streams during
   * a follow-up request.
   */
  private Socket releaseIfNoNewStreams() {
    assert (Thread.holdsLock(connectionPool));
    RealConnection allocatedConnection = this.connection;
    if (allocatedConnection != null && allocatedConnection.noNewStreams) {
      return deallocate(false, false, true);
    }
    return null;
  }

這里先從看注釋看一下,其實(shí)注釋寫的很清楚

釋放當(dāng)前的連接涣楷,返回一個(gè)socket為了防止當(dāng)前的連接限制了新的連接被create分唾。由于Http2多個(gè)請(qǐng)求可以用一條連接的特性,所以我們連接可能會(huì)限制后續(xù)的請(qǐng)求狮斗。
從代碼上看绽乔,先將當(dāng)前的Connection賦值給需要回收的連接allocatedConnection,如果allocatedConnection不為空(也就是當(dāng)前的連接不為空)碳褒,并且當(dāng)前的連接沒有新的流可以創(chuàng)建折砸,則釋放這條連接。否則返回空沙峻。所以這個(gè)方法的作用其實(shí)可以歸結(jié)到以下幾點(diǎn):
1.如果當(dāng)前這條連接為空睦授,也就是沒有連接,直接返回null.
2.如果當(dāng)期這條連接不為空摔寨,并且還可以創(chuàng)建流(也就是還可以用)去枷,返回null.
3.如果當(dāng)前這條連接不為空,并且不能再創(chuàng)建流了(不能用了)是复,回收删顶。

這里看一下回收的方法deallocate。

/**
   * Releases resources held by this allocation. If sufficient resources are allocated, the
   * connection will be detached or closed. Callers must be synchronized on the connection pool.
   *
   * <p>Returns a closeable that the caller should pass to {@link Util#closeQuietly} upon completion
   * of the synchronized block. (We don't do I/O while synchronized on the connection pool.)
   */
  private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
    assert (Thread.holdsLock(connectionPool));

    if (streamFinished) {
      this.codec = null;
    }
    if (released) {
      this.released = true;
    }
    Socket socket = null;
    if (connection != null) {
      if (noNewStreams) {
        connection.noNewStreams = true;
      }
      if (this.codec == null && (this.released || connection.noNewStreams)) {
        release(connection);
        if (connection.allocations.isEmpty()) {
          connection.idleAtNanos = System.nanoTime();
          if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
            socket = connection.socket();
          }
        }
        connection = null;
      }
    }
    return socket;
  }

從注釋上看淑廊,其實(shí)就可以發(fā)現(xiàn)這個(gè)方法的作用其實(shí)就是回收資源逗余,也就是將所持有的資源至空,關(guān)閉季惩。
這里可以看一下做了哪些录粱。

1.codec = null
2.released = true
3.noNewStreams = true
4.connection = null
5.返回connection對(duì)應(yīng)的socket

其實(shí)可以看到,當(dāng)這一系列方法執(zhí)行完后画拾,如果有可以回收關(guān)閉的Connection啥繁,則最后釋放Connection持有的資源后,返回了這個(gè)Connection對(duì)應(yīng)的Socket給toClose青抛。接下來看下一步输虱。

      //當(dāng)前這個(gè)connection不能使用,嘗試從連接池里面獲取一個(gè)請(qǐng)求
      if (result == null) {
        // Attempt to get a connection from the pool.
        //Internal是一個(gè)抽象類脂凶,instance是在OkHttpClient中實(shí)現(xiàn)的,get方法實(shí)現(xiàn)的時(shí)候從pool的get方法
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }

可以看到這里如果上面的第一個(gè)沒有合適的連接愁茁,result==null蚕钦,這時(shí)候就是OkHttp的獨(dú)特之處:連接池

Internal.instance.get(connectionPool, address, this, null);


//Internal.java
public abstract class Internal {
  ...
  public static Internal instance;
  ...
}

這里可以看到用到了Internal這個(gè)對(duì)象鹅很,這個(gè)對(duì)象通過查看源碼可以發(fā)現(xiàn)是一個(gè)抽象類嘶居,并且實(shí)際上調(diào)用的是instance對(duì)象,看到這個(gè)名詞其實(shí)第一個(gè)反應(yīng)就是單例模式,源碼上看也沒錯(cuò),這里確實(shí)是單例模式中的類似餓漢模式邮屁。但是不同的是這里的初始化并沒有在這里寫整袁,其實(shí)也難怪,這個(gè)類是抽象類佑吝,是不能初始化的坐昙,所以這里我們就需要找到這個(gè)抽象類的實(shí)現(xiàn)類。通過尋找可以發(fā)現(xiàn)這個(gè)類的實(shí)現(xiàn)類的初始化是在OkHttpClient中芋忿,這里進(jìn)到源碼中看一看炸客。

static {
    Internal.instance = new Internal() {
    ...
      @Override public RealConnection get(ConnectionPool pool, Address address,
          StreamAllocation streamAllocation, Route route) {
        return pool.get(address, streamAllocation, route);
      }
    ...
    }

這里可以看到這里初始化是在靜態(tài)代碼塊中寫的,也就是在類加載的時(shí)候初始化的戈钢,這里我們調(diào)用了get方法痹仙,其實(shí)可以看到實(shí)際上調(diào)用的是ConnectionPool.get()方法。所以繼續(xù)看源碼殉了。

/**
   * 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) {
    //這種方法的目的是允許一個(gè)程序斷言當(dāng)前線程已經(jīng)持有指定的鎖
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.isEligible(address, route)) {
        //連接池里面存在可以復(fù)用的連接
        //往連接池中這條可以復(fù)用的連接增加一條流
        streamAllocation.acquire(connection, true);
        return connection;
      }
    }
    return null;
  }

注釋其實(shí)也可以幫助我們理解开仰,這里就不翻譯了,其實(shí)代碼也比較清楚薪铜,遍歷pool中的connections(ArrayQueue),如果連接是可以復(fù)用的众弓,則將這個(gè)連接返回。
這里看一下判斷連接可以復(fù)用的isEligible()方法痕囱。

/**
   * Returns true if this connection can carry a stream allocation to {@code address}. If non-null
   * {@code route} is the resolved route for a connection.
   */
  public boolean isEligible(Address address, @Nullable Route route) {
    // If this connection is not accepting new streams, we're done.
    //如果當(dāng)前這次連接的最大并發(fā)數(shù)達(dá)到上限田轧,false
    if (allocations.size() >= allocationLimit || noNewStreams) return false;

    // If the non-host fields of the address don't overlap, we're done.
    //如果兩個(gè)address的其他參數(shù)不相同,false
    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;

    // If the host exactly matches, we're done: this connection can carry the address.
    //如果兩個(gè)address的url的host相同鞍恢,true,復(fù)用這條連接
    if (address.url().host().equals(this.route().address().url().host())) {
      return true; // This connection is a perfect match.
    }
    //如果上面的不符合傻粘,在下面的情況下可以合并連接
    // At this point we don't have a hostname match. But we still be able to carry the request if
    // our connection coalescing requirements are met. See also:
    // https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
    // https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/
    //首先這個(gè)連接需要時(shí)HTTP/2
    // 1. This connection must be HTTP/2.
    if (http2Connection == null) return false;

    // 2. The routes must share an IP address. This requires us to have a DNS address for both
    // hosts, which only happens after route planning. We can't coalesce connections that use a
    // proxy, since proxies don't tell us the origin server's IP address.
    if (route == null) return false;
    //代理不可以
    if (route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
    //IP address需要相同
    if (!this.route.socketAddress().equals(route.socketAddress())) return false;

    // 3. This connection's server certificate's must cover the new host.
    //這個(gè)連接的服務(wù)器證書必須覆蓋新的主機(jī)。
    if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
    if (!supportsUrl(address.url())) return false;

    // 4. Certificate pinning must match the host.
    //證書將必須匹配主機(jī)
    try {
      address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
    } catch (SSLPeerUnverifiedException e) {
      return false;
    }

    return true; // The caller's address can be carried by this connection.
  }

這里其實(shí)涉及到的其實(shí)是比較多的HTTP和HTTP/2的知識(shí)帮掉,原理細(xì)節(jié)上準(zhǔn)備后期入手本書研究研究弦悉,這里其實(shí)流程上理解還是比較容易的,總結(jié)一下,這里連接池里的一個(gè)連接可以復(fù)用的判定條件有這幾個(gè)(注釋我寫的也比較清楚):

1.當(dāng)前的連接的最大并發(fā)數(shù)不能達(dá)到上限蟆炊,否則不能復(fù)用
2.兩個(gè)連接的address的Host不相同稽莉,不能復(fù)用
3.1、2通過后涩搓,url的host相同則可以復(fù)用
4.如果3中url的host不相同污秆,可以通過合并連接實(shí)現(xiàn)復(fù)用
5.但首先這個(gè)連接需要時(shí)HTTP/2
6.不能是代理
7.IP的address要相同
8.這個(gè)連接的服務(wù)器證書必須覆蓋新的主機(jī)
9.證書將必須匹配主機(jī)
10.以上都不行,則這個(gè)連接就不能復(fù)用

其實(shí)這里主要就是分為兩種復(fù)用方式:一.host相同直接復(fù)用連接昧甘。二.如果是HTTP/2良拼,通過其特性合并連接復(fù)用。
這里看完判斷連接是否合格的方法后充边,就執(zhí)行acquire()方法庸推,這里來看一下。

/**
//pool中的get方法
   streamAllocation.acquire(connection, true);
//StreamAllocation中的acquire方法
   * Use this allocation to hold {@code connection}. Each call to this must be paired with a call to
   * {@link #release} on the same connection.
   */
  public void acquire(RealConnection connection, boolean reportedAcquired) {
    assert (Thread.holdsLock(connectionPool));
    if (this.connection != null) throw new IllegalStateException();

    this.connection = connection;
    this.reportedAcquired = reportedAcquired;
    //往這條連接中增加一條流
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  }

可以看到這里如果通過isEligible()判斷通過后,執(zhí)行acquire方法贬媒。
這里講reportedAcquired設(shè)置為true聋亡,并向connection持有的allocations中增加了一條新new的流的弱引用,也就是往這條連接中增加了一條流际乘。
至此從連接池中的get方法也分析結(jié)束了坡倔,要回到我們的主線方法中了,也就是findConnection()方法中蚓庭。

//當(dāng)前這個(gè)connection不能使用致讥,嘗試從連接池里面獲取一個(gè)請(qǐng)求
      if (result == null) {
        // Attempt to get a connection from the pool.
        //Internal是一個(gè)抽象類,instance是在OkHttpClient中實(shí)現(xiàn)的器赞,get方法實(shí)現(xiàn)的時(shí)候從pool的get方法
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    closeQuietly(toClose);
    //釋放一條連接,回調(diào)
    if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
    //如果找到復(fù)用的垢袱,則使用這條連接,回調(diào)
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
      //找到一條可復(fù)用的連接
      // If we found an already-allocated or pooled connection, we're done.
      return result;
    }

可以看到這里執(zhí)行完get方法后港柜,如果connection请契!=null,則標(biāo)記foundPooledConnection = true,將connection賦值給result夏醉,沒找到則保存當(dāng)前路由route到selectedRoute變量爽锥。執(zhí)行完這一系列東西后則是一些關(guān)閉和回調(diào)操作,最后如果找到了可用的連接畔柔,則返回這條可以復(fù)用的連接氯夷。

// If we need a route selection, make one. This is a blocking operation.
    boolean newRouteSelection = false;
    //切換路由再在連接池里面找下,如果有則返回
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      newRouteSelection = true;
      routeSelection = routeSelector.next();
    }

如果上面沒有找到可以復(fù)用的連接靶擦,則繼續(xù)執(zhí)行下面的步驟腮考,可以看這里其實(shí)做的是切換路由的操作。

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.
        //遍歷RooteSelector
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {
          Route route = routes.get(i);
          Internal.instance.get(connectionPool, address, this, route);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }

     ...
    }

可以到這里切換完路由后玄捕,其實(shí)就是遍歷路由踩蔚,再執(zhí)行一次上面分析過的Internal.instance.get()方法,也就是在切換完路由后再嘗試在連接池中尋找可以復(fù)用的連接.

if (!foundPooledConnection) {
        //沒找到則創(chuàng)建一條
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }

        // 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, false);
      }

如果經(jīng)歷了上面的操作后還是沒有找到可以復(fù)用的連接枚粘,那么則創(chuàng)建一個(gè)新的連接馅闽,終于看到了RealConnection的構(gòu)造方法,new了一個(gè)新的RealConnection,并賦值給result馍迄,并執(zhí)行了上面分析過的acquire()方法福也,往new的連接中加入了流。

當(dāng)然這里還沒有說如果剛在交換路由后找到可以復(fù)用的連接怎么辦攀圈,接著往下看拟杉。

// If we found a pooled connection on the 2nd time around, we're done.
    //如果第二次找到了可以復(fù)用的,則返回
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }

可以看到如果第二次找到了量承,同樣回調(diào),然后返回找到的連接。

// Do TCP + TLS handshakes. This is a blocking operation.
    // 建立連接,開始握手
    result.connect(
        connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
    //將這條路由從錯(cuò)誤緩存中清除
    

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

      // Pool the connection.
      //將這個(gè)請(qǐng)求加入連接池
      Internal.instance.put(connectionPool, result);

      // If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      // 如果是多路復(fù)用撕捍,則合并
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);

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

接下來的這些代碼都是基于沒有找到可以復(fù)用的連接這一前提下的拿穴,沒有找到可以復(fù)用的,則是上面6)new出來的新的連接忧风,所以接下來的代碼就是執(zhí)行connect()方法默色,里面其實(shí)就涉及到三次握手連接流程了。
后面執(zhí)行的這行代碼特意說一下狮腿,routeDatabase().connected(result.route());
單從代碼上看腿宰,可能只是理解為往數(shù)據(jù)庫(kù)記錄了下這個(gè)路由的記錄,但是詳細(xì)進(jìn)入看一下源碼缘厢。

public final class RouteDatabase {
  //這個(gè)太屌了吃度,錯(cuò)誤緩存,錯(cuò)誤過的連接會(huì)被緩存贴硫,防止錯(cuò)誤請(qǐng)求重復(fù)請(qǐng)求
  private final Set<Route> failedRoutes = new LinkedHashSet<>();

  /** Records a failure connecting to {@code failedRoute}. */
  public synchronized void failed(Route failedRoute) {
    failedRoutes.add(failedRoute);
  }

  /** Records success connecting to {@code route}. */
  public synchronized void connected(Route route) {
    failedRoutes.remove(route);
  }

  /** Returns true if {@code route} has failed recently and should be avoided. */
  public synchronized boolean shouldPostpone(Route route) {
    return failedRoutes.contains(route);
  }
}

代碼很簡(jiǎn)單椿每,思想和全面,用Set<>集合保存錯(cuò)誤過的數(shù)據(jù)集英遭,因?yàn)槭莕ew出來的連接间护,所有肯定沒有錯(cuò)誤,所以講這個(gè)路由從set中移除挖诸,防止多余的檢測(cè)汁尺,那么對(duì)應(yīng)的就可以聯(lián)想到這里肯定有如果路由發(fā)生過錯(cuò)誤的記錄,每次使用前先檢查一下多律,如果原來錯(cuò)誤過痴突,就不用執(zhí)行后面的流程了(考慮的很全面有木有,相當(dāng)于緩存了發(fā)生過錯(cuò)誤的信息)
執(zhí)行完這個(gè)后菱涤,就將這new得到的連接加入連接池苞也,
Internal.instance.put(connectionPool, result);
后面還有個(gè)多路合并的判斷,但是具體細(xì)節(jié)這里就不深入了(需要詳細(xì)了解HTTP+底層代碼)粘秆。
至此:findConnection()分析完了如迟,這里大體總結(jié)一下流程吧:

1.嘗試當(dāng)前連接是否可以復(fù)用。
2.嘗試連接池中找可以復(fù)用的連接
3.切換路由攻走,繼續(xù)在連接中嘗試找可以復(fù)用的連接
4.以上都沒有則new一個(gè)新的殷勘。

到這里其實(shí)findHealthyConnection()也分析完了,過程在上上上……上面已經(jīng)分析了昔搂。玲销。。再回到主流程上了摘符。

try {
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
      //建立HttpCodec
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

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

可以看到找到健康的連接后贤斜,執(zhí)行了newCodec方法策吠,得到了HttpCodec實(shí)例,這個(gè)上面我們已經(jīng)分析過了瘩绒,是一個(gè)接口猴抹,只是這里再放一下,便于回顧:

Encodes HTTP requests and decodes HTTP responses

這里就看一下newCodec方法

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

可以看到這里其實(shí)就是判斷是Http還是Http2锁荔,然后根據(jù)策略模式最后返回蟀给。
至此ConnectInterceptor過濾器的的全部流程就分析完了。再放一下主要方法的代碼:

@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 httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    //返回RealConnection
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

到此阳堕。跋理。。結(jié)束了恬总。前普。。沒有結(jié)束語越驻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末汁政,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子缀旁,更是在濱河造成了極大的恐慌记劈,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件并巍,死亡現(xiàn)場(chǎng)離奇詭異目木,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)懊渡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門刽射,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人剃执,你說我怎么就攤上這事誓禁。” “怎么了肾档?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵摹恰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我怒见,道長(zhǎng)俗慈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任遣耍,我火速辦了婚禮闺阱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舵变。我一直安慰自己酣溃,他們只是感情好瘦穆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赊豌,像睡著了一般难审。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亿絮,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音麸拄,去河邊找鬼派昧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拢切,可吹牛的內(nèi)容都是我干的蒂萎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼淮椰,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼五慈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起主穗,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤泻拦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后忽媒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體争拐,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年晦雨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了架曹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡闹瞧,死狀恐怖绑雄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奥邮,我是刑警寧澤万牺,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站漠烧,受9級(jí)特大地震影響杏愤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜已脓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一珊楼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧度液,春花似錦厕宗、人聲如沸画舌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽曲聂。三九已至,卻和暖如春佑惠,著一層夾襖步出監(jiān)牢的瞬間朋腋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工膜楷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旭咽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓赌厅,卻偏偏與公主長(zhǎng)得像穷绵,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子特愿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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