Okhttp的缓存机制(1)

// 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 the 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转换过来的key
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
//根据key获取对应的snapshot
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
return null;
}
try {
//创建一个Entry对象,并由snapshot.getSource()获取Sink
entry = 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构建一个Entry
Entry entry = new Entry(network);
//从缓存中获取DiskLruCache.Snapshot
DiskLruCache.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) {
    throw new IllegalStateException();
    }
    if (!entry.readable || entry.currentEditor != this) {
    return null;
    }
    try {
    return fileSystem.source(entry.cleanFiles[index]);
    } catch (FileNotFoundException e) {
    return null;
    }
    }
    }

/**

  • Returns a new unbuffered output stream to write the value at {@code index}. If the underlying
  • output stream encounters errors when writing to the filesystem, this edit will be aborted
  • when {@link #commit} is called. The returned output stream does not throw IOExceptions.
    */
    public Sink newSink(int index) {
    synchronized (DiskLruCache.this) {
    if (done) {
    throw new IllegalStateException();
    }
    if (entry.currentEditor != this) {
    return Okio.blackhole();
    }
    if (!entry.readable) {
    written[index] = true;
    }
    File dirtyFile = entry.dirtyFiles[index];
    Sink sink;
    try {
    sink = fileSystem.sink(dirtyFile);
    } catch (FileNotFoundException e) {
    return Okio.blackhole();
    }
    return new FaultHidingSink(sink) {
    @Override protected void onException(IOException e) {
    synchronized (DiskLruCache.this) {
    detach();
    }
    }
    };
    }

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
d移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-rVVcYt5h-1715713545254)]

[外链图片转存中…(img-JCpuGug1-1715713545255)]

[外链图片转存中…(img-1GkDThnP-1715713545256)]

[外链图片转存中…(img-zYskvkJo-1715713545257)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值