Cache-control 的小知识
一般的缓存策略都会根据 response
的 cache-control
来指定的。而这个 key
有以下几个选项:
-
public
表示数据内容都可以被储存起来,就连有密码保护的网页也储存,安全性很低 -
private
表示数据内容只能被储存到私有的cache,仅对某个用户有效,不能共享 -
no-cache
表示可以缓存,但是只有在跟WEB服务器验证了其有效后,才能返回给客户端,触发对比缓存 -
no-store
表示请求和响应都禁止被缓存,强制缓存,对比缓存都不会触发 -
max-age
表示返回数据的过期时间
对以上几项进行以下分类:首先 public
& private
都是可以缓存内容的,但在安全性上有区别,而 no-cache
字面意思和实际上有点出入,并非不能缓存,而是可以缓存,但是每次访问都会先问一下服务器,这个文件是否变了。no-store
这个没什么好说的了,就是不能缓存,而 max-age
则和前面的格格不入,因为它的值是指过期时间。那么把前面几个分类的话,得到以下几个缓存策略
- 强缓存:
public
&private
过期前都不会找服务器聊天 - 对比缓存:
no-cache
每次都会找服务器聊天,但如果发现没有更新的话,就不会重新下载。 - 不缓存
由此可以看出,使用缓存最主要使用到 强缓存 和 对比缓存,而什么时候使用 强缓存 ,什么时候 对比缓存,这个得从前端开发的基本流程说起。
前端开发流程
前端资源
前端开发的流程就目前主流来说一般会使用 webpack
等工具去打包,js
, css
, image
都能设置成每次打包根据内容是否替换去生成一个 hash
值的文件名。因此可以看出,在静态资源的把控上,前端已经能够做到了更新内容而每次更新文件名。因此在这些文件上,我们可以直接采用 强缓存 。
前端程序的入口
和所有程序都一样,前端工程入口一般就是一个 HTML
。 而这个 HTML
可以是一个 file
也可以是一个请求响应体。不管它是一个怎么的形态存在,response
的 header
都会存在 content-type: text/html
这个设定。但当它是请求响应体的时候,我们是不会缓存它的。因此我们只讨论当它是一个存在于 CDN
中的一个 HTML
格式的 file
的时候。应该如何缓存。当然这个缓存策略还取决于客户端 webView
载入 URL
的方案。
-
当一些页面,我们期望于前端在内容进行变化而非后端去操作才去变化的。比如说什么隐私公告,XX条款等,一般都会把访问
URL
写死在代码中。我们针对这种情况假设一个以下的场景https://www.XXX.com/index.html
是放置在CDN
上的一个HTML
文件,用于给客户端作为辅佐的内容展示页,因为产品会经常改动这个页面的内容。而CDN
默认的cache-control
一般只会设定max-age
的。因此这个页面在打开的第一次就会缓存,过期前都不会再向服务器请求页面来看看是否更新了。当然最理想还是在cache-control
加上no-cache
从而做到 对比缓存,而且一般的CDN
厂商提供的相关API
去设置cache-control
。但这样做的话不安全,如果有一次设置少了no-cache
的话就直接变成了 强缓存 从而导致过期前无法再更新。因此最好客户端层面上最好做多一层保险。 - 有一些页面,我们希望后端可以通过配置下发到客户端去打开,比如说开屏弹窗(
webView
)。
从以上两个场景我们可以看到,如果是后端下发的 URL
的话,我们可以通过修改入口名字来摆脱 强缓存 。但是如果是写死在客户端的 URL
的话,我们还是最好把它设置成 对比缓存 。但是为了开发的简便性,我还是比较推荐把所有的 html
类型的缓存都设置成 对比缓存 :毕竟有时候后端下发的配置可能就在 APP
打开的时候才去获取的,而有些用户能很久都不重启 APP
。
客户端的实现
由上面我们得到一个很不错的缓存策略:就是当 content-type: text/html
的时候,我们采用 对比缓存 。而反之采用 强缓存 。
安卓下的实现
安卓的缓存策略有:
-
LOAD_CACHE_ONLY
:不使用网络,只读取本地缓存数据 -
LOAD_DEFAULT
:(默认)根据cache-control
决定是否从网络上取据。 -
LOAD_NO_CACHE
:不使用缓存,只从网络获取数据. -
LOAD_CACHE_ELSE_NETWORK
:只要本地有,无论是否过期,或no-cache,都使用缓存中的数据。
需要做的方案很简单:禁用 webview
的缓存 setCacheMode(WebSettings.LOAD_NO_CACHE)
重写 shouldInterceptRequest
方法。使用 okhttp
的缓存替代 webview
的缓存
// SanYueWebView.java
public class SanYueWebView extends WebView {
public SanYueWebView(Context context) {
super(context, null,0,0);
// 禁用缓存
setCacheMode(WebSettings.LOAD_NO_CACHE);
setWebViewClient(new WebViewClient(){
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = String.valueOf(request.getUrl());
// file 协议的不走自定义缓存
if (url.startsWith("file")){
return super.shouldInterceptRequest(view, request);
}
// 这里使用 OKHTTP,加入 NetworkInterceptor
File cacheDirectory = new File(cacheDirectoryString);
int cacheSize = 1024 * 1024 * 1024; // 1G
Cache cache = new Cache(cacheDirectory, cacheSize);
resourcesClient = new OkHttpClient.Builder()
// 这里如果希望能离线访问的话,可以加多一个 Interceptor 去操作没有网络的时候直接直接返回一个缓存内容
.addNetworkInterceptor(new SanYueNetCacheInterceptor(60*60*24*365))
.cache(cache).build();
final Call call = resourcesClient.newCall(new Request.Builder().url(url).build());
try{
final Response response = call.execute();
return new WebResourceResponse(
response.header("content-type", "text/plain"),
response.header("content-encoding", "utf-8"),
Objects.requireNonNull(response.body()).byteStream()
);
}
catch (IOException e) {
e.printStackTrace();
}
// 请求不成功就返回 空信息
byte[] nullInputStream = {};
return new WebResourceResponse("text/plain", "utf-8", new ByteArrayInputStream(nullInputStream));
}
})
}
}
// SanYueNetCacheInterceptor
public class SanYueNetCacheInterceptor implements Interceptor {
private int maxAge;
public SanYueNetCacheInterceptor(int maxAge) { this.maxAge = maxAge; }
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder builder = request.newBuilder();
request = builder.build();
Response response = chain.proceed(request);
List<String> contentTypes = response.headers("Content-Type");
Boolean flag = false;
for (String value : contentTypes){
if (value.equals("text/html")){
flag = true;
break;
}
}
// Content-Type 带上 text/html 的设置成对比缓存
if(flag){
return response.newBuilder()
.header("Cache-Control", "no-cache , max-age=" + maxAge)
.build();
}
return response;
}
}
IOS 下实现
待续
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。