概述
合理地利用本地缓存可以有效地减少网络开销,减少响应延迟。OkHttp 也支持缓存功能。
关于 OkHttp 缓存的使用,请参考博客OkHttp 使用指南 – 基本使用 。
本文将会介绍一些 Http 的缓存知识以及OkHttp实现缓存的原理。
Http 缓存
Http 协议在头信息中定义了一些域来标识缓存功能。我们首先了解一下相关的知识,有助于我们更好理解 OkHttp 缓存。
Expires
1 | Expires: Thu, 12 Mar 2019 12:08:54 GMT |
超时时间,一般用在服务器的response报头中用于告知客户端对应资源的过期时间。当客户端需要再次请求相同资源时先比较其过期时间,如果尚未超过过期时间则直接返回缓存结果,如果已经超过则重新请求。
该字段存在于HTTP/1.0中,这种机制有一个非常大的问题,因为该字段是存在于响应头中,也就是说它的时间是服务器上的时间,但是客户端的时间是很有可能与服务器上的时间存在误差的,比如不在一个时区,用户修改了自己电脑时间等因素,这样这个字段就没有意义了;在HTTP 1.1开始,使用Cache-Control: max-age=秒 替代;而且现在浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。
Cache-Control
缓存中非常重要的一个字段,作用与Expires差不多,存在于响应头,都是标注当前资源的有效期。
但是它有很多的值,可以指定较为复杂的缓存规则,如果与Expires同时存在,Cache-Control比Expires优先级更高。
1 | Cache-Control:max-age=31536000,public |
可以组合的值有:
- public:表明该资源或者说响应可以被任何用户缓存,比如客户端,代理服务器等都可以缓存资源,写法:Cache-Control:public
- private:表明该资源只能被单个用户缓存,默认是private,即只能被客户端缓存,不能被代理服务器缓存,写法:Cache-Control:private
- max-age:表明该资源的有效时间,单位是s,写法: Cache-Control:max-age=3600,即在获取该资源后3600s内不需要再向服务器获取
- no-cache:表明客户端需要忽略已存在的缓存,强制每次请求直接发送给服务器,拉取资源,写法:Cache-Control:no-cache
- no-store:表明该资源不能被缓存,如果缓存了需要删除,写法:Cache-Control:no-store
- s-maxage:和max-age含义类似,只不过用于public 修饰的缓存,写法:Cache-Control:s-maxage=3600
- must-revalidate:表明在使用缓存前必须要验证旧资源状态,并且不可使用过期资源, 写法:Cache-Control:must-revalidate
- max-stale:表明缓存的资源在过期了但未超过max-stale指定的时间,那么就可以继续使用该缓存,超过后就必须去服务器获取;写法:Cache-Control:max-stale(代表着资源永不过期); Cache-Control:max-stale=3600(表明在缓存过期后的3600秒内还可以继续用)
- min-fresh:字面意思是最小新鲜度,跟max-age相对应(最大新鲜度),比如max-age=3600,min-fresh=600,那么 他两的差值就是3000,也就是说缓存真正有效时间只有3000s,超过这个时间就要去服务器拉取了。可以代表客户端告知服务器,如果当前时间加上min-fresh的值,超了该缓存的过期时间,则要给我一个新的。
- only-if-cached:不管缓存是否过期,或者服务端有更新,只要存在缓存就是用它,写法:Cache-Control:only-if-cached
- no-transform:不得对资源进行转换,即代理服务器不能修改Content-Encoding, Content-Range, Content-Type等HTTP头;因为有时候代理服务器为了节省缓存空间或者提高传输效率,会对图片等进行压缩;写法: Cache-Control:no-transform、
- immutable:表示资源在有效期内服务器不会对其更改,这样客户端就不需要再发送验证请求头,比如If-None-Match或If-Modified-Since来检测更新,即使用户主动刷新页面,写法:Cache-Control:immutable
Last-Modified-Date
客户端第一次请求时,服务器返回:
1 | Last-Modified: Tue, 12 Jan 2016 09:31:27 GMT |
当客户端二次请求时,可以头部加上如下header:
1 | If-Modified-Since: Tue, 12 Jan 2016 09:31:27 GMT |
如果当前资源没有被二次修改,服务器返回304告知客户端直接复用本地缓存。
ETag
ETag是对资源文件的一种摘要,可以通过ETag值来判断文件是否有修改。当客户端第一次请求某资源时,服务器返回:
1 | ETag: "5694c7ef-24dc" |
客户端再次请求时,可在头部加上如下域:
1 | If-None-Match: "5694c7ef-24dc" |
如果文件并未改变,则服务器返回304告知客户端可以复用本地缓存。
OkHttp 缓存实现原理
相关类
OkHttp 中实现缓存相关的类有下面几个:
- CacheInterceptor:实现缓存的拦截器,缓存工作都是由它来完成的。
- Cache:缓存管理器,其内部包含一个 DiskLruCache 将cache写入文件系统。
- CacheStrategy:缓存策略。其内部维护一个request和response,通过指定request和response来描述是通过网络还是缓存来获取response,或者二者同时使用。
- CacheStrategy$Factory:缓存策略工厂类,根据实际请求返回对应的缓存策略
- CacheControl:缓存控制,我们使用 OkHttpClient 时可以使用这个类来控制缓存
CacheInterceptor
先来看一下 CacheInterceptor
的核心代码 CacheInterceptor.intercept()
1 | @Override public Response intercept(Chain chain) throws IOException { |
CacheStrategy
从上面代码可以看出,缓存的使用都是根据缓存策略来执行的,那么缓存策略是如何生成的呢?我们接下来解析一下。
先来看一下缓存策略工厂:
1 | public Factory(long nowMillis, Request request, Response cacheResponse) { |
缓存策略工厂是根据 Request
和获取的本地缓存 cacheResponse
来构造的,主要是对一些变量赋值以及解析一下 cacheResponse
的头信息,为后面生成缓存策略做准备。
1 | // 获取缓存策略 |
get()
方法这里有个对得到的缓存策略的判断,candidate.networkRequest != null
意思就是通过对缓存的解析,得到的结果是我们需要通过网络请求获取响应。但是我们的请求头设置了 only-if-cached
,那这里的意思就不是说只是用缓存,而是不要使用网络。那这两个就出现了矛盾,OKHttp 的解决方法是直接创建一个参数都是 null 的 CacheStrategy,这样在缓存拦截器中将会构建一个504错误返回给用户。
1 | /** Returns a strategy to use assuming the request can use the network. */ |
Cache
Cache 是 OkHtpp 的Cache管理器,负责将数据缓存到文件系统,或者从文件系统上取出响应的缓存信息。Cache 内部通过 DiskLruCache 管理 cache 在文件系统层面的创建,读取,清理等等工作。
构造方法
1 | Cache(File directory, long maxSize, FileSystem fileSystem) { |
添加缓存
1 | @Nullable CacheRequest put(Response response) { |
获取缓存
1 | @Nullable Response get(Request request) { |
更新缓存
1 | void update(Response cached, Response network) { |
推荐阅读
OkHttp 3.7源码分析(四)——缓存策略
OKHttp3–缓存拦截器CacheInterceptor源码解析【八】
OKHttp3– HTTP缓存机制解析 缓存处理类Cache和缓存策略类CacheStrategy源码分析 【九】