版权声明:
本公众号发布的所有文章,均属于原创,版权归本公众号所有。
未经允许,不得转载。
一、前言
在 Android 开发中,如果用过 WebView 来加载一个网页,总是逃不过 WebView 的缓存策略的设定。WebView 本身也提供了多种缓存的策略来供开发者使用,而有一些涉及到 Http 协议,所以将两个概念集中整理一起讲解,希望对大家有帮助。
二、WebView 缓存策略
WebView 本身是提供了设置缓存策略的 API 的,可以使用 WebSetting 对象进行设置。
而 WebSetting 中,可以设置多种缓存策略,如下:
LOAD_CACHE_ONLY:不使用网络,只读本地缓存。
LOAD_NORMAL:在 API Level 17 中已经被废弃,而在API Level 11 开始,策略如 LOAD_DEFALT。
LOAD_NO_CACHE:不使用缓存,只从网络获取数据。
LOAD_CACHE_ELSE_NETWORK:只要本地有缓存,就从缓存中读取数据。
LOAD_DEFAULT:根据 Http 协议,决定是否从网络获取数据。
LOAD_CACHE_ONLY 和 LOAD_NO_CACHE 都是比较极端的情况,一般我们也不会使用。LOAD_NORMAL 已经被废弃了,也没什么好说的,而 LOAD_CACHE_ELSE_NETWORK 本身已经决定了策略,只要本地有,就不会重新获取,也不会有什么可以变化的。
LOAD_DEFAULT 才是我们项目上比较常用测策略,本文主要对 LOAD_DEFAULT 模式进行讲解,本身它也是 WebView 的默认模式。但是实际开发中,最好还是显式设定一下,很多 ROM 都可能会修改这部分代码的,我在乐视手机上做过测试,它的默认策略就是 LOAD_CACHE_ELSE_NETWORK 。
先来看看 LOAD_DEFAULT 的 API。
可以看到,它是默认策略,如果不设定强制策略,在资源没有过期的时候,从缓存获取,如果资源过期了,则从网络获取。
这样的一个策略,就和 Http 缓存相关了,由协议来确定资源加载的策略。
三、Http缓存策略
既然知道 WebView 也是遵循 Http 的缓存策略的,那么我们就先来看看 Http 缓存的策略是怎么样的。
用 Charles 抓一个包,看看一个常规的静态文件。
可以看到,一个请求的响应头中,包含了很多信息,而其中有一部分是和缓存相关的,下面我们来一一讲解。
1、Cache-Control
Cache-Control 是 Http 1.1 中新增加的一个用来定义缓存时间的头。如果使用了 Cache-Control 的话,会覆盖掉 Http 1.0 中的一些,例如:Pragma、Expires等,以 Cache-Control 为准。
Cache-Control 也是一个通用的 Http 报文头字段,它可以分别在请求报文和响应报文中使用,而它作为不同的使用方式,存在不同的含义。
Cache-Control 的规范写法:
Cache-Control:cache-directive
cache-directive 的可选值有很多,no-cache、no-store、only-if-cached 等,有兴趣的可以自行查查 Http 协议中的定义。但是一般而言,如上图所示,会使用 max-age 来设定一个最大的有效时间的方式来使用,max-age 设定的时间,单位为秒(s)。
Cache-Control 的 max-age 出现在请求报文头和响应报文头中,含义是不一样的。
请求头:告知服务器客户端希望接收一个存在时间(Age)不大于 max-age 的资源。
响应头:告知客户端,该资源在 max-age 设定的时间内是新鲜的,无需再向服务器发送请求了。
WebView 中,如果被设定为 LOAD_DEFAULT 的话,是遵循此规则的,也就是说,当请求资源回来之后,会根据 max-age 设定当前资源的过期时间,在这个时间范围内,则不会重新请求,会直接从缓存中读取资源,而上面的例子中,max-age 被设定为 40000,差不多 11 个多小时。
2、数据新鲜度校验
Cache-Control 这个报文头,决定了客户端是否需要向服务器发送请求。但是,如果已经过期(超过 max-age 设定的时间),当这个请求发送到服务器之后,是否需要服务器返回一个完整的数据呢?
虽然我们设定了 max-age ,但是它只能表示一个合理的变化频度,也就是说,可能超过这个 max-age 设定的时间,但是请求的文件也并没有变化。那么服务器只需要告知客户端,文件没变化,你还是读缓存的资源就好了。
这个策略,就是使用 ETag 和 Last-Modified 来校验的。当客户端通过 max-age 判断发现请求的资源文件已经不再新鲜了,需要从服务器上重新获取,在向服务器发送请求的时候,就会通过这些值告知服务器本地缓存资源的一个标识,服务器就通过这个标识来判断客户端缓存的资源是否依然新鲜。
可以看到,当 max-age 失效之后,发送的请求,会携带 if-None-Match 和 if-Modified-Since 这两个报文头,服务器就是根据这两个报文头来判定客户端的资源是否过期,如果过期,则返回新的资源,如果未过期,则返回一个状态码304的一个响应,告知客户端可以继续读取缓存使用。
细心的应该可以看到,请求头里的 if-None-Match 就是之前响应头里的 ETag ,而 if-Modified-Since 就是之前响应头里的 Last-Modified。
下面看看他们的含义:
ETag:资源的唯一匹配标识信息。
Last-Modified:资源的最后一次修改时间。
if-None-Match:比较 ETag 是否不一致。
If-Modified-Since:比较资源最后更新的时间是否一致。
当然,对于请求头,还有其他的规则,例如:if-Match、if-Unmodified-Since 等,这个就看服务器的和客户端的实现了。
这里的 ETag 和 Last-Modified 其实可以分开使用,但是如果被同时使用,则要求服务器对这两个值都进行校验,都校验通过了才会返回 304。
那么这么做,有什么好处,实际是所有的缓存策略,都是为了减小各种地方的压力。对于客户端而言,减少了网络请求的压力,对于服务器而言,也减小了请求和流量的压力。
可以看到,一个完整的资源请求,需要 24kb,而当资源没有过期的时候,只需要 1kb 左右即可,并且响应的时间也更快了。
四、例外情况
到这里,对于 WebView 的各个缓存策略的理解应该就明确了。如果使用 LOAD_DEFAULT 则依赖 Http 的缓存策略,而Http 缓存又是依赖 Cache-Control、ETag、Last-Modified 等值来确定的。
那么,如果我们将 CacheMode 设定为 LOAD_DEFAULT ,并且给出了一个 max-age = 40000 资源响应头,在不清理缓存的情况下,我们的 WebView 就不会对该继续发送请求?这样我们不小心设定了一个极大的 max-age 值,是否客户端的资源很久才会被更新?
其实并不是绝对的,在 WebView 中,加载网页,我们一般使用 loadUrl() 方法,当使用 loadUrl() 方法的时候,它会完全遵循上面给出的缓存策略,在没有过期的时候去从缓存中读取资源。
但是浏览器在 Http 缓存策略之外,还提供了强制刷新的策略,这样也保证了在某些情况下,可以去服务器是重新获取资源。而这个策略反应在 WebView 中,就是使用 reload() 方法。当使用 reload() 方法的时候,WebView 会重新请求资源,并在报文头中,修改 max-age 为 0 。这样既可以保证当前刷新了会有一个真实的网络请求,又能保证在缓存资源不过期的情况下,不给服务器造成压力。
五、小结
Http 的缓存策略,在 Android 开发中,并不是只用在 WebView 上,一些网络库,例如 OkHttp,也是依赖 Http 缓存策略来进行缓存数据的。