合理的利用本地的缓存策略,可以有效的减少网络请求时候的网络开销,减少响应的延迟。而在OkHttp3.0中的缓存主要作用在缓存拦截器CacheInterceptor里面。所以现在我们就具体分析下CacheInterceptor中对缓存的具体操作。
我们都知道,OkHttp的核心或者说精华部分就是其强大的拦截器功能,几乎你在使用他的时候都是一些拦截器在背后默默帮你做一些操作。而缓存拦截器也正是在背后默默帮你对数据的缓存作着操作。在了解缓存拦截器之前,我们必须先理解内部的三个东西。
Cache:缓存管理器。其内部拥有一个DiskLruCache算法在操作,将获取到的缓存写入到系统文件当中去。
CacheStrategy:缓存策略。内部维护了request与response。通过策略来判断到底是从网络端获取数据还是从本地缓存中获取数据亦或者两者并用。
CacheStrategyFactory:缓存工厂。通过此方法来获取到缓存策略这个对象。
实际的缓存是在CacheInterceptor这个类中的intercept方法中完成的,那么我们下面来看看这个方法中具体的操作逻辑。
@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; if (cache != null) { cache.trackResponse(strategy); } //如果当前的缓存不符合要求,则将其close if (cacheCandidate != null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // 如果网络不能用并且缓存不能用则抛出504服务器超时异常 if (networkRequest == null && cacheResponse == null) { return new Response.Builder() .request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(Util.EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); } // 如果需要网络加载,则去进行网络加载 if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } Response networkResponse = null; try { networkResponse = chain.proceed(networkRequest); } finally { // If we're crashing on I/O or otherwise, don't leak the cache body. if (networkResponse == null && cacheCandidate != null) { closeQuietly(cacheCandidate.body()); } } // 如果既有缓存,同时又发起了请求,说明此时是一个Conditional Get请求 if (cacheResponse != null) { if (networkResponse.code() == HTTP_NOT_MODIFIED) { Response response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); // Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by initContentStream()). cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); if (cache != null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { // 将网络请求之后的结果写入cache CacheRequest cacheRequest = cache.put(response); return cacheWritingResponse(cacheRequest, response); } if (HttpMethod.invalidatesCache(networkRequest.method())) { try { cache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } } return response; } 复制代码
分析上面代码可以看到,首先我们会从缓存策略工厂(CacheStrategyFactory)中获取缓存策略(CacheStrategyFactory)。之后做几次判断,如果本地有缓存则直接获取缓存,如果缓存和网络都不能使用,则抛出504连接超时的异常。如果本地没有缓存但是网络可以使用,则调用networkResponse来请求网络数据,并且将网络数据通过cacheWritingResponse()写入diskLruCache中。到此整个缓存就算是全部弄完了。
Cache内部通过DiskLruCache管理cache在文件系统层面的创建,读取,清理等等工作,接下来看下DiskLruCache的主要逻辑:
public final class DiskLruCache implements Closeable, Flushable { final FileSystem fileSystem; final File directory; private final File journalFile; private final File journalFileTmp; private final File journalFileBackup; private final int appVersion; private long maxSize; final int valueCount; private long size = 0; BufferedSink journalWriter; final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true); // Must be read and written when synchronized on 'this'. boolean initialized; boolean closed; boolean mostRecentTrimFailed; boolean mostRecentRebuildFailed; /** * To differentiate between old and current snapshots, each entry is given a sequence number each * time an edit is committed. A snapshot is stale if its sequence number is not equal to its * entry's sequence number. */ private long nextSequenceNumber = 0; /** Used to run 'cleanupRunnable' for journal rebuilds. */ private final Executor executor; private final Runnable cleanupRunnable = new Runnable() { public void run() { ...... } }; ... } 复制代码
DiskLruCache内部日志文件,对cache的每一次读写都对应一条日志记录,DiskLruCache通过分析日志分析和创建cache
日志文件的应用场景主要有四个:
每一个DiskLruCache.Entry对应一个cache记录
一个Entry主要由以下几部分构成:
总结起来DiskLruCache主要有以下几个特点: