我们日常开发经常和网络打交道,从服务器上面获取数据。但是如果我们如果在短时间内多次向服务器请求的数据其实都是一样的,我们是没有必要这么浪费用户的流量的。为了提高用户的体验,我们需要合理使用缓存,要使用缓存就得搞明白缓存的一些相关策略机制,于是就有了这篇文章。
Http的缓存机制
我们可以先看下面的思维导图来简单了解一下Http协议里面的一些缓存机制:
导图.png正如上图所示,Http的缓存就仅可用于Get请求。为了方便大家理解上图,这里我简单介绍一下一些缓存相关的重要字段:
Expires:
这个字段是记录着缓存的有效期,如Expires:Wed, 26 Jul 2017 13:18:20 GMT.当客户端发现这个缓存有效期已经过去了,就会重新向服务器请求数据.但是这个字段存在问题,就是客户端本地时间和服务器端时间相差过大,就是出现缓存读取失效的情况.
Cache-Control:
Last-Modified / If-Modified-Since
这是需要有缓存存在才能起作用的字段。
<code>Last-Modified:</code>这个字段是用来标记这个响应资源的最后修改时间。服务器在响应请求时,将会告诉客户端这个响应资源的最后修改时间,如Last-Modified:Thu, 13 Jul 2017 06:31:05 GMT;
<code>If-Modified-Since:</code>当缓存不新鲜时,发现缓存具有Last-Modified声明,服务器就会检查请求头If-Modified-Since(客户端缓存页面数据的最后修改时间),服务器会把这个时间与服务器上实际文件的最后修改时间进行比较。如If-Modified-Since:Mon , 24 Jul 2017 18:53:33 GMT
如果时间一致,那么返回HTTP状态码304,客户端接到之后,就直接把本地缓存文件显示到屏幕上面。
如果时间不一致,就返回HTTP状态码200和新的文件内容,客户端接到之后,会丢弃旧文件,把新文件缓存起来并显示新的内容。
Etag / If-None-Match
这是需要有缓存存在才能起作用的字段,而且<code>Etag</code>的优先级高于<code>Last-Modified</code>。
<code>Etag:</code>这个字段是请求变量的实体标记。简单来说就是服务器响应时给请求URL标记,并在HTTP响应头中将其传送到客户端进行存储。服务器端返回的格式如:Etag:“5d8c72a5edda8d6a:3239″.这是通过对文件的索引节(INode)大小(Size)和最后修改时间(MTime)进行Hash后得到的数值;
Etag 和 Last-Modified
到这里我们会发现一件事,就是<code>Last-Modified</code>和<code>Etag</code>的功能居然是重复的,这就奇怪了。既然使用<code>Last-Modified</code>已经足以让客户端知道本地的缓存副本是否足够新,为什么还需要<code>Etag</code>呢?其实还是有原因的,因为<code>Last-Modified</code>有下面几个问题难以解决:
1.<code>Last-Modified</code>标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内被修改多次的话,它将不能准确标注文件的修改时间
2.如果某些文件会被定期生成,当有时内容并没有任何变化,但<code>Last-Modified</code>却改变了,导致文件没法使用缓存
3.有可能存在服务器没有准确获取文件修改时间或者与代理服务器时间不一致等情形
<code>Etag</code>是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存;<code>Last-Modified</code>与<code>ETag</code>是可以一起使用的,服务器会优先验证<code>ETag</code>,一致的情况下,才会继续比对<code>Last-Modified</code>,最后才决定是否返回304或200.
通过okhttp的cache设置来加深理解
public void click(View view) {
int maxCacheSize = 10 * 1024 * 1024;
//设置缓存路径
Cache cache = new Cache(getCacheDir(), maxCacheSize);
OkHttpClient client = new OkHttpClient.Builder()
//设置缓存
.cache(cache)
.build();
Request request = new Request.Builder()
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.w(TAG, "start");
Log.w(TAG, "response cache :" + response.cacheResponse());
Log.w(TAG, "response network :" +
response.body().close();
}
@Override
public void onFailure(Call call, IOException e) {
}
});
}
我们先来看下qq官网的Cache-Control:
image.png可以看到max-age=60,说明本地缓存在60秒内都是新鲜的,那么我们就连续两次访问qq官网来看下Log吧:
image.png image.png为了满足好奇心,这里我们来逐一来看下:
7f4c79817fabaeaa0e909754cfe655e7.0里面的内容 7f4c79817fabaeaa0e909754cfe655e7.1里面的内容 journal里面的内容 image.pngmax-age=0说明缓存有效时间为0秒,但是设置了Etag校验缓存,我们看下请求打印的Log吧:
image.png image.png它已经在Cache-Control里面设置不支持硬盘存储的no-store字段了,我们看下运行的Log:
访问知乎的结果可以看到,我们已经多次重新访问知乎的首页了,但是每次都只能重新向服务器拿最新的数据。