OkHttp源码分析(二)——拦截器链

本片文章主要分析的是OkHttp获取响应的过程,以及拦截器链。

getResponseWithInterceptorChain方法

在上篇分析同步和异步请求流程的时候都出现了getResponseWithInterceptorChain方法,现在从这里开始分析。

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //添加应用拦截器
    interceptors.addAll(client.interceptors());
    //添加重试和重定向拦截器
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //添加转换拦截器
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //添加缓存拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //添加连接拦截器
    interceptors.add(new ConnectInterceptor(client));
    //添加网络拦截器
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));
    
    //生成拦截器链
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    
    boolean calledNoMoreExchanges = false;
    try {
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
}

这段代码主要是把

  • 应用拦截器(外部配置)client.interceptors()
  • 重试跟进拦截器RetryAndFollowUpInterceptor
  • 桥拦截器BridgetInterceptor
  • 缓存拦截器CacheInterceptor
  • 连接拦截器ConnectInterceptor
  • 网络拦截器(外部配置)client.neworkInterceptors()
  • 请求服务拦截器CallServerInterceptor

将这些拦截器一次添加到集合interceptors中,然后使用interceptors、transmitter、originalRequest等创建了拦截器链RealInterceptorChain实例,最后用proceed方法获取到请求的结果Response。

RealInterceptorChain

public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
  throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    
    calls++;
    
    if (this.exchange != null && !this.exchange.connection().supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }
    
    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.exchange != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }
    
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    
    // Confirm that the next interceptor made its required call to chain.proceed().
    if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }
    
    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }
    
    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

return response;
}

在实例化RealInterceptorChain时 index赋值是0,exchange是null,所以前面三个if都没走进去。然后获取了第一个拦截器,也就是我们配置的应用拦截器,调用了它的interceptor方法,并返回和校验了结果。这里证实了我们猜想。同时注意到,调用 应用拦截器的interceptor方法传入的参数:拦截器链实例next,next就是把index + 1而已,其他参数和当前实例是一样的。也就是说 在我们的应用拦截器中调用的是 next的proceed方法。

进一步,next的proceed方法中 同样会获取interceptors的index=1的拦截器,即RetryAndFollowUpInterceptor实例,然后调用其interceptor方法,参数是index+1即index=2的chain。跟进RetryAndFollowUpInterceptor的代码发现,interceptor方法内部也是有调用chain的proceed方法。这样就会依次传递下去,直到最后一个拦截器CallServerInterceptor。

实际上 除了最后一个拦截器CallServerInterceptor之外,所有拦截器的interceptor方法都调用了 传入 chain的proceed方法。每个拦截器在chain的proceed方法 前后 处理了自己负责的工作。例如我们的应用拦截器,在chain的proceed方法前 打印了request信息的日志,chain的proceed方法获取结果 之后 打印了response信息的日志。每个拦截器interceptor方法在 调用chain的proceed方法时 都是为了获取下一个拦截器处理的response,然后返回给上一个拦截器。

下面我们依次分析这些拦截器。

RetryAndFollowUpInterceptor-重试、重定向

如果请求创建时没有添加应用拦截器 ,那第一个拦截器就是RetryAndFollowInterceptor,意为重试和跟进拦截器。作用是连接失败后进行重试,对请求结果跟进后进行重定向。下面看下它的interceptor方法:

public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();
    
    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      //准备连接
      transmitter.prepareToConnect(request);
    
      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }
    
      Response response;
      boolean success = false;
      try {
        //继续执行下一个Interceptor
        response = realChain.proceed(request, transmitter, null);
        success = true;
      } catch (RouteException e) {
        // 连接路由异常,此时请求还未发送,尝试恢复
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
          throw e.getFirstConnectException();
        }
        continue;
      } catch (IOException e) {
        // IO异常 请求可能已发出,尝试恢复
        if (!recover(e, transmitter, requestSendStarted, request)) throw e;
        continue;
      } finally {
        // 请求没成功,释放资源
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }
    
      // 关联上一个response
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }
    
      Exchange exchange = Internal.instance.exchange(response);
      Route route = exchange != null ? exchange.connection().route() : null;
      //跟进结果,主要作用是根据响应码处理请求,返回request不为空时则进行重定向处理,拿到重定向的request
      Request followUp = followUpRequest(response, route);
    
      if (followUp == null) {
        if (exchange != null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
        return response;
      }
    
      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        return response;
      }
    
      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }
      //最多重试20次
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
    
      request = followUp;
      priorResponse = response;
    }
}

