本文将从以下方面介绍Volley的缓存机制
- 使用缓带来哪些优势
- Volley的缓存原理
- Volley对服务端header解析
- Volley缓存过期判断
使用缓带来哪些优势:
- 速度:已缓存的资源加载的更快;
- 减少服务器负载:不用每次都和服务器进行交互;
- 较少网络流量:对于已缓存且在有效期内的数据将不会再次向服务器请求,如果已过期,只需要判断返回的数据是否发生变化,没有变化返回304,告诉客户端可以使用以前的缓存数据,否则返回新的数据;
- 离线浏览:用户可以在离线时使用;
Volley缓存原理
Volley的缓存原理和浏览器的缓存原理基本一致,基于HTTP协议,通过HTTP协议Header中的Cache-Control(或Expires)和Last-Modified(或Etag)等字段来控制缓存,再使用DiskBasedCache类将数据存储在本地;,下面对Header里面的一些字段进行说明, 服务端:Cache-Control、Expires、max-age、Last-Modified、ETag、stale-while-revalidate、must-revalidate、proxy-revalidate、no-cache、no-store;客户端:If-Modified-Since、If-None-Match;
服务端:
- Cache-Control:控制是否进行缓存,Http1.1后才出现,可添加的配置信息如下:
- no-cache或no-store,示列:Cache-Control:no-store或Cache-Control:no-cache表示不缓存数据
- max-age:缓存有效期(单位秒);示列Cache-Control:max-age=60表示基于当前时间60秒后缓存数据过期
- stale-while-revalidate:异步缓存机制(单位秒),当请求一个即将过期的文件时,会将本地的过期数据返回给用户,然后在异步请求数据,与must-revalidate或proxy-revalidate配套使用;示列:Cache-Control:stale-while-revalidate=1
- must-revalidate或proxy-revalidate:表示是否启用stale-while-revalidate字段,有了该字段后stale-while-revalidate才会生效;示列:Cache-Control:stale-while-revalidate=1;must-revalidate
- Expires:表示在该指定日期前缓存数据有效;示列:Expires:1451273426630
- Last-Modified:最后一次修改时间,精确到秒;可表示某个文件或者某个请求返回数据的最后修改时间,与If-Modified-Since配套使用,服务端通过取If-Modified-Since值与需要返回的Last-Modified值进行对比判断是否返回304;示列:Last-Modified:1451273426630
-
ETag:表示某个请求或者某个文件的标记,如某个文件或者某个请求返回数据的md5值、hashcode值等,与If-None-Match配套使用,服务端通过取If-None-Match值与需要返回的ETag值进行对比判断是否返回304;示列:ETag:ee4f36eca94844eaebfcc2633f1a8de8
客户端:
- If-Modified-Since:最后一次修改时间;值取服务端Last-Modified值,在客户端header中设置该字段,服务端取值后与需要返回的Last-Modified值进行对比判断是否返回304
-
If-None-Match:缓存标识:值取服务端ETag值,在客户端的header中设置该字段,服务端取值后与需要返回的ETag值进行对比判断是否返回304
Volley在BasicNetwork的addCacheHeaders方法中对If-Modified-Since和If-None-Match进行设置,代码如下:
private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
// If there's no cache entry, we're done.
if (entry == null) {
return;
}
if (entry.etag != null) {
headers.put("If-None-Match", entry.etag);
}
if (entry.lastModified > ) {
Date refTime = new Date(entry.lastModified);
headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
}
}
Volley对服务端header解析
Volly中对服务返回header信息的解析在HttpHeaderParser的parseCacheHeaders方法中,下面展现部分代码:
.........省略部分代码............
headerValue = headers.get("Cache-Control");
if (headerValue != null) {
hasCacheControl = true;
String[] tokens = headerValue.split(",");
for (int i = ; i < tokens.length; i++) {
String token = tokens[i].trim();
if (token.equals("no-cache") || token.equals("no-store")) {
return null;
} else if (token.startsWith("max-age=")) {
try {
maxAge = Long.parseLong(token.substring());
} catch (Exception e) {
}
} else if (token.startsWith("stale-while-revalidate=")) {
try {
staleWhileRevalidate = Long.parseLong(token.substring());
} catch (Exception e) {
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
mustRevalidate = true;
}
}
}
headerValue = headers.get("Expires");
if (headerValue != null) {
serverExpires = parseDateAsEpoch(headerValue);
}
headerValue = headers.get("Last-Modified");
if (headerValue != null) {
lastModified = parseDateAsEpoch(headerValue);
}
serverEtag = headers.get("ETag");
// Cache-Control takes precedence over an Expires header, even if both exist and Expires
// is more restrictive.
if (hasCacheControl) {
softExpire = now + maxAge * ;
finalExpire = mustRevalidate
? softExpire
: softExpire + staleWhileRevalidate * ;
} else if (serverDate > && serverExpires >= serverDate) {
// Default semantic for Expire header in HTTP specification is softExpire.
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
.........省略部分代码............
从以上代码中可以看出Cache-Control的优先级要高于 Expires
Volley缓存过期判断
Volley的缓存逻辑处理在CacheDispatcher类的run方法中;
- 通过Cache.Entry entry = mCache.get(request.getCacheKey());取出缓存信息,如果不存在缓存信息,则将请求放入mNetworkQueue
- 通过 Cache.Entry 的isExpired()方法判断缓存是否过期;isExpired()方法代码如下:
/** True if the entry is expired. */
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
这里的ttl时候为header解析时的finalExpire时间,如果过期,则将请求放入mNetworkQueue;finalExpire取值如下:
// Cache-Control takes precedence over an Expires header, even if both exist and Expires
// is more restrictive.
if (hasCacheControl) {
softExpire = now + maxAge * ;
finalExpire = mustRevalidate
? softExpire
: softExpire + staleWhileRevalidate * ;
} else if (serverDate > && serverExpires >= serverDate) {
// Default semantic for Expire header in HTTP specification is softExpire.
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
可以看出finalExpire>=softExpire ,与stale-while-revalidate和must-revalidate或proxy-revalidate的设置有关。
3.通过Cache.Entry的refreshNeeded()方法判断是否需要刷新
/** True if a refresh is needed from the original data source. */
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
这里的sotfTtl时间为softExpire,该方法的用途如下:
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
这段代码表示如果不需要刷新,这将数据直接返回缓存数据,否则,返回缓存数据的同时异步请求新的数据。