美文网首页
Android-OKhttp底层原理浅析(三)

Android-OKhttp底层原理浅析(三)

作者: 广州萧敬腾 | 来源:发表于2019-05-05 16:15 被阅读0次

    第一篇讲的是同步、异步调用源码走向Android-OKHTTP底层原理浅析(一)
    第二篇讲的是重定向拦截器、桥拦截器的工作内容Android-OKHttp底层原理浅析(二)
    这篇来讲剩下的拦截器,CacheInterceptor , ConnectInterceptor , CallServerInterceptor
    首先看CacheInterceptor——缓存拦截器

    @Override public Response intercept(Chain chain) throws IOException {
        Response cacheCandidate = cache != null
            ? cache.get(chain.request())
            : null;
    
        long now = System.currentTimeMillis();
    
        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) {
          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.
        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) {
          return cacheResponse.newBuilder()
              .cacheResponse(stripBody(cacheResponse))
              .build();
        }
    
        Response networkResponse = null;
        try {
          networkResponse = chain.proceed(networkRequest);
        } finally {
          // If we're crashing on I/O or otherwise, don't leak the cache body.
          if (networkResponse == null && cacheCandidate != null) {
            closeQuietly(cacheCandidate.body());
          }
        }
    
        // If we have a cache response too, then we're doing a conditional get.
        if (cacheResponse != null) {
          if (networkResponse.code() == HTTP_NOT_MODIFIED) {
            Response response = cacheResponse.newBuilder()
                .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();
            networkResponse.body().close();
    
            // 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;
          } else {
            closeQuietly(cacheResponse.body());
          }
        }
    
        Response response = networkResponse.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
    
        if (HttpHeaders.hasBody(response)) {
          CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
          response = cacheWritingResponse(cacheRequest, response);
        }
    
        return response;
      }
    

    pia一下又是一堆代码,别慌,挑重点,跟哥走, 这一段代码我们需要明确几个关键对象

    Response cacheCandidate = cache != null
            ? cache.get(chain.request())
            : null;
    
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
        Request networkRequest = strategy.networkRequest;
        Response cacheResponse = strategy.cacheResponse;
    

    一个是cacheCandidate ,一个是networkRequest ,一个是cacheResponse
    你仔细看看就会发现这整一篇,都是这三个参数的非空判断

    if (cacheCandidate != null && cacheResponse == null) {}
    
    // If we're forbidden from using the network and the cache is insufficient, fail.
        if (networkRequest == null && cacheResponse == null) {}
    
    // If we don't need the network, we're done.
        if (networkRequest == null) {}
    
    // If we have a cache response too, then we're doing a conditional get.
        if (cacheResponse != null) {}
    

    对吧,差不多有这几个判断,好几个已经官方都给注释上了,这里笼统的描述一下这个流程线,如果真的要完全搞懂可能得去研究一下http的缓存策略,

    第一个如果候选缓存cacheCandidate 不适用,就调用closeQuietly()方法关闭了
    第二个networkRequest跟cacheResponse 都为空的时候表示如果被禁止使用网络并且缓存不足,则返回失败, 可以看到在这里是返回了一个504出去
    第三个那如果只有网络不适用的情况(因为上面已经判断过cacheResponse == null了,能走到这个流程说明缓存是有的),那没网络有缓存咋办?还能咋地,把缓存返回出去呗

    接下来就走执行下一个拦截器的代码了

    networkResponse = chain.proceed(networkRequest);
    

    能走到这一步来说明networkRequest不等于空,就是说有网络的,才会去发起请求(啊,剧透了后面的拦截器了)
    ok,走完回来

    第四个判断,此时我们的请求已经回来了,但是我们也有一个缓存存在(cacheResponse != null)这时咋办?缓存对比

    if (networkResponse.code() == HTTP_NOT_MODIFIED) {}
    

    HTTP_NOT_MODIFIED是304,这里可以直接验证我们的猜想是对的
    ok,走到这里如果所有的if都不满足,那么就只有一种情况——有网络(networkRequest!=null)无缓存(cacheResponse == null),这时又咋办?直接把请求的结果返回出去呗。

    Response response = networkResponse.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
    

    networkResponse.newBuilder() 对吧,networkResponse是我们在后面的拦截器获取回来的数据。
    拆开来一讲是不是其实也不算太复杂?不过这里涉及了Http的缓存策略,如果需要深入了解还是得学习http的相关知识,这里我比较在乎的是整个大体流程,所以就不深挖了,有兴趣的筒子可以自行查阅哈。

    那接下来就是我们的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 = streamAllocation.newStream(client, doExtensiveHealthChecks);
        RealConnection connection = streamAllocation.connection();
    
        return realChain.proceed(request, streamAllocation, httpCodec, connection);
      }
    

    StreamAllocation 是不是很眼熟?
    是的,这就是我们第一个拦截器当时定义的一个对象,最后是在这里使用他创建了HttpCodec跟RealConnection 对象,然后传到下个拦截器去
    那既然如此我们来挖一下StreamAllocation.newStream()吧

    public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
        int connectTimeout = client.connectTimeoutMillis();
        int readTimeout = client.readTimeoutMillis();
        int writeTimeout = client.writeTimeoutMillis();
        boolean connectionRetryEnabled = client.retryOnConnectionFailure();
    
        try {
          RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
              writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
          HttpCodec resultCodec = resultConnection.newCodec(client, this);
    
          synchronized (connectionPool) {
            codec = resultCodec;
            return resultCodec;
          }
        } catch (IOException e) {
          throw new RouteException(e);
        }
      }
    

    这里一开始先配置了几个参数,超时时间,然后调用findHealthyConnection查找健康的连接,里面大概流程是返回一个连接,首先会从连接池中来,如果连接池中没有对应连接, 则再重新新建一个连接。
    最后初始化HttpCodec并返回出去,看看resultConnection.newCodec()

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

    这里咱们看到了http1跟2的判断了,组装成一个HttpCodec类返回了出去。
    ok,轮到挖findHealthyConnection()了,来吧

    private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
          int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
          throws IOException {
        while (true) {
          RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
              connectionRetryEnabled);
    <省略代码>
          return candidate;
        }
      }
    

    挖findConnection

    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);
          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();
        }
    
        // 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.
        RealConnection result;
        synchronized (connectionPool) {
          route = selectedRoute;
          refusedStreamCount = 0;
          result = new RealConnection(connectionPool, selectedRoute);
          acquire(result);
          if (canceled) throw new IOException("Canceled");
        }
    
        // 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;
      }
    

    这里主要是分为了几个步骤

    RealConnection allocatedConnection = this.connection;
    

    尝试使用已分配的连接

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

    尝试从连接池中获取连接

     if (selectedRoute == null) {
          selectedRoute = routeSelector.next();
        }
    

    这里有一个路由对象,根据官方注释可以理解为“如果我们需要一条路线,请制作一条。 这是一个阻止操作。”,可见这里是一个阻塞操作获取一条新路线(如果目前的选择为空的话)

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

    后来又开启了一个同步,又执行了上面这句代码,注意这下第四个参数是传的selectedRoute,经过上面这么一顿操作这个值应该是不为空了,这里通过这个路由线路,去池里找连接了。

    result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
        routeDatabase().connected(result.route());
    

    官方注释“做TCP + TLS握手。 这是一个阻止操作。”看来这里就是连接处了,

    Internal.instance.put(connectionPool, result);
    

    最后再汇集连接

    这个方法内容有点多,也属于比较底的一部分了,简单总结一下这里的工作任务:

    1,先尝试现在的连接是否可用
    2,再尝试一下在连接池获取一个连接(此时路由线路为空)
    3,获取路由线路
    4,通过已获得的路由线路,去连接池获取连接
    5,如果都找不到,那么就新建一个
    6,对连接进行连接操作(握手)
    7,把生成的连接对象,丢进池里

    这里的步骤就有这么多,可以看出代码对复用的要求极其洁癖,各种查询,最后真不行了才去生成,生成完又扔了进去提供给后面的筒子们复用。那么这个拦截器的内容就到这过,主要是建立连接,以及数据的准备,最后交给下一个拦截器CallServerInterceptor
    啊文章字又太多了,脸疼,下一篇。
    Android-Okhttp底层原理浅析(四)

    相关文章

      网友评论

          本文标题:Android-OKhttp底层原理浅析(三)

          本文链接:https://www.haomeiwen.com/subject/kopcoqtx.html