使用while循环

prepareToConnect

public void prepareToConnect(Request request) {
    if (this.request != null) {
      if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
        return; // 有相同连接
      }
      ...
    }
    
    this.request = request;
    //创建ExchangeFinder,是为获取连接做准备
    this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
        call, eventListener);
}

ExchangeFinder是交换查找器,作用是获取请求的连接。

接着调用realChain.proceed继续传递请求给下一个拦截器,从下个拦截器获取原始结果。如果此过程发生了连接路由异常或IO异常,就会调用recover判断是否进行重试恢复。

recover

private boolean recover(IOException e, Transmitter transmitter,
      boolean requestSendStarted, Request userRequest) {
    // 应用层禁止重试,就不重试
    if (!client.retryOnConnectionFailure()) return false;
    
    // 不能再次发送请求,就不重试
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;
    
    // 发生的异常是致命的,就不重试
    if (!isRecoverable(e, requestSendStarted)) return false;
    
    // 没有路由可以尝试,就不重试
    if (!transmitter.canRetry()) return false;
    
    //返回true,就会进入下一次循环,重新请求
    return true;
}

如果realChain.proceed没有发生异常,返回了结果response,就会使用followUpRequest方法跟进结果并重定向request。如果不用跟进处理(例如响应码是200),则返回null。

BridgeInterceptor-桥接拦截器

桥拦截器相当于在请求发起端和网络执行端架起一座桥,其作用:

  • 把应用层发出的请求变为网络层认识的请求;
  • 把网络层执行后的响应变为应用层便于应用层使用的结果。
public final class BridgeInterceptor implements
Interceptor {
  //cookie管理器,初始化OkHttpClient时创建的,默认是C
  private final CookieJar cookieJar;

  public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }
  ...//这部分代码比较长我们在下面分步展开
}

添加头部信息

Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }
    
      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
}

这段代码主要为request添加Content-Type(文档类型)、Content-Length(内容长度)或Transfer-Encoding。这些信息不需要我们手动添加。

if (userRequest.header("Host") == null) {
    requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}

if (userRequest.header("Connection") == null) {
  requestBuilder.header("Connection", "Keep-Alive");
}

//默认支持gzip压缩
//"Accept-Encoding: gzip",表示接受:返回gzip编码压缩的数据
// 如果我们手动添加了 "Accept-Encoding: gzip" ,那么下面的if不会进入,transparentGzip是false,就需要我们自己处理数据解压。
//如果 没有 手动添加"Accept-Encoding: gzip" ,transparentGzip是true,同时会自动添加,而且后面也会自动处理解压。
boolean transparentGzip = false;
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
  transparentGzip = true;
  requestBuilder.header("Accept-Encoding", "gzip");
}

if (userRequest.header("User-Agent") == null) {
    requestBuilder.header("User-Agent", Version.userAgent());
}

这段代码主要为Host、Connection和User-Agent字段添加默认值。这些属性只有用户没有设置时,才会自动添加。

cookie部分

//从cookieJar中获取cookie,添加到Header
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
  requestBuilder.header("Cookie", cookieHeader(cookies));
}

private String cookieHeader(List<Cookie> cookies) {
StringBuilder cookieHeader = new StringBuilder();
    for (int i = 0, size = cookies.size(); i < size; i++) {
      if (i > 0) {
        cookieHeader.append("; ");
      }
      Cookie cookie = cookies.get(i);
      cookieHeader.append(cookie.name()).append('=').append(cookie.value());
    }
    return cookieHeader.toString();
}

处理请求

Response networkResponse = chain.proceed(requestBuilder.build());
//从networkResponse中获取header Set-Cookie存入cookieJar
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

Response.Builder responseBuilder = networkResponse.newBuilder()
    .request(userRequest);

