Okhttp 缓存设计
一、缓存接口
InternalCache接口,定义一系列缓存方法。
public interface InternalCache {
Response get(Request request) throws IOException;
CacheRequest put(Response response) throws IOException;
void remove(Request request) throws IOException;
void update(Response cached, Response network);
void trackConditionalCacheHit();
void trackResponse(CacheStrategy cacheStrategy);
}
Cache类是okhttp设计的缓存类,通过持有InternalCache匿名内部类对象访问Cache类缓存方法(同名方法)。final类型,未实现InternalCache接口。
final InternalCache internalCache = new InternalCache() {
@Override
public Response get(Request request) throws IOException {
return Cache.this.get(request);
}
@Override
public CacheRequest put(Response response) throws IOException {
return Cache.this.put(response);
}
...
};
在缓存拦截器定义时,从OkHttpClient获取InternalCache缓存对象。
interceptors.add(new CacheInterceptor(client.internalCache()));
OkHttpClient类中包含Cache和InternalCache,二者有一个是null,okhttp希望使用内部设计的缓存。
final Cache cache;
final InternalCache internalCache;
InternalCache internalCache() {
return cache != null ? cache.internalCache : internalCache;
}
InternalCache接口暴露Cache类缓存的具体实现,如果Cache是空,选择InternalCache类对象。
二、存储
Cache缓存采用DiskLruCache类存储。
final DiskLruCache cache;
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
LinkedHashMap<String, Entry>
DiskLruCache.Entry实体,Snapshot快照包含key、Source数组。
数组中第0个Source,读取,封装Cache.Entry,由Entry构建Response。
DiskLruCache.Entry实体创建。
数组数量valueCount:ENTRY_COUNT=2。分别存METADATA和BODY。
存储文件目录,在Cache的构造方法传入,Cache对象创建时机,在okhttpclient建造者builder中创建,自己定义存储目录。
cleanFiles文件,创建Source。
DiskLruCache.Entry创建时,初始化cleanFiles文件,文件名:directory目录?+key。
match匹配request和response。
url md5作为key。
三、策略
CacheInterceptor拦截器
@Override
public Response intercept(Chain chain) throws IOException{
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
....
}
根据请求,在Cache类中查找Response,创建缓存策略。
工厂CacheStrategy.Factory类,CacheStrategy内部类,构造方法传入当前时间、Request请求、缓存Response。
Factory工厂类get()方法创建CacheStrategy对象。
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
return new CacheStrategy(null, null);
}
return candidate;
}
内部getCandidate()方法。
private CacheStrategy getCandidate() {
if (cacheResponse == null) {//1
return new CacheStrategy(request, null);
}
if (request.isHttps() && cacheResponse.handshake() == null) {//2
return new CacheStrategy(request, null);
}
if (!isCacheable(cacheResponse, request)) {//3
return new CacheStrategy(request, null);
}
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {//4
return new CacheStrategy(request, null);
}
...
}
根据请求Request和缓存Response,生成策略对象CacheStragey。
CacheStragey策略仅包含Request的情况。
1,缓存Response是空。
2,https协议请求,且Response的handshake是空。
3,CacheControl设置为noStore状态,说明不存储,策略中仅包含Request。
4,请求中包含noCache标志,或者请求中Header的If-Modified-Since或If-None-Match非空,策略中仅包含Request。
5,当小于最大期限时,使用缓存,策略中仅包含Response。
缓存策略创建后,根据内部networkRequest与cacheResponse是否存在,决定后续是否使用缓存。
若无需网络访问,构建Response,返回。若需网络访问,继续链式调用,下一个拦截器是ConnectInterceptor,建立访问链路。最后,获取的Response视情况存入Cache。
存储Request、cacheResponse以及从Response的Header解析的servedDate、expires。
Request头部
If-None-Match:加入上次访问服务器获取的etag(相当资源的hash),如果服务器资源未变,返回304,如果服务器资源改变,返回200。
If-Modified-Since:把上次服务器告知的最后修改时间返回给服务器。如果在这个指定时间后未修改,返回200,否则304。
Response头部
Last-Modified:服务器文件的最后修改时间。
Date:服务器返回请求发送的日期时间。
1,缓存不存在
2,https协议请求,无握手对象
3,针对reponse的code,,非白名单的code,不存储,response和request中的nostore标志都是false,才会存储,有一个nostore不存储,就不会存储。
白名单:200,203,204,300.
4,request的cacheControl,noCache不存储,或请求header有"If-Modified-Since或If-None-Match,不存储
5,response的CacheControl,是immutable,返回的策略request是空。
6,当responseCaching.noCache是false时,比较request中的时间,返回策略是一个响应超时的response,无request。
7,解析response的header上次修改时间,etag,服务端时间,这些都没有,不存储。
8,用这些重建request,加入到header中,绑定response
If-None-Match,If-Modified-Since
任重而道远