美文网首页andnroid
okHttp拦截器分析(二)

okHttp拦截器分析(二)

作者: 放码过来吧 | 来源:发表于2018-09-08 09:19 被阅读30次
    image.png

    继续上一篇的okHttp拦截器分析(一),下一个要分析的拦截器是CacheInterceptor,听名字就知道跟缓存有关,在这之前,我们先来看看一张Http缓存的流程图,网络上找到的:


    image.png

    当发送相同请求的时候,先判断缓存是否过期,如果过期了,该请求会携带If-Modified-Since和If-None-Match,通过这两个值,判断本地资源是否发生变化,没有变化直接获取缓存并返回code 304。
    注:
    Last-Modified:服务器返回给客户端的头部信息,表示资源的最后修改时间(ETag 比较的是响应内容的特征值,而Last-Modified 比较的是响应内容的修改时间)。

    很好,接下来我们来看看okHttp里面的缓存拦截器关键实现:

    /** Serves requests from the cache and writes responses to the cache. */
    public final class CacheInterceptor implements Interceptor {
    @Override public Response intercept(Chain chain) throws IOException {
        //获取到缓存
        Response cacheCandidate = cache != null
            ? cache.get(chain.request())
            : null;
    
        long now = System.currentTimeMillis();
        
        //这个类有点叼,request和缓存的response都是CacheStrategy类返回的,也许这个CacheStrategy就是管理者吧,决定到底使用缓存还是进行网络请求
        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.
        //  1- 如果无网络访问(请求体networkRequest为null,内部的url也是null),又无缓存,返回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.
        //   2 - 如果不需要网络请求,直接返回缓存数据
        if (networkRequest == null) {
          return cacheResponse.newBuilder()
              .cacheResponse(stripBody(cacheResponse))
              .build();
        }
    
        Response networkResponse = null;
        try {
         // 3 - 进行网络请求,得到 网络返回数据
          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.
        //4 - HTTP_NOT_MODIFIED 标识缓存有效,网络请求返回数据和缓存数据合并,并更新缓存
        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 (cache != null) { //判断是否支持缓存
          if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
            // Offer this request to the cache.
            CacheRequest cacheRequest = cache.put(response);
            //5- 判断有无缓存,写入缓存
            return cacheWritingResponse(cacheRequest, response);
          }
    
          if (HttpMethod.invalidatesCache(networkRequest.method())) {
            try {
              cache.remove(networkRequest);
            } catch (IOException ignored) {
              // The cache cannot be written.
            }
          }
        }
    
        return response;
      }
    
    }
    

    上面关键代码,我们来总结下执行流程:
    1 如果无网络访问,又无缓存,返回504错误,执行2
    2 如果不需要网络请求,直接返回缓存数据,否则 执行3
    3 进行网络请求,得到网络返回数据,执行4
    4 HTTP_NOT_MODIFIED 标识缓存有效,网络请求返回数据和缓存数据合并,并更新缓存,否则 执行5
    5 判断有无缓存,写入缓存并返回response


    image.png

    很好,CacheInterceptor分析完了,喝杯茶压压惊。

    接下来 我们来看看ConnectInterceptor,字如其名,是一个跟连接有关的拦截器,我们看看关键代码:

    /** Opens a connection to the target server and proceeds to the next interceptor. */
    public final class ConnectInterceptor implements Interceptor {
       @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, chain, doExtensiveHealthChecks);
        RealConnection connection = streamAllocation.connection();
    
        return realChain.proceed(request, streamAllocation, httpCodec, connection);
      }
    }
    
    

    代码看的去很少,我们只需要关注下面两行:

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

    不管是httpCodec,还是connection,都是由streamAllocation完成,我们来看看newStream()里面做了什么:

    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 {
          RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
              writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
          HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
    
          synchronized (connectionPool) {
            codec = resultCodec;
            return resultCodec;
          }
        } catch (IOException e) {
          throw new RouteException(e);
        }
      }
    
    ......
     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);
        }
      }
    

    findHealthyConnection ,是找到一个可用连接的意思,重点就在这,通过找到的可用连接newCodec(),返回了HttpCodec的实现类Http1Codec对象。我们去看看findHealthyConnection(),看看如何找到可用连接:

    private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
          int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
          boolean doExtensiveHealthChecks) throws IOException {
        while (true) {
          RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
              pingIntervalMillis, 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;
        }
      }
    

    哇,这个方法用了死循环,如果找不到可用连接,就一直卡在这里,再来看看findConnection()关键方法,有点长,分析都在源码里面的注释上:

     /**
       * Returns a connection to host a new stream. This prefers the existing connection if it exists,
       * then the pool, finally building a new connection.
       */
      private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
          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) { //
          //经过releaseIfNoNewStreams,connection不为null,则连接是可用的
            // 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;
          }
          
          //无可用连接,去连接池connectionPool中获取
          if (result == null) {
            // Attempt to get a connection from the pool.
            Internal.instance.get(connectionPool, address, this, null);
            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) {//上面通过去连接池中找,如果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.
          //提供address,再次从连接池中获取连接
            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) {
            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.
        //如果连接是从连接池中找到的,直接拿出来使用
        if (foundPooledConnection) {
          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);
    
          // If another multiplexed connection to the same address was created concurrently, then
          // release this connection and acquire that one.
          //如果是一个http2连接,http2连接应具有多路复用特性,
          if (result.isMultiplexed()) {
            socket = Internal.instance.deduplicate(connectionPool, address, this);
            result = connection;
          }
        }
        closeQuietly(socket);
    
        eventListener.connectionAcquired(call, result);
        return result;
      }
    
    

    我们来回顾下上面的获取可用连接的流程:
    先检测链接是否可用:
    a 可用的话直接返回,结束流程。
    b 不可用,先去 连接池中查找:
    连接池找到:结束流程。
    未找到:提供address,再次去连接池中查找。如果找到了直接结束流程,如果没找到:生成一个新的连接,并且将这个新的链接加入到连接池,然后返回这个新链接。

    ConnectInterceptor 分析结束。

    接下来是CallServerInterceptor,这个拦截器用来完成最终的请求执行,这里面涉及到OKio这个库,假装它就是一个httpUrlConnection就行了,由于是另外一个库,这里就不分析了。

    相关文章

      网友评论

        本文标题:okHttp拦截器分析(二)

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