//如果我们手动添加“Accept-Encoding:gzip”,这里会创建  能自动解压的responseBody---GzipSource
if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

梳理下整个流程:

  • chain.proceed()执行前,对请求添加Header:Content-Type、Content-Length或Transfer-Encoding、Host、Connection、Accept-Encoding、Cookie、User-Agent,即网络层真正可执行的请求。默认是没有cookie处理的,需要我们在初始化OkHttpClient时配置我们自己的cookieJar
  • chain.proceed()执行后,先把响应header中的cookie存入cookieJar,如果没有手动添加请求heade:"Accept-Encoding:gzip",会通过创建能自动解压的responseBody——GzipSource,接着构建新的response返回。

CacheInterceptor-缓存拦截器

缓存拦截器,提供网络请求缓存的存取。

发送一个网络请求,如果每次都经过网络的发送和读取,效率肯定是很低的。若之前有相同的请求已经执行过一次,是否可以将其结果保存起来,这次请求直接使用。这就用到了CacheInterceptor,合理使用本地缓存,有效的减少网络开销,减少响应延迟。

  final @Nullable InternalCache cache;

  public CacheInterceptor(@Nullable InternalCache cache) {
    this.cache = cache;
  }
  
  @Override 
  public Response intercept(Chain chain) throws IOException {
    // 先从缓存中获取响应,没有则返回null。
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    
    //获取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.
    }

    // 网络请求 缓存都不能用,返回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 (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    
    // 进行网络请求
    Response networkResponse = null;
    try {
      //调用下一个拦截器
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // 发生IO错误
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    //网路请求返回304,表示服务端资源没有修改,就结合网络响应和网络缓存,更新缓存,返回结果,结束。
    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();

        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        //如果是非304,说明服务端资源有更新,就关闭缓存body
        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)) {
        // 写入缓存
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
      
      // OkHttp默认只会对get请求进行缓存 因为get请求的数据一般是比较持久的 而post一般是交互操作
      //不是get请求就移除缓存
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

现在来整体梳理下思路。

CacheStrategy缓存策略用来决定是否使用缓存及如何使用。根据缓存策略中的networkRequest和cacheResponse来进行一系列是使用缓存还是新的网络数据的判断:

  1. 若networkRequest、cacheResponse都为null,即网络请求、缓存都不能用,返回504;
  2. 若networkRequest为null,cacheResponse肯定不为null,就是不使用网络,使用缓存,就结束返回缓存数据;
  3. 若networkResponse不为null,不管cacheResponse是否为null,都会去请求网络,获取网络响应networkResponse;
  4. 若cacheResponse不为null,且networkResponse.code是304,表示服务端资源未修改,缓存还是有效的。结合网络响应和缓存响应,然后更新缓存;
  5. 若cacheResponse==null或cacheResponse不为null,但networkResponse.code不是304,就写入缓存,返回响应。

CacheStrategy

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

这里将请求request、候选缓存cacheCandidate传入工厂类Factory,然后调用get方法。

public Factory(long nowMillis, Request request, Response cacheResponse) {
  this.nowMillis = nowMillis;
  this.request = request;
  this.cacheResponse = cacheResponse;
  
  //获取候选缓存的请求时间、响应时间,从header中获取过期时间、修改时间、资源标记等。
  if (cacheResponse != null) {
    this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
    this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
    Headers headers = cacheResponse.headers();
    for (int i = 0, size = headers.size(); i < size; i++) {
      String fieldName = headers.name(i);
      String value = headers.value(i);
      if ("Date".equalsIgnoreCase(fieldName)) {
        servedDate = HttpDate.parse(value);
        servedDateString = value;
      } else if ("Expires".equalsIgnoreCase(fieldName)) {
        expires = HttpDate.parse(value);
      } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
        lastModified = HttpDate.parse(value);
        lastModifiedString = value;
      } else if ("ETag".equalsIgnoreCase(fieldName)) {
        etag = value;
      } else if ("Age".equalsIgnoreCase(fieldName)) {
        ageSeconds = HttpHeaders.parseSeconds(value, -1);
      }
    }
  }
}

