全面深入OkHttp源码(下)

分支开始Chain,保存RealCall位置

到了烂大街的责任链了

interceptors先添加okHttpClient对象中的应用层拦截器,紧接着RetryAndFollowUpInterceptor,之所以没在这里初始化,是因为Call对象需要持有RetryAndFollowUpInterceptor对象进行其他操作

接着是顺序添加的拦截器,最后添加了okHttpClient对象中的网络层拦截器

https://www.jianshu.com/u/0790f0629fc6

最终形态的Chain,Request经过重重拦截器的包装重整发送到Server,当Response返回时又逆向经历了重重拦截器的解析处理最终到达Client

道理很简单,但OkHttp的实现手法很高明

这里有两个接口Interceptor、Chain

RealInterceptorChain

Interceptor即拦截器接口只有一个intercept方法

而Chain只有一个实现类RealInterceptorChain,它是实现责任链的重要类,我更喜欢称其为

责任链开始之初,实例化了一个环,参数使用的时候再解释

接着环开始滚动proceed,我们以index参数作为环的标记

环不能够滚出链条,所以要判断当前环的位置

RealInterceptorChain

当前环滚动(调用)次数自加,判断参数条件,由于当前环各参数为空,所以不throw

RealInterceptorChain

紧接着环滚到了下一层,新环index=1,当前index=0的环取出第一个拦截器RetryAndFollowUpInterceptor,处理新环index=1,得到Response

index=i的环内生成index=i+1的新环,并交由拦截器链中第i项拦截器处理,第i项拦截器又调用环的process方法生成新环交给下一个拦截器,一直重复动作,直到环滚到头时,再滚回来,和算法中的递归相似

了解了责任链与环的协作,看看具体的拦截器做了什么

1、 RetryAndFollowUpInterceptor

生成了一个关键对象StreamAllocation,就执行下一个环的滚动。StreamAllocation类是协调Connections、Streams、Calls三者间关系的,使用时讲。

2、BridgeInterceptor

生成新的Requester.builder,在保留原参数的基础上增加新参数,至于具体参数的设置,相信有更加详细准确的资料这里就不显摆了。

我要说下body这个变量,来自RequestBody是个抽象类,有两个实现类,在Request初始化时赋值

RequestBody为网络请求传输的数据载体,共两部分:MediaType、Content,Content根据MediaType的不同而不同,可以是字节、字符串或文件,这三种类型已经满足了所有的请求内容。

可以通过RequestBody中静态方法,实现相应的数据传输

3、CacheInterceptor

用来返回相同请求且结果未更新的Response

分支开始Cache,保存CacheInterceptor位置

缓存来自okHttpClient对象,但在Builder中没有默认实现

OkHttpClient.Builder

需要手动完成,这里有两个Cache:InternalCache、Cache

官方已经不建议使用InternalCache,而是使用Cache替代

OkHttpClient

但OkHttpClient返回的却还是InternalCache,Cache只是InternalCache的一层具体实现

有点像Cache实现了InternalCache接口的味道,但实际是使用了类聚合

Cache

判断是否符合缓存条件,如果是POST、PATCH、PUT、DELETE、MOVE方法则移除缓存,意味着以上方法不缓存。

紧接着判断GET方法,只有GET方法的请求能够被缓存

再来判断vary头信息,具体内容看这里,决定是否缓存

所有条件都通过后,将response转成Entry存入DiskLruCache,key取请求URL的MD5

存取的关键逻辑在Entry类中

通过不同的构造方法,初始化变量

这里使用了okio相关的内容

读写顺序是相反的,按照图中的指示对照着看,如果没接触过okio,单独看会懵

分支结束Cache,读取CacheInterceptor位置

取到候选Response后,交由缓存策略

初始化缓存相关的Http头信息(这里真不是专业的后端开发,未避免误人子弟,各个头信息还请自行查询)

走到这一步,将缓存策略对象strategy交由缓存cache对象,用来统计缓存是否命中

候选Response取到了,但经过缓存策略判断不符合,则关闭候选Response的body,为什么?

cacheCandidate是从Cache中取的Response

Cache

经过了Entry的转换

image.png

此时snapshot被传入了

由于cacheCandidate从Cache缓存中经okio,取出,如今发现cacheCandidate不符合条件,所以需要关闭okio,类似流的概念

又到了缓存判断的阶段(网络缓存这方面逻辑是真复杂,其实并不是复杂,而是做移动端的应该对此都比较陌生)

CacheInterceptor

经过缓存策略判定根据不同的情况返回请求与response,若请求为空,缓存的response也空,直接返回HttpCode 504(Gateway timeout),为什么?

产生原因在这:

CacheStrategy

请求不null,但请求的参数设置了onlyIfCached,注释:我有请求参数,但我请求的response必须使用缓存,而不是来自服务器,当缓存不存在时就报了504

终于是最后的判断了:

由策略产出的networkRequest为空得出结论,此请求不需要真正执行,将缓存的response脱光返回

英语讲解:stripper 为strip的名词,双写p加er

走到这一步还没有合适的response,没办法交给下一环处理吧!!

4、ConnectInterceptor

重要的网络连接操作

ConnectInterceptor

获取之前环中生成的streamAllocation对象,调用其newStream方法

还记得streamAllocation是在RetryAndFollowUpInterceptor那一环中被实例化赋值的吗?

当时只是实例化对象没做任何操作,具体的参数上面都讲过了,返回看看参数的来源,会对整个流程更加清晰!

这里生成了RealConnection对象,接着RealConnection对象又生成了HttpCodec对象,一个个来

它是Connection接口的唯一实现类,看见红色框是不是很熟悉,BufferedSource、BufferedSink是okio中的输入输出流,有了socket和流就可以进行传输通信了,因此RealConnection是实际的连接类。

看看它是怎么产生的

如方法名,找到一个健康的Connection,何谓健康后面谈,先看看Connection怎么来的

  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;//是否找到Connection的标记
    RealConnection result = null;//找到的Connection
    Route selectedRoute = null;//路由
    Connection releasedConnection;//需要释放的Connection
    Socket toClose;//需要关闭的socket
    synchronized (connectionPool) {//锁住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;//将自身的Connection记为需要释放的状态
      toClose = releaseIfNoNewStreams();//Connection上可以生成多个流,noNewStreams标志表示此Connection是否能够产生新的流
      if (this.connection != null) {//复用之前的connection
        // We had an already-allocated connection and it's good.
        result = this.connection;
        releasedConnection = null;
      }
      if (!reportedAcquired) {//streamAllocation对象中标记是否已经获取到connection
        // If the connection was never reported acquired, don't report it as released!
        releasedConnection = null;
      }

      if (result == null) {//走到这里,表明streamAllocation对象中没有可使用的connection
        // Attempt to get a connection from the pool.
        Internal.instance.get(connectionPool, address, this, null);//从connectionPool池中按要求取一个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.
    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 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) {//找到符合路由的connection
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }

      if (!foundPooledConnection) {//没找到
        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);//根据路由自己创建Connection
        acquire(result, false);//通知并赋值
      }
    }

    // If we found a pooled connection on the 2nd time around, we're done.
    if (foundPooledConnection) {//如果在池子找到则回调,否则不回调,因为池子找到的connection已经连接好了,否则还要经过下面的连接
      eventListener.connectionAcquired(call, result);
      return result;
    }

    // Do TCP + TLS handshakes. This is a blocking operation.
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);//连接
    routeDatabase().connected(result.route());//记录路由

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

      // Pool the connection.
      Internal.instance.put(connectionPool, result);//创建的connection放入池子管理

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

    eventListener.connectionAcquired(call, result);//一切完成回调
    return result;
  }

经过了重重的筛选,无论是池子中获取的已经连接的connection,还是自行创建并连接放入池子的connection对象,StreamAllocation对象总算持有了connection对象,现在对connection对象进行健康检查

如果此connection对象是新生的不是在池子取的,一定健康

RealConnection

具体的健康检查有:socket是否关闭、http2的话连接是否关闭、非get请求还要检验流是否关闭

如果connection不健康,释放connection关闭socket和流,并从connectionPool中移除

并将与此connection相关联的所有StreamAllocation引用移除

引用是在获取到connection对象时建立的

StreamAllocation对象获取了健康的connection对象,紧接着有用connection对象生成了解码HttpCodec对象

根据http协议的不同生成,Http1时可以看到流在此处配置,那socket和流什么时候初始化的?

前面只说了connection对象从池子取或自己连接,那最初的那个connection对象一定是自连接的,根据这个思路看看RealConnection类

StreamAllocation

connection对象持有池子connectionPool和路由route对象,连接又是大串的代码

  public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
      EventListener eventListener) {
    if (protocol != null) throw new IllegalStateException("already connected");//还没尝试连接就知道协议了,状态肯定错了

    RouteException routeException = null;
    List connectionSpecs = route.address().connectionSpecs();
    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);

    if (route.address().sslSocketFactory() == null) {//是否https
      if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {//不是https且不支持明文
        throw new RouteException(new UnknownServiceException(
            "CLEARTEXT communication not enabled for client"));
      }
      String host = route.address().url().host();
      if (!Platform.get().isCleartextTrafficPermitted(host)) {//这里根据okhttp使用平台的不同而判断,是否明文
        throw new RouteException(new UnknownServiceException(
            "CLEARTEXT communication to " + host + " not permitted by network security policy"));
      }
    } else {//https的话不允许使用prior_knowledge协议
      if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
        throw new RouteException(new UnknownServiceException(
            "H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"));
      }
    }

    while (true) {
      try {
        if (route.requiresTunnel()) {//根据路由判断是建立隧道还是socket连接
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);//建立隧道
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break;
          }
        } else {
          connectSocket(connectTimeout, readTimeout, call, eventListener);//建立socket
        }
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);//连接建立起后,初始化协议
        eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);//回调连接解释
        break;
      } catch (IOException e) {//连接异常捕获处理
        closeQuietly(socket);
        closeQuietly(rawSocket);
        socket = null;
        rawSocket = null;
        source = null;
        sink = null;
        handshake = null;
        protocol = null;
        http2Connection = null;

        eventListener.connectFailed(call, route.socketAddress(), route.proxy(), null, e);//连接异常回调

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

        if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {//设置重连则不抛
          throw routeException;
        }
      }
    }

    if (route.requiresTunnel() && rawSocket == null) {//如果路由隧道的代理是http的而请求是https则隧道异常
      ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "
          + MAX_TUNNEL_ATTEMPTS);
      throw new RouteException(exception);
    }

    if (http2Connection != null) {//这里则是http2的多连接复用
      synchronized (connectionPool) {
        allocationLimit = http2Connection.maxConcurrentStreams();
      }
    }
  }

先看看熟悉的socket连接

如果代理模式为直连或Http则直接生成socket,否则是代理socket

socketFactory从okHttpclient对象传来,默认为DefaultSocketFactory直接new Socket返回

回调连接开始,根据平台连接socket并初始化流。

隧道有中间代理所以要生成代理请求

先创建了socket,后又创建隧道,createTunnel方法创建隧道,方法内先发送隧道请求,紧接着就是阻塞状态下不停的读取数据,直到返回结束状态。

到此connection有了,其内部的socket也已经连接

解码器的功能就是实现socket数据的编解码,操作主要以HttpCodec对象为主,其内部持有了连接好的socket对象

5、CallServerInterceptor

经过前面几环不同的初始化,最后一环终于可以大展拳脚了

