Okhttp3源码(2)---CacheInterceptor解析

来源:互联网 发布:讨鬼传极捏脸数据 编辑:程序博客网 时间:2024/06/14 10:18

前言

通过前一篇文章 Okhttp3源码(1)—同步异步请求流程解析 ,我们对Okhttp3同步异步请求的流程有了大概的了解,但是也留了个坑,那一大串interceptor没去仔细分析。

  1. client.interceptors()
  2. retryAndFollowUpInterceptor
  3. BridgeInterceptor
  4. CacheInterceptor
  5. ConnectInterceptor
  6. client.networkInterceptors()
  7. CallServerInterceptor

这里面除了两个自定义的interceptor以外,在我看来开发最需要去了解的就是CacheInterceptor。
OK,在看CacheInterceptor之前,我们需要先了解下Http缓存机制,这里推荐 彻底弄懂HTTP缓存机制及原理。

源码

通过之前的文章,我们已经知道interceptor里最重要的就是intercept方法了

@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 (cache != null) {      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;  }

源码很长,一步一步来看

 final InternalCache cache;

cache是CacheInterceptor的一个变量

 Response cacheCandidate = cache != null        ? cache.get(chain.request())        : null;

如果cache不为空,取出来,否则null。

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

构建请求策略,进入这个方法。

public Factory(long nowMillis, Request request, Response cacheResponse) {    this.nowMillis = nowMillis;    this.request = request;    this.cacheResponse = cacheResponse;    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);            }        }    }}

“Date”,“Expires”,“Last-Modified”,“ETag”,“Age”这几个单词有没有很熟?之前推荐的文章里有讲过。全都都是header里的标志,这里就是用来获取header里标志的内容。

 /** The request to send on the network, or null if this call doesn't use the network. */  public final @Nullable Request networkRequest;  /** The cached response to return or validate; or null if this call doesn't use a cache. */  public final @Nullable Response cacheResponse;

根据CacheStrategy里的注释,可以知道

  • networkRequest:无网络为null,否则为将要发送的请求在的网络部分。
  • cacheResponse:无缓存为null,否则为缓存的响应用来返回或验证。

回到intercept()的代码

 Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse;

取出这两个值

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

无网络无cache直接报错,“Unsatisfiable Request (only-if-cached)”错误代码504。如果你写自定义interceptor的时候遇到这个error,不一定是你代码的问题,很可能是服务端没有配合。

// If we don't need the network, we're done. if (networkRequest == null) {      return cacheResponse.newBuilder()          .cacheResponse(stripBody(cacheResponse))          .build();    }

无网络,但是有cache直接返回。

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

之前已经分析过了,执行下一个interceptor。

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

如果已经接收到网络结果,code304,返回缓存结果。

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

response 为网络返回结果。

if (cache != null) {      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.        }      }    }

缓存response,删除本地的无用缓存。

CacheRequest cacheRequest = cache.put(response);return cacheWritingResponse(cacheRequest, response);

如果你往里走会发现两个新朋友:

  • DiskLruCache:相信做过缓存功能的同学都认识这个类,用来做硬盘缓存的类,后面会专门写篇文章讲讲DiskLruCache和LruCache,这里只要知道它是用来缓存就可以了。
  • okio:同样是square公司开发的,它补充了java.io和java.nio的不足,作用就是io操作。

这部分主要就是通过DiskLruCache和okio的组合来写入本地缓存。

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

如果是无效缓存,从cache中删除。

总结

cacheinterceptor主要流程:

  1. 获取缓存
  2. 创建缓存策略
  3. 如果无网络,无缓存就直接报错:code=504 ,error报“Unsatisfiable Request (only-if-cached)”,碰到这种错很可能是服务端没有配合。
  4. 如果无网络,有缓存就直接return缓存。
  5. 上面的情况都没有发生,执行下一个interceptor
  6. 如果缓存不为空,code = 304, return缓存。
  7. 获取网络结果。
  8. 如果cache!=null,写入本地缓存,删除无效缓存
原创粉丝点击