//get方法内部先调用了getCandidate()获取到缓存策略实例
public CacheStrategy get() {
  CacheStrategy candidate = getCandidate();

  if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
    return new CacheStrategy(null, null);
  }

  return candidate;
}

getCandidate()

这个方法里涉及到很多http缓存字段方面的东西

    private CacheStrategy getCandidate() {
      // 没有缓存:网络请求
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      // https,但没有握手:网络请求
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }

      //网络响应 不可缓存(请求或响应的 头 Cache-Control 是'no-store'):网络请求
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }
      //请求头的Cache-Control是no-cache 或者 请求头有"If-Modified-Since"或"If-None-Match":网络请求
      //意思就是 不使用缓存 或者 请求 手动 添加了头部 "If-Modified-Since"或"If-None-Match"
      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }

      CacheControl responseCaching = cacheResponse.cacheControl();

       //缓存的年龄
      long ageMillis = cacheResponseAge();
      //缓存的有效期
      long freshMillis = computeFreshnessLifetime();
      //比较请求头里有效期,取较小值
      if (requestCaching.maxAgeSeconds() != -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }

      //可接受的最小 剩余有效时间(min-fresh标示了客户端不愿意接受 剩余有效期<=min-fresh 的缓存。)
      long minFreshMillis = 0;
      if (requestCaching.minFreshSeconds() != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }
      //可接受的最大过期时间(max-stale指令标示了客户端愿意接收一个已经过期了的缓存,例如 过期了 1小时 还可以用)
      long maxStaleMillis = 0;
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
            // 第一个判断:是否要求必须去服务器验证资源状态
            // 第二个判断:获取max-stale值,如果不等于-1,说明缓存过期后还能使用指定的时长
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }
      
      //如果响应头没有要求忽略本地缓存 且 整合后的缓存年龄 小于 整合后的过期时间,那么缓存就可以用
      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        //没有满足“可接受的最小 剩余有效时间”,加个110警告
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        //isFreshnessLifetimeHeuristic表示没有过期时间,那么大于一天,就加个113警告
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        
        return new CacheStrategy(null, builder.build());
      }

      //到这里,说明缓存是过期的
      // 然后 找缓存里的Etag、lastModified、servedDate
      String conditionName;
      String conditionValue;
      if (etag != null) {
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
      } else {
        //都没有,就执行常规的网络请求
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

      //如果有,就添加到网络请求的头部。
      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
          
      //conditionalRequest表示 条件网络请求: 有缓存但过期了,去请求网络 询问服务端,还能不能用。能用侧返回304,不能则正常执行网路请求。
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }

下面总结下getCandidate()方法的流程:

  1. 没有缓存、是https请求但是没有握手、网络响应不可缓存、忽略缓存或手动配置缓存过期,都直接进行网络请求;
  2. 以上条件都不满足,如果缓存没过期那么就使用缓存;
  3. 如果缓存过期了,但响应头有Etag、Last-Modified、Data,就添加这些header进行条件网络请求;
  4. 如果缓存过期了,且响应头没有设置Etag、Last-Modified、Data,就进行网络请求。

再继续看get()方法:

public CacheStrategy get() {
      CacheStrategy candidate = getCandidate();
      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        return new CacheStrategy(null, null);
      }
      return candidate;
    }

getCandidate()获取的缓存策略对象后,判断:进行了网络请求且原请求配置是能使用缓存。这说明此时即使有缓存也是过期的缓存,所以new一个实例,传入null。

缓存的读写是通过InternalCache完成的,InternalCache是在创建CacheInterceptor实例时,用client.internalCache()作为参数传入。而InternalCahce是OkHttp内部使用,InternalCache的实例是类Cache的属性。Cache是我们初始化OkHttpClient时传入的,所以如果没有传入Cache实例是没有缓存功能的。

OkHttpClient client = new OkHttpClient.Builder()
        .cache(new Cache(getExternalCacheDir(),500 * 1024 * 1024))
        .build();

Cache是通过OkHttp内部的DiskLruCache实现的。

ConnectInterceptor拦截器和CallServerInterceptor拦截器会下下一篇文章中分析。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335