Okhttp3源码(2)---CacheInterceptor解析
来源:互联网 发布:讨鬼传极捏脸数据 编辑:程序博客网 时间:2024/06/14 10:18
前言
通过前一篇文章 Okhttp3源码(1)—同步异步请求流程解析 ,我们对Okhttp3同步异步请求的流程有了大概的了解,但是也留了个坑,那一大串interceptor没去仔细分析。
- client.interceptors()
- retryAndFollowUpInterceptor
- BridgeInterceptor
- CacheInterceptor
- ConnectInterceptor
- client.networkInterceptors()
- 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主要流程:
- 获取缓存
- 创建缓存策略
- 如果无网络,无缓存就直接报错:code=504 ,error报“Unsatisfiable Request (only-if-cached)”,碰到这种错很可能是服务端没有配合。
- 如果无网络,有缓存就直接return缓存。
- 上面的情况都没有发生,执行下一个interceptor
- 如果缓存不为空,code = 304, return缓存。
- 获取网络结果。
- 如果cache!=null,写入本地缓存,删除无效缓存
- Okhttp3源码(2)---CacheInterceptor解析
- OKhttp源码解析---拦截器之CacheInterceptor
- Okhttp3源码解析
- OkHttp3源码解析
- OkHttp3源码解析
- Okhttp3使用 + 源码完全解析
- OkHttp3源码解析01-请求
- OkHttp3源码解析03-缓存
- OkHttp3.9源码解析(一)
- Okhttp3源码(1)---同步异步请求流程解析
- 望穿秋水:基于实例纵深解析Okhttp3源码
- OkHttp3.0源码解析---拦截器
- OkHttp3源码解析02-拦截器
- OkHttp3源码解析04-失败重连
- OkHttp3源码解析05-连接池
- OkHttp3源码分析(一)
- OkHttp3源码分析(二)
- OkHttp3源码分析(三)
- 对实体类的CRUD操作
- [绍棠_Swift] Swift3.0中的Alamofire网络请求的封装
- 深度学习在语音识别中的声学模型以及语言模型的应用
- 程序中出现的.dll .lib .def 和 .exp文件
- 数组实现循环队列
- Okhttp3源码(2)---CacheInterceptor解析
- 模仿QQ背景为视频的登录页
- 开篇序(必看)
- Epii.js 一个极其简单的Js模板引擎
- 链表
- SpringBoot用JdbcTemplates访问Mysql
- Chrome 程序 Postman的使用
- 集成shiro后,登录页面样式不能正常显示问题
- Could not commit with auto-commit set on