前言:
HTTP缓存策略有效提高网络效率,开发一个网络库或者图片加载库都需要用到它,用于判断存储的数据是否过期,是否需要重新请求服务器。
简单介绍HTTP缓存策略
HTTP 1.0 缓存
- Pragma :表示是否缓存
- Expires : 过去时间
HTTP 1.1 缓存:
新增了一些字段,具体如下所示。
Request Header:
- Cache-Control
- If-Modified-Since
- If-None-Match
Response Header:
- Cache-Control
- Last-Modified
- ETag
Cache-Control控制使用缓存策略和过期时间,If-Modified-Since和Last-Modified一起使用来缓存时间判断缓存是否过期,If-None-Match和ETag根据验证令牌来控制缓存是否过期,ETag是可选的方式。
Cache-Control介绍:
HTTP 1.1 引入 Cache-Control 响应头参数,弥补了 Expires的局限。
Http的缓存设置,Cache-Control用于控制缓存,常见的取值有private、no-cache、max-age、must- revalidate等,默认Private.
- private :响应只能够作为私有的缓存,默认。
- no-cache:每次访问,都刷新,实时向服务器端请求资源 。
- max-age:设置缓存最大的有效时间,过完指定时间后再访问刷新
- no-store:响应不缓存,不写进磁盘中,基于某些安全考虑。
- must-revalidate :响应在特定条件下会被重用,以满足接下来的请求,但是它必须到服务器端去验证它是不是仍然是最新的。
- proxy-revalidate :类似于 must-revalidate,但不适用于代理缓存.
接下来,开始实战操作 , 以下代码来源Android 官方Volley库。
HTTP Response的缓存策略
来看一个HTTP1.1的典型Response标头。
HTTP/1.1 200 OK
Date: Fri, 30 Oct 1998 13:19:41 GMT
Server: Apache/1.3.3 (Unix)
Cache-Control: max-age=3600, must-revalidate
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Last-Modified: Mon, 29 Jun 1998 02:28:12 GMT
ETag: "3e86-410-3596fbbc"
Content-Length: 1040
Content-Type: text/html
1. softExpire算法:
在Http规范中Expire标头的语义是softExpire。若是两者同时存在,Cache-Control是优先于Expires标头,Expires是更多的局限性。
当存在Cache-Control的情况下:过期时间=(当前时间+缓存的有效时间*1000)
long softExpire =getSoftExpire() ;
public long getSoftExpire(Map<String, String> headers){
//当前时间
long now = System.currentTimeMillis();
long maxAge = 0;
String headerValue=headers.get("Cache-Control");
if (headerValue != null) {
String[] tokens = headerValue.split(",");
for (int i = 0; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.equals("no-cache") || token.equals("no-store")) {
return 0;
} else if (token.startsWith("max-age=")) {
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e) {
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
maxAge = 0;
}
}
return now + maxAge * 1000;
}
return 0;
}
当只有Expires标头的情况下: 过期时间=现在时间+(服务器返回数据的过期时间-服务器响应时间)
long softExpire =getSoftExpire() ;
public long getSoftExpire(Map<String, String> headers){
//当前时间
long now = System.currentTimeMillis();
long serverDate = 0;
long serverExpires = 0;
String headerValue;
headerValue = headers.get("Date");
if (headerValue != null) {
serverDate = parseDateAsEpoch(headerValue);
}
headerValue = headers.get("Expires");
if (headerValue != null) {
serverExpires = parseDateAsEpoch(headerValue);
}
//服务器响应时间大于0,且服务器过期时间>响应时间
if (serverDate > 0 && serverExpires >= serverDate){
return now + (serverExpires - serverDate);
}
return 0;
}
public static long parseDateAsEpoch(String dateStr) {
try {
// Parse date in RFC1123 format if this header contains one
return newRfc1123Formatter().parse(dateStr).getTime();
} catch (ParseException e) {
// Date in invalid format, fallback to 0
return 0;
}
}
private static SimpleDateFormat newRfc1123Formatter() {
final String RFC1123_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
SimpleDateFormat formatter = new SimpleDateFormat(RFC1123_FORMAT, Locale.US);
formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
return formatter;
}
2. 判断是否在缓存时间:
boolean refreshNeeded=softExpire<System.currentTimeMillis()
当缓存时间大于当前时间,不需要重新请求。
HTTP Request的缓存策略
先获取到上一次的Response中信息:serverEtag 和 lastModified
String serverEtag = null;
long lastModified = 0;
String headerValue;
headerValue = headers.get("Last-Modified");
if (headerValue != null) {
lastModified = parseDateAsEpoch(headerValue);
}
serverEtag = headers.get("ETag");
public static long parseDateAsEpoch(String dateStr) {
try {
// Parse date in RFC1123 format if this header contains one
return newRfc1123Formatter().parse(dateStr).getTime();
} catch (ParseException e) {
// Date in invalid format, fallback to 0
return 0;
}
}
private static SimpleDateFormat newRfc1123Formatter() {
final String RFC1123_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
SimpleDateFormat formatter = new SimpleDateFormat(RFC1123_FORMAT, Locale.US);
formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
return formatter;
}
接下来,将上一次缓存策略的表头添加到Request中,一起传递给服务器。
if (serverEtag!= null) {
headers.put("If-None-Match", serverEtag);
}
if (lastModified > 0) {
headers.put("If-Modified-Since",formatEpochAsRfc1123(lastModified));
}
static String formatEpochAsRfc1123(long epoch) {
return newRfc1123Formatter().format(new Date(epoch));
}
private static SimpleDateFormat newRfc1123Formatter() {
final String RFC1123_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
SimpleDateFormat formatter = new SimpleDateFormat(RFC1123_FORMAT, Locale.US);
formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
return formatter;
}
资源参考: