Android HTTP 缓存策略(用于检查磁盘数据是否过期)

前言:

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;
}

资源参考

猜你喜欢

转载自blog.csdn.net/hexingen/article/details/81220916