OkHttp之getResponseWithInterceptorChain(二)

接上篇OkHttp之getResponseWithInterceptorChain(一)继续

目录

CacheInterceptor

构造方法 

Cache 

CacheStrategy

CacheInterceptor的具体逻辑

总结

ConnectInterceptor

StreamAllocation

总结

CallServerInterceptor

Okio在CacheInterceptor应用

总结

 


CacheInterceptor

缓存拦截器。其构造函数传入一个InternalCache,在getResponseWithInterceptorChain()实例化时从OkHttpClient中传入。

构造方法 

  public CacheInterceptor(InternalCache cache) {
    this.cache = cache;
  }

在OkHttpClient的Builder提供了一个设置缓存的方法,默认的为null

    @Nullable Cache cache;
    @Nullable InternalCache internalCache;
    
  /** Sets the response cache to be used to read and write cached responses. */
    void setInternalCache(@Nullable InternalCache internalCache) {
      this.internalCache = internalCache;
      this.cache = null;
    }

    /** Sets the response cache to be used to read and write cached responses. */
    public Builder cache(@Nullable Cache cache) {
      this.cache = cache;
      this.internalCache = null;
      return this;
    }

 从源码中看到可以通过Builder设置Cache,但不能设置internalCache。下面是获取这个缓存的代码

  public @Nullable Cache cache() {
    return cache;
  }

  InternalCache internalCache() {
    return cache != null ? cache.internalCache : internalCache;
  }

Cache 

我们看下这个Cache类,提供了对缓存内容的创建和增删改查方法。

public final class Cache implements Closeable, Flushable {
//内部含有一个internalCache
  final InternalCache internalCache = new InternalCache() {
  }
//使用DiskLruCache来实现缓存
  final DiskLruCache cache;

  public Cache(File directory, long maxSize) {
    this(directory, maxSize, FileSystem.SYSTEM);
  }

  Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
  }
//从中可以看到key就是对应的url地址进行了md5加密
  public static String key(HttpUrl url) {
    return ByteString.encodeUtf8(url.toString()).md5().hex();
  }

  @Nullable Response get(Request request) {
    String key = key(request.url());
//.....省略代码
  }
  @Nullable CacheRequest put(Response response) {
    String requestMethod = response.request().method();
//.....省略代码
 
    Entry entry = new Entry(response);
    DiskLruCache.Editor editor = null;
    try {
      editor = cache.edit(key(response.request().url()));
      if (editor == null) {
        return null;
      }
      entry.writeTo(editor);
//.....省略代码
  }

 void remove(Request request) throws IOException {
    cache.remove(key(request.url()));
  }

  void update(Response cached, Response network) {
  
  }
}

Cache里面有一个 internalCache,同时采用了DiskLruCache进行缓存,缓存的内容就是response,通过url地址进行了md5加密作为key来进行增删改查。

同时注意下,在构建Cache的时候,必须要传入要保存的文件路径和最大缓存的大小。

CacheStrategy

就是给定一个request和cached response,根据里面设置的条件决定去使用网络请求还是缓存的response。从创建的Factory中可以看到返回的CacheStrategy就是根据不同的条件来将CacheStrategy中的networkRequest和cacheResponse进行设置值。

CacheInterceptor的具体逻辑

进入到CacheInterceptor中看下这个拦截器是怎么实现的缓存?


 @Override public Response intercept(Chain chain) throws IOException {
//1)判断有没有设置的缓存里的response
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;
//2)将request和缓存的response传入到缓存策略中,获取缓存策略中的networkRequest和cacheResponse
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
//.....省略代码
//2.1)既不需要请求服务器同时缓存无效,直接返回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();
    }

//2.2)不需要网络服务器,缓存有效,则直接返回缓存的response
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
//2.3)缓存无效,直接请求服务器,进入到下一个拦截器返回对应的response
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
//.......省略代码
    }
//2.4)如果缓存response有效,则对比服务器获取的response的情况进行选择使用哪个响应
    if (cacheResponse != null) {
//如果缓存有效,并且返回的response中不需要请求网络,则直接将缓存response进行返回,状态码为304
      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();
//.......省略代码
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
//2.5)使用网络请求
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
//3)根据情况进行缓存response
    if (cache != null) {
//如果有缓存,并且该response允许缓存,则将该response放到缓存中
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

1)先从设置的缓存中获取response,由于在OkHttpClient中默认的是null,如果没有设置,这里返回的是null

2)将request和缓存的response传入到缓存策略中,获取缓存策略中的networkRequest和cacheResponse,然后根据返回的值来返回不同的response,具体有以下几种情况:

(2.1)networkRequest和cacheResponse都为null:即不需要请求服务器,同时缓存无效,则直接返回,状态码为504

(2.2)networkRequest为null:不需要请求服务器,直接返回缓存response

(2.3)networkRequest不为null:则直接调用下一个拦截器去请求服务器

(2.4)cacheResponse不为null:如果缓存有效,则与服务器返回的response进行比较,如果返回的response含有标示为 Not Modified,则直接返回缓存response

3)如存在缓存,则将从服务器返回的response缓存到本地。

总结

1)缓存策略是OkHttp的特色之一。

2)Cache类用来实现对缓存内容的增删改查,request中的对应的url进行MD5加密之后作为缓存的key,同时采用DiskLruCache进行缓存response

3)CacheStrategy缓存策略就是根据request和之前缓存的response里面的header里面的配置的内容来决定里面的networkRequest和cacheResponse是否为null,来决定是去请求服务器还是直接使用缓存response

4)CacheInterceptor拦截器中根据缓存策略中的networkRequest和cacheResponse的值来决定返回服务器返回的response还是直接使用缓存response。若需要请求服务器,则通过RealInterceptorChain的 realChain.proceed(),调用到下一个拦截器ConnectInterceptor

ConnectInterceptor

与服务器建立连接

 @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
//获取request和StreamAllocation
    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");
//获取StreamAllocation的流
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//获取的streamAllocation连接
    RealConnection connection = streamAllocation.connection();

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

从源码中看上去很简单就是获取requst和streamAllocation。然后通过streamAllocation的方法来建立连接

StreamAllocation

简单的来说就是为请求寻找可用的连接,并建立连接的过程。

1)实例化

在RetryAndFollowUpInterceptor中进行实例化,在完成RetryAndFollowUpInterceptor拦截器的失败重试或重定向,调用RealInterceptorChain的 realChain.proceed()时,将该实例对象传入到了RealInterceptorChain中

2)获取StreamAllocation的流

通过调用StreamAllocation的newStream()返回流,进入到该方法中

  public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
//.....代码省略
    try {
//1)首先找到一个可用的连接RealConnection
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//2)通过连接获得HttpCodec
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
//.....代码省略
  }
  • 首先通过findHealthyConnection()找到一个可用的连接。最终就是调用的findConnection(),其中里面的大体流程就是

(1)首先判断复用的connection是否有效

  result = this.connection;

该connection是通过在RealConnection中get()一个可用链接的时候,如果该链接可用,则直接通过streamAlloction.acquire()给this.connection赋值。

2)如果上面this.connection无效,则从连接池中取出一个链接

 if (result == null) {
    // Attempt to get a connection from the pool.
     Internal.instance.get(connectionPool, address, this, null);
//.......省略代码
}

该connectionPool是在实例化StreamAllocation时传入的。在RetryAndFollowUpInterceptor实例化的时候,从OkHttpClient中获取的

    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);

而OkHttpClient中的Builder时,初始化的

 public Builder() { 
//......代码省略
    connectionPool = new ConnectionPool();
//......代码省略
}

所以OkHttpClient最好在全局只有一个实例,这样可以共用该链接池。

如果此时的connection的可用,则直接返回

3)如果上述的connection不可用,则更换路由,更换线路,进行查找,直到找到一个返回

4)如果还是没有找到,则创建一个新的链接,并将该链接通过acquire关联到connection.alloctions上

result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);

5)建立TCP链接并握手

result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);

 6)将链接放到链接池中

synchronized (connectionPool) {
 //......省略代码
   Internal.instance.put(connectionPool, result); 
//......省略代码
}
  • 得到可用的链接之后,通过该链接获取到流
 HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

进入到源码中可以看到: 

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

里面的逻辑很简单,就是根据情况返回是Http1Codec还是Http2Codec。这个HttpCodec就是为发送请求的时候写请求头部,创建请求体,在接收的时候读取响应头,以便后续获取响应数据。

总结

ConnectInterceptor就是通过StreamAllocation来为请求与服务器建立链接。而真正的去请求服务器就在下一个拦截器中。并且还是通过调用realChain.proceed的方式进行调用下一个拦截器,并且将(request, streamAllocation, httpCodec, connection)这些信息传到下一个拦截器中

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

CallServerInterceptor

真正的向服务器发送请求,并且得到服务器返回的数据,返回给上一个拦截器。看下源码

 @Override public Response intercept(Chain chain) throws IOException {
//.......省略代码
//1.写入请求头
    httpCodec.writeRequestHeaders(request);
//.......省略代码
    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // 如果发送到请求中含有100-continue(客户端在发送request之前,需要先判断服务器是否愿意接受客户端发来的消息主体),则只有服务器返回100-continue的应答之后,才会把数据发送给服务器
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
//开始请求服务器,检查返回的response中是否含有100-continue
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }
//如果前面的100-continue需要握手,但又握手失败,这时候responseBuilder不为null 
//2.写入请求体
      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
//3.写数据
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      }
//.......省略代码
//完成网络请求,实际上就是将信息写入到socket的输出流中
    httpCodec.finishRequest();
//4.读取响应头
    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

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

    int code = response.code();
//....省略代码     读取响应里面的内容进行处理
//5.读取服务器返回的内容
   else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

//....省略代码    
    return response;
  }

通过上面5步,完成整个发送和接收服务器的数据。

Okio在CacheInterceptor应用

具体的一个解析过程,可以参见OkHttp的Okio在CacheInterceptor中的应用

总结

CacheInterceptor主要就是真正的将请求发送给服务器,并接收服务器返回的数据。整个过程通过Okio来进行数据的处理。Okio里面增加了缓存机制和超时机制来进行读写数据。

 

猜你喜欢

转载自blog.csdn.net/nihaomabmt/article/details/88416148
今日推荐