//CallServerInterceptor.class
  @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());//回调
    httpCodec.writeRequestHeaders(request);//使用httpCodec发送头信息
    realChain.eventListener().requestHeadersEnd(realChain.call(), 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"))) {//请求头中有{"Expect":"100-continue"}表明有请求体数据上传征求服务器意见,具体看服务端有没有实现此协议,若没有client也会在等待超时后发送请求体
        httpCodec.flushRequest();//发送头信息
        realChain.eventListener().responseHeadersStart(realChain.call());//回调
        responseBuilder = httpCodec.readResponseHeaders(true);//读头信息
      }

      if (responseBuilder == null) {//服务端接受了100-continue
        // Write the request body if the "Expect: 100-continue" expectation was met.
        realChain.eventListener().requestBodyStart(realChain.call());//回调
        long contentLength = request.body().contentLength();//body长度
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);//这两个对象为okio,类似流

        request.body().writeTo(bufferedRequestBody);//具体的内容写入流中
        bufferedRequestBody.close();//关闭流
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);//回调写出多少数据
      } else if (!connection.isMultiplexed()) {//服务端不接受100-continue则设置此connection对象不能生成新流
        // 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) {//无body时进入获取response
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();//在原基础上新建response,这也是构建者模式的优势

    int code = response.code();//服务端返回码
    if (code == 100) {//服务端返回100表示让client继续发送数据
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      responseBuilder = httpCodec.readResponseHeaders(false);//重新读一遍返回信息

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

      code = response.code();
    }

    realChain.eventListener()
            .responseHeadersEnd(realChain.call(), response);//回调

    if (forWebSocket && code == 101) {//请求使用的是webSocket,且服务端已接受
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)//由于websocket的性质,所以给body占位
          .build();
    } else {//读取返回信息
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {//请求为一次连接or服务端返回关闭连接
      streamAllocation.noNewStreams();//使连接不能够产生新流
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {//204表示没内容,205表示清空表单,但返回的数据却有长度,明显此次的通信异常了
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

HttpCodec具体的数据读写看实现类Http1Codec,使用的是okio

拼接请求行,抓包时常见

RequestLine

将请求行与头信息写入流

同socket的flush

读取头信息,红框标准的地方也是为什么

这里要判null的原因

请求体的创建包了好几层,chunked表示空请求体,请求体实际也是一个流,FixedLengthSink继承自Sink

finish同flush

创建ResponseBody,此时的body还不是内容,只是流供上层拦截器使用。

最终Response获取到了,环滚到头又要向回滚了

4、ConnectInterceptor

对response没做处理直接返回

3、CacheInterceptor

对返回的response做缓存处理

    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);//上一环返回的response
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {//结果异常,但有缓存,关闭缓存的流
        closeQuietly(cacheCandidate.body());
      }
    }
//为什么只有try-finally,发生异常时不捕获而是向上抛出,在抛出异常前做finally的保护工作。这种写法是为了充分的暴露异常交给外层处理
    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {//到此处一定有networkResponse,否则早在上步异常了,当有networkResponse且有cacheResponse时根据状态码决定取哪个
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {//304表示内容自上次获取后就没被修改过
        Response response = cacheResponse.newBuilder()//直接使用缓存cacheResponse
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))//将networkResponse和cacheResponse的头信息合并
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))//这里只保留cacheResponse除body外的信息,因为body是流
            .networkResponse(stripBody(networkResponse))//同上
            .build();
        networkResponse.body().close();//既然使用缓存,那返回的networkResponse就需要将body流关闭

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();//记录缓存命中
        cache.update(cacheResponse, response);//更新缓存
        return response;//直接返回response
      } else {//有缓存但是缓存已过时,不使用cacheResponse,关闭cacheResponse中body的流
        closeQuietly(cacheResponse.body());
      }
    }

    Response response = networkResponse.newBuilder()//在返回networkResponse的基础上使用构造者模式
        .cacheResponse(stripBody(cacheResponse))//不要body
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {//okhttp有缓存策略
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {//返回有body,且http返回信息支持缓存
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);//存入缓存,返回一个带有response.body输入流的cacheRequest
        return cacheWritingResponse(cacheRequest, response);//将cacheRequest中的response.body输入流与response的body流连接,新建Response并返回
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {//根据请求方法判断是否缓存,除get外其余不许缓存
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

2、BridgeInterceptor

保存cookie和解压

    Response networkResponse = chain.proceed(requestBuilder.build());//从上一环接棒

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());//从headers信息中取出cookie,与URL关联后存入cookieJar,CookieJar是一个接口需要手动实现,OkHttp默认没有存储

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);//从这一环传递到下一环的请求信息是经过了加工的,因此返回的时候要将原始的userRequest恢复回来,否则用户会奇怪请求多了些没设置过的参数

    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {//请求是否开启了gzip压缩,默认设置是开了,且Response也使用了gzip压缩,且Response有body
      GzipSource responseBody = new GzipSource(networkResponse.body().source());//将body流转为Gzip流
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();//从headers中移除项
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));//将原body流切换成Gzip流
    }

    return responseBuilder.build();

1、RetryAndFollowUpInterceptor

重连

    while (true) {//因为重连所以要无限循环
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;//释放连接标志
      try {
        response = realChain.proceed(request, streamAllocation, null, null);//从上环中获取
        releaseConnection = false;//是否释放连接,当发生预料外的Exception时释放
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {//是否异常能被解决
          throw e.getFirstConnectException();
        }
        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, streamAllocation, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {//最终异常不能被解决则释放连接
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {//因为有重连,所以要记录上次连接的返回
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Request followUp;//下步连接操作
      try {
        followUp = followUpRequest(response, streamAllocation.route());//根据返回状态和路由判断是否有下次连接
      } catch (IOException e) {
        streamAllocation.release();
        throw e;
      }

      if (followUp == null) {//无下次连接
        if (!forWebSocket) {//不是websocket协议
          streamAllocation.release();//这次请求完成,关闭连接
        }
        return response;//返回结果
      }

      closeQuietly(response.body());//还有下次连接则关闭,response.body的流

      if (++followUpCount > MAX_FOLLOW_UPS) {//连接次数超限
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {//不许重连
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }

      if (!sameConnection(response, followUp.url())) {//根据host、port、scheme三个标准判断下次连接与这次是否相同,多次请求与重定向的区别
        streamAllocation.release();//重定向的话要用新的连接
        streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(followUp.url()), call, eventListener, callStackTrace);
        this.streamAllocation = streamAllocation;
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;//请求变为新请求
      priorResponse = response;//保存此次的response为priorResponse
    }

分支结束Chain,读取RealCall位置

经过了重重拦截器的处理获得了result,来到了最初的地方

完结


这里仅从GET请求出发,就有如此多的内容。要体会OkHttp的深度,不仅仅要具有算法、设计模式、平台特性,这些软件工程师需千锤百炼才能有的技能外,还需要有扎实的网络基础与实际的网络经验。

恰恰网络方面是我的软肋,这次对OkHttp源码的深入,使我对网络通信中关键的点有了模糊的认识。

相信能看到这里的人,自己看一遍call.enqueue应该不是问题。

你可能感兴趣的:(全面深入OkHttp源码(下))