Okhttp之CacheInterceptor简单分析
《OkHttp之BridgeInterceptor简单分析 》简单分析了BridgeInterceptor的工作原理,在Okhttp的拦截器链上BridgeInterceptor的下一个拦截器就是CacheInterceptor,所以本文就对此拦截器的功能做一个简单的梳理。顾名思义,该拦截器的工作跟缓存有关。
正如浏览器在访问网络请求的时候提供缓存功能一样,Okhttp也提供了缓存功能,使用Okhttp的缓存其实跟浏览器的缓存有相似之处;不像使用图片缓存框架那样,如果有缓存图片框架会直接使用缓存中的图片(比如ImageLoader),而再继续网络请求;而浏览器不一样,即使有缓存也会再次发起网络请求,下面就简单说下网络请求缓存的一些基本知识,算是为本文对CacheInterceptor的分析做铺垫。
在服务器响应的时候,在”响应头信息”里面包含了资源的最后修改时间,我们可以通过Last-Modified这个header来获取该时间:
既然我们能拿到资源文件的最后修改时间,那么我们就可以根据这个时间来判断服务器上面的资源是否有修改了!怎么做呢?当我们再次请求服务器的时候,在”请求头信息”会带上次请求获取的Last-Modified时间,该时间在请求头信息”用If-Modified-Since 这个header里面,见下图:
当服务器收到请求后检测到If-Modified-Since这个Header,则与被请求资源的最后修改时间进行比对:
if(服务器资源最后修改时间<=If-Modified-Since) {
说明对应的资源没有修改过,此时服务器返回304状态码,不再需要将报文主体部分返回给客户端;
客户端此时直接用缓存的资源即可
}else {
说明资源又被改动过,服务器返回状态码200,此时就需要将报文的主体部分返给客户端,;
}
其实,通过上面的图我们发现在请求/响应的头信息里面还有两个Header:If-None-Match/Etag。当第一次请求的时候,”响应头信息”里面有一个Etag的Header可以看做是资源的标识符。再次请求的时候,”请求头信息”会包含一个If-None-Match的头信息。此时服务器取得If-None-Match后会和资源的Etag进行比如,如果相同则说明资源没改动过,那么响应304,客户端可以使用缓存;否则返回200,并且将报文的主题返回给客户端(Etag的说明可以参考百度百科)
可以简单的举个例子:
清空浏览器缓存,随便用浏览器发出一个http请求,得到如下图所示的结果:
刷新浏览器再次发起请求的时候,则因为缓存的作用会有如下结果:
到此为止,简单的分析完了浏览器缓存的那些事儿,下面带着上面的一些基本理论来正式分析一下CacheInterceptor的源码,当然仍然是分析其intercept方法:
Response intercept(Chain chain) throws IOException {
//如果配置了缓存:优先从缓存中读取Response
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;
。。。。
//如果根据缓存策略strategy禁止使用网络,并且缓存无效,直接返回空的Response
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
。。。
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)//空的body
。。。
.build();
}
//如果根据缓存策略strategy禁止使用网络,且有缓存则直接使用缓存
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//需要网络
Response networkResponse = null;
try {//执行下一个拦截器,发起网路请求
networkResponse = chain.proceed(networkRequest);
} finally {
。。。
}
//本地有缓存,
if (cacheResponse != null) {
//并且服务器返回304状态码(说明缓存还没过期或服务器资源没修改)
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//使用缓存数据
Response response = cacheResponse.newBuilder()
。。。
.build();
。。。。
//返回缓存
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)) {
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
。。。。
//返回最新的数据
return response;
}
简单的总结下上面的代码都做了些神马:
1、如果在初始化OkhttpClient的时候配置缓存,则从缓存中取caceResponse
2、将当前请求request和caceResponse 构建一个CacheStrategy对象
3、CacheStrategy这个策略对象将根据相关规则来决定caceResponse和Request是否有效,如果无效则分别将caceResponse和request设置为null
4、经过CacheStrategy的处理(步骤3),如果request和caceResponse都置空,直接返回一个状态码为504,且body为Util.EMPTY_RESPONSE的空Respone对象
5、经过CacheStrategy的处理(步骤3),resquest 为null而cacheResponse不为null,则直接返回cacheResponse对象
6、执行下一个拦截器发起网路请求,
7、如果服务器资源没有过期(状态码304)且存在缓存,则返回缓存
8、将网络返回的最新的资源(networkResponse)缓存到本地,然后返回networkResponse.
根据博客《Okhttp源码解析》得出的拦截器执行图(下图):
CacheInterceptor返回的Response交给拦截器链前面的一个拦截器,即BridgeInterceptor来处理,那么这个拦截器是怎么处理的呢,在《OkHttp之BridgeInterceptor简单分析 》这篇博客的最后有了简单的说明:
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//判断服务器是否支持gzip压缩格式,如果支持则交给kio压缩
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);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
BridgeInterceptor拿到CacheInterceoptor返回的response之后根据请求头的Content-Encoding类型来决定返回的类型,如果是gzip,则将response经过GzipSource 处理后将其Response的body设置为RealResponseBody对象,否则就简单的返回response。
简单流程如下:
到此本篇博客结束,如有不当之处,欢迎批评指正,共同学习
Okhttp之CacheInterceptor简单分析的更多相关文章
- OkHttp之ConnectInterceptor简单分析
在< Okhttp之CacheInterceptor简单分析 >这篇博客中简单的分析了下缓存拦截器的工作原理,通过此博客我们知道在执行完CacheInterceptor之后会执行下一个浏览 ...
- Okhttp之CallServerInterceptor简单分析
在Okhttp源码分析专栏的几篇博客分析了Okhttp几个拦截器的主要功能,还剩下最后一个拦截器CallServerInterceptor没有分析,本篇博客就简单分析下该拦截器的功能. 在Okhttp ...
- OkHttp之BridgeInterceptor简单分析
在< Okhttp源码简单解析(一) >这篇博客简单分析了Okhttp请求的执行流程,通过该篇博客我们知道OkHttp的核心网络请求中内置"拦截器"发挥了重大作用:本篇 ...
- Okhttp之连接池ConnectionPool简单分析(一)
开篇声明:由于本篇博文用到的一些观点或者结论在之前的博文中都已经分析过,所以本篇博文直接拿来用,建议读此博文的Monkey们按照下面的顺序读一下博主以下博文,以便于对此篇博文的理解: <Okht ...
- Okhttp对http2的支持简单分析
在< Okhttp之RealConnection建立链接简单分析>一文中简单的分析了RealConnection的connect方法的作用:打开一个TCP链接或者打开一个隧道链接,在打开t ...
- Okhttp之RealConnection建立链接简单分析
在之前的博客中我们知道Okhttp在发起链接请求先从链接池中获取连接,如果链接池中没有链接则创建新的链接RealConnection对象,然后执行其connet方法打开SOCKET链接(详见< ...
- Okhttp之RouteSelector简单解析
继前面的几篇OKhttp的拦截器简单分析之后,对于后续Okhttp之间的分析自己也着实琢磨了一段时间,是分析RealConnection?还是ConnectionPool,随着对Okhttp源码的深入 ...
- 简单分析JavaScript中的面向对象
初学JavaScript的时候有人会认为JavaScript不是一门面向对象的语言,因为JS是没有类的概念的,但是这并不代表JavaScript没有对象的存在,而且JavaScript也提供了其它的方 ...
- CSipSimple 简单分析
简介 CSipSimple是一款可以在android手机上使用的支持sip的网络电话软件,可以在上面设置使用callda网络电话.连接使用方式最好是使用wifi,或者3g这样上网速度快,打起电话来效果 ...
随机推荐
- 20145333茹翔 《Java程序设计》实验四 实验报告
实验要求 完成实验.撰写实验报告,实验报告以博客方式发表在博客园,注意实验报告重点是运行结果,遇到的问题(工具查找,安装,使用,程序的编辑,调试,运行等).解决办法(空洞的方法如"查网络&q ...
- 学Git,用Git ②
之前介绍了git的最核心功能游戏存档式的本地版本管理.这会我们介绍git剩下的两个核心功能:分支和远程仓库. 1.Git游戏存档进化版--Git分支 git分支的思想很有意思,git允许我们可以随时从 ...
- MysQL使用一查询
简介 查询的基本语法 select * from 表名; from关键字后面写表名,表示数据来源于是这张表 select后面写表中的列名,如果是*表示在结果中显示表中所有列 在select后面的列名部 ...
- mp4格式的视频,编码方式mpeg4,转化为h264
知识点:在使用vcastr3.swf播放器播放flv视频,(同时在html5页面,使用<video>标签时),发现某些MP4格式的代码不能播放 原因:vcastr3.swf和video,不 ...
- Python学习札记(三) I/O
参考:输入和输出 I/O 1.print()函数 a.调用print()输出字符串有以下两种方式:(1)print('[字符串]') (2)print("[字符串]") b.调用p ...
- $.extendGit 丢弃所有本地修改的方法
git checkout . #本地所有修改的.没有的提交的,都返回到原来的状态 git stash #把所有没有提交的修改暂存到stash里面.可用git stash pop回复. git rese ...
- 简单的使用hibernate插入数据的例子
数据库创建脚本: drop table person create table person( id varchar(32) not null primary key ...
- linux 用 grep 查找单个或多个字符串(关键字)
1.单个 cat /tmp/php.log | grep "成功" 所有的成功都会被查询出来. 2.多个,并列查询 cat /tmp/php.log | grep "推荐 ...
- Burpsuite的简单应用-y-Proxy
打开burpsuite:Proxy功能 一.进入Proxy页面,代理设置 将浏览器的代理地址设置一样: 之前没有代理,直接添加,有的话,可以勾选就好了:不同浏览器,设置位置不一致,百度参考 二.执行: ...
- 全--教程API, gem 'rest-client'(用于发简单请求); 请求测试;
安装:rest-client4400✨ gem install rest-client 一个简单的HTTP和REST client for Ruby. 可以用它来发HTTP请求 基本用法: requi ...