写点什么

Okhttp 的缓存机制

用户头像
Android架构
关注
发布于: 1 小时前

CacheStrategy

主要用于判断是否使用缓存数据


public final class CacheStrategy {


public Factory(long nowMillis, Request request, Response cacheResponse) {this.nowMillis = nowMillis;//网络请求和缓存响应 this.request = request;this.cacheResponse = cacheResponse;


if (cacheResponse != null) {//找到缓存响应的响应头信息 Headers headers = cacheResponse.headers();for (int i = 0, size = headers.size(); i < size; i++) {//查看响应头信息中是否有以下字段信息 String fieldName = headers.name(i);String value = headers.value(i);if ("Date".equalsIgnoreCase(fieldName)) {servedDate = HttpDate.parse(value);servedDateString = value;} else if ("Expires".equalsIgnoreCase(fieldName)) {expires = HttpDate.parse(value);} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {lastModified = HttpDate.parse(value);lastModifiedString = value;} else if ("ETag".equalsIgnoreCase(fieldName)) {etag = value;} else if ("Age".equalsIgnoreCase(fieldName)) {ageSeconds = HeaderParser.parseSeconds(value, -1);} else if (OkHeaders.SENT_MILLIS.equalsIgnoreCase(fieldName)) {sentRequestMillis = Long.parseLong(value);} else if (OkHeaders.RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {receivedResponseMillis = Long.parseLong(value);}}}}


public CacheStrategy get() {//获取判定的缓存策略 CacheStrategy candidate = getCandidate();


if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {// 如果判定的缓存策略的网络请求不为空,但是只使用缓存,则返回两者都为空的缓存策略。return new CacheStrategy(null, null);}


return candidate;}


/** Returns a strategy to use assuming the request can use the network. */private CacheStrategy getCandidate() {// No cached response.//如果没有缓存响应,则返回没有缓存响应的策略 if (cacheResponse == null) {return new CacheStrategy(request, null);}


// Drop the cached response if it's missing a required handshake.//如果请求是 https,而缓存响应的握手信息为空,则返回没有缓存响应的策略 if (request.isHttps() && cacheResponse.handshake() == null) {return new CacheStrategy(request, null);}


// If this response shouldn't have been stored, it should never be used// as a response source. This check should be redundant as long as the// persistence store is well-behaved and the rules are constant.//如果请求对应的响应不能被缓存,则返回没有缓存响应的策略 if (!isCacheable(cacheResponse, request)) {return new CacheStrategy(request, null);}


//获取请求头中的 CacheControl 信息 CacheControl requestCaching = request.cacheControl();//如果请求头中的 CacheControl 信息是不缓存的,则返回没有缓存响应的策略 if (requestCaching.noCache() || hasConditions(request)) {return new CacheStrategy(request, null);}


//获取响应的年龄 long ageMillis = cacheResponseAge();//计算上次响应刷新的时间 long freshMillis = computeFreshnessLifetime();//如果请求里有最大持续时间要求,则取较小的值作为上次响应的刷新时间 if (requestCaching.maxAgeSeconds() != -1) {freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));}


//如果请求里有最短刷新时间要求,则用它来作为最短刷新时间 long minFreshMillis = 0;if (requestCaching.minFreshSeconds() != -1) {minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());}


//最大过期时间 long maxStaleMillis = 0;//获取缓存响应头中的 CacheControl 信息 CacheControl responseCaching = cacheResponse.cacheControl();//如果缓存响应不是必须要再验证,并且请求有最大过期时间,则用请求的最大过期时间作为最大过期时间 if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());}


//如果支持缓存,并且持续时间+最短刷新时间<上次刷新时间+最大验证时间 则可以缓存 if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {Response.Builder builder = cacheResponse.newBuilder();if (ageMillis + minFreshMillis >= freshMillis) {builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");}long oneDayMillis = 24 * 60 * 60 * 1000L;if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");}//返回响应缓存 return new CacheStrategy(null, builder.build());}


//构造一个新的有条件的 Request,添加 If-None-Match,If-Modified-Since 等信息 Request.Builder conditionalRequestBuilder = request.newBuilder();


if (etag != null) {conditionalRequestBuilder.header("If-None-Match", etag);} else if (lastModified != null) {conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString);} else if (servedDate != null) {conditionalRequestBuilder.header("If-Modified-Since", servedDateString);}


Request conditionalRequest = conditionalRequestBuilder.build();//根据是否有 If-None-Match,If-Modified-Since 信息,返回不同的缓存策略 return hasConditions(conditionalRequest)? new CacheStrategy(conditionalRequest, cacheResponse): new CacheStrategy(conditionalRequest, null);}


/**


  • Returns true if t


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


he request contains conditions that save the server from sending a response


  • that the client has locally. When a request is enqueued with its own conditions, the built-in

  • response cache won't be used.*/private static boolean hasConditions(Request request) {return request.header("If-Modified-Since") != null || request.header("If-None-Match") != null;}}

Cache

对外开放的缓存类,类似数据库能够增删改查


  1. 增添缓存


CacheRequest put(Response response) {String requestMethod = response.request().method();//如果请求是"POST","PUT","PATCH","PROPPATCH","REPORT"则移除这些缓存


if (HttpMethod.invalidatesCache(response.request().method())) {try {remove(response.request());} catch (IOException ignored) {}return null;}//仅支持 GET 的请求缓存,其他请求不缓存 if (!requestMethod.equals("GET")) {return null;}//判断请求中的 http 数据包中 headers 是否有符号"*"的通配符,有则不缓存


if (HttpHeaders.hasVaryAll(response)) {return null;}//把 response 构建成一个 Entry 对象 Entry entry = new Entry(response);DiskLruCache.Editor editor = null;try {//生成 DiskLruCache.Editor 对象 editor = cache.edit(key(response.request().url()));if (editor == null) {return null;}//对缓存进行写入 entry.writeTo(editor);//构建一个 CacheRequestImpl 类,包含 Ok.io 的 Sink 对象 return new CacheRequestImpl(editor);} catch (IOException e) {abortQuietly(editor);return null;}}


  1. 查找缓存


Response get(Request request) {//获取 url 转换过来的 keyString key = key(request.url());DiskLruCache.Snapshot snapshot;Entry entry;try {//根据 key 获取对应的 snapshotsnapshot = cache.get(key);if (snapshot == null) {return null;}} catch (IOException e) {return null;}try {//创建一个 Entry 对象,并由 snapshot.getSource()获取 Sinkentry = new Entry(snapshot.getSource(ENTRY_METADATA));} catch (IOException e) {Util.closeQuietly(snapshot);return null;}//通过 entry 和 response 生成 respson,通过 Okio.buffer 获取请求体,然后封装各种请求信息 Response response = entry.response(snapshot);if (!entry.matches(request, response)) {//对 request 和 Response 进行比配检查,成功则返回该 Response。Util.closeQuietly(response.body());return null;}return response;}


  1. 更新缓存


void update(Response cached, Response network) {//用 Respon 构建一个 EntryEntry entry = new Entry(network);//从缓存中获取 DiskLruCache.SnapshotDiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot;DiskLruCache.Editor editor = null;try {//获取 DiskLruCache.Snapshot.edit 对象 editor = snapshot.edit(); // Returns null if snapshot is not current.if (editor != null) {//将 entry 写入 editor 中 entry.writeTo(editor);editor.commit();}} catch (IOException e) {abortQuietly(editor);}}


  1. 删除缓存


主体位于 DiskLruCache 之中


void remove(Request request) throws IOException {//通过 url 转化成的 key 去删除缓存 cache.remove(key(request.url()));}


  1. writeTo ok.io


public void writeTo(DiskLruCache.Editor editor) throws IOException {BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));


sink.writeUtf8(url).writeByte('\n');sink.writeUtf8(requestMethod).writeByte('\n');sink.writeDecimalLong(varyHeaders.size()).writeByte('\n');for (int i = 0, size = varyHeaders.size(); i < size; i++) {sink.writeUtf8(varyHeaders.name(i)).writeUtf8(": ").writeUtf8(varyHeaders.value(i)).writeByte('\n');}


sink.writeUtf8(new StatusLine(protocol, code, message).toString()).writeByte('\n');sink.writeDecimalLong(responseHeaders.size() + 2).writeByte('\n');for (int i = 0, size = responseHeaders.size(); i < size; i++) {sink.writeUtf8(responseHeaders.name(i)).writeUtf8(": ").writeUtf8(responseHeaders.value(i)).writeByte('\n');}sink.writeUtf8(SENT_MILLIS).writeUtf8(": ").writeDecimalLong(sentRequestMillis).writeByte('\n');sink.writeUtf8(RECEIVED_MILLIS).writeUtf8(": ").writeDecimalLong(receivedResponseMillis).writeByte('\n');


if (isHttps()) {sink.writeByte('\n');sink.writeUtf8(handshake.cipherSuite().javaName()).writeByte('\n');writeCertList(sink, handshake.peerCertificates());writeCertList(sink, handshake.localCertificates());sink.writeUtf8(handshake.tlsVersion().javaName()).writeByte('\n');}sink.close();}

DiskLruCache

真实存储(文件格式)的缓存功能类,使用了基于 LinkedHashedMap。可以看到除了一些关键的方法之外其主要包括了三个重要的内部类。



  1. Entry


用于存储缓存数据的实体类,一个 url 对应一个实体,在 Entry 还有 Snapshot 对象


private final class Entry {final String key;


/** Lengths of this entry's files. */final long[] lengths;final File[] cleanFiles;final File[] dirtyFiles;


/** True if this entry has ever been published. */boolean readable;


/** The ongoing edit or null if this entry is not being edited. */Editor currentEditor;


/** The sequence number of the most recently committed edit to this entry. */long sequenceNumber;


Entry(String key) {this.key = key;


lengths = new long[valueCount];cleanFiles = new File[valueCount];dirtyFiles = new File[valueCount];


// The names are repetitive so re-use the same builder to avoid allocations.StringBuilder fileBuilder = new StringBuilder(key).append('.');int truncateTo = fileBuilder.length();for (int i = 0; i < valueCount; i++) {fileBuilder.append(i);cleanFiles[i] = new File(directory, fileBuilder.toString());fileBuilder.append(".tmp");dirtyFiles[i] = new File(directory, fileBuilder.toString());fileBuilder.setLength(truncateTo);}}


/** Set lengths using decimal numbers like "10123". */void setLengths(String[] strings) throws IOException {if (strings.length != valueCount) {throw invalidLengths(strings);}


try {for (int i = 0; i < strings.length; i++) {lengths[i] = Long.parseLong(strings[i]);}} catch (NumberFormatException e) {throw invalidLengths(strings);}}


/** Append space-prefixed lengths to {@code writer}. */void writeLengths(BufferedSink writer) throws IOException {for (long length : lengths) {writer.writeByte(' ').writeDecimalLong(length);}}


private IOException invalidLengths(String[] strings) throws IOException {throw new IOException("unexpected journal line: " + Arrays.toString(strings));}


/**


  • Returns a snapshot of this entry. This opens all streams eagerly to guarantee that we see a

  • single published snapshot. If we opened streams lazily then the streams could come from

  • different edits.*/Snapshot snapshot() {if (!Thread.holdsLock(DiskLruCache.this)) throw new AssertionError();


Source[] sources = new Source[valueCount];long[] lengths = this.lengths.clone(); // Defensive copy since these can be zeroed out.try {for (int i = 0; i < valueCount; i++) {sources[i] = fileSystem.source(cleanFiles[i]);}return new Snapshot(key, sequenceNumber, sources, lengths);} catch (FileNotFoundException e) {// A file must have been deleted manually!for (int i = 0; i < valueCount; i++) {if (sources[i] != null) {Util.closeQuietly(sources[i]);} else {break;}}// Since the entry is no longer valid, remove it so the metadata is accurate (i.e. the cache// size.)try {removeEntry(this);} catch (IOException ignored) {}return null;}}}


  1. Snapshot


public final class Snapshot implements Closeable {private final String key;private final long sequenceNumber;private final Source[] sources;private final long[] lengths;


Snapshot(String key, long sequenceNumber, Source[] sources, long[] lengths) {this.key = key;this.sequenceNumber = sequenceNumber;this.sources = sources;this.lengths = lengths;}


public String key() {return key;}


/**


  • Returns an editor for this snapshot's entry, or null if either the entry has changed since

  • this snapshot was created or if another edit is in progress.*/public @Nullable Editor edit() throws IOException {return DiskLruCache.this.edit(key, sequenceNumber);}


/** Returns the unbuffered stream with the value for {@code index}. */public Source getSource(int index) {return sources[index];}


/** Returns the byte length of the value for {@code index}. */public long getLength(int index) {return lengths[index];}


public void close() {for (Source in : sources) {Util.closeQuietly(in);}}}


  1. Editor


在 Editor 的初始化中要传入 Editor,其实 Editor 就是编辑 entry 的类


public final class Editor {final Entry entry;final boolean[] written;private boolean done;


Editor(Entry entry) {this.entry = entry;this.written = (entry.readable) ? null : new boolean[valueCount];}


/**


  • Prevents this editor from completing normally. This is necessary either when the edit causes

  • an I/O error, or if the target entry is evicted while this editor is active. In either case

  • we delete the editor's created files and prevent new files from being created. Note that once

  • an editor has been detached it is possible for another editor to edit the entry.*/void detach() {if (entry.currentEditor == this) {for (int i = 0; i < valueCount; i++) {try {fileSystem.delete(entry.dirtyFiles[i]);} catch (IOException e) {// This file is potentially leaked. Not much we can do about that.}}entry.currentEditor = null;}}


/**


  • Returns an unbuffered input stream to read the last committed value, or null if no value has

  • been committed.*/public Source newSource(int index) {synchronized (DiskLruCache.this) {if (done) {

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Okhttp的缓存机制