当前位置:网站首页>Okhttp3 source code explanation (IV) cache strategy, disadvantages of Android mixed development
Okhttp3 source code explanation (IV) cache strategy, disadvantages of Android mixed development
2022-06-26 07:36:00 【m0_ sixty-six million two hundred and sixty-five thousand and o】
// Get cache policy
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
// If you have a cache , Update relevant statistical indicators : shooting
if (cache != null) {
cache.trackResponse(strategy);
}
// If the current cache does not meet the requirements , Put it close
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn’t applicable. Close it.
}
// If the network is not available , At the same time, there is no qualified cache , Direct throw 504 error
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 you have a cache and don't use the network , Then directly return the cache result
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
// Try to get a reply through the network
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());
}
}
// If there is an existing cache , At the same time, a request was made , It means that this is a Conditional Get request
if (cacheResponse != null) {
// If the server returns NOT_MODIFIED, Caching works , Merge local cache and network response
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 {// If the response resource is updated , Turn off the original cache
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)) {
// Write the network response to cache in
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;
}
The core logic is marked in the code in the form of Chinese comments , Look at the code . You can see from the above code that , Almost all the movements are in the form of CacheStrategy Based on cache policy , Next, let's look at how the cache policy is generated , The related code is implemented in CacheStrategy$Factory.get() In the method :
[CacheStrategy$Factory]
/**
- Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
*/
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We’re forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}
/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {
// If there is no local cache , Initiate network request
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// If the current request is HTTPS, The cache does not TLS handshake , Reissue the network request
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);
}
// If the current caching policy is no caching or conditional get, Initiate network request
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//ageMillis: cache age
long ageMillis = cacheResponseAge();
//freshMillis: Cache freshness time
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 responseCaching = cacheResponse.cacheControl();
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
// If age + min-fresh >= max-age && age + min-fresh < max-age + max-stale, The cache has expired , // But the cache can still be used , Just add... In the header 110 Warning code
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());
}
// launch conditional get request
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = “If-None-Match”;
conditionValue = etag;
} else if (lastModified != null) {
conditionName = “If-Modified-Since”;
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = “If-Modified-Since”;
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
You can see that the core logic is getCandidate Function . Basically is HTTP Implementation of Caching Protocol , The core code logic has been explained with Chinese notes , Just look at the code .
- DiskLruCache
Cache Through internal DiskLruCache management cache Creation at the file system level , Read , Cleaning and so on , So let's see DiskLruCache Main logic of :
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() {
…
}
};
…
}
3.1 journalFile
DiskLruCache Internal log file , Yes cache Each read / write of the corresponds to a log record ,DiskLruCache Analyze and create by analyzing logs cache. The log file format is as follows :
libcore.io.DiskLruCache
1
100
2
CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
DIRTY 335c4c6028171cfddfbaae1a9c313c52
CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
REMOVE 335c4c6028171cfddfbaae1a9c313c52
DIRTY 1ab96a171faeeee38496d8b330771a7a
CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
READ 335c4c6028171cfddfbaae1a9c313c52
READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
front 5 The row is fixed , Respectively : Constant :libcore.io.DiskLruCache;diskCache edition ; Application version ;valueCount( Later, I will introduce ), Blank line
Each next line corresponds to one cache entry A status record of , The format for :[ state (DIRTY,CLEAN,READ,REMOVE),key, State correlation value( Optional )]:
DIRTY: Show a cache entry Being created or updated , Every successful DIRTY Each record should correspond to a CLEAN or REMOVE operation . If one DIRTY Missing expected match CLEAN/REMOVE, It corresponds to entry operation failed , It needs to be removed from lruEntries Delete in
CLEAN: explain cache Has been successfully operated , Currently it can be read normally . every last CLEAN The row also needs to record each of its value The length of
READ: Record once cache Read operation
REMOVE: Record once cache eliminate
There are four main scenarios for log files :
DiskCacheLru Create by reading the log file during initialization cache Containers :lruEntries. At the same time, the log filtering operation is unsuccessful cache term . The logic is DiskLruCache.readJournalLine,DiskLruCache.processJournal
Once the initialization is complete , To avoid the log file from expanding , Rebuild and simplify the log , Specific logic in DiskLruCache.rebuildJournal
Whenever there is cache Record it in the log file for the next initialization
When there are too many redundant logs , By calling cleanUpRunnable Thread rebuild log
3.2 DiskLruCache.Entry
every last DiskLruCache.Entry Corresponding to one cache Record :
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);
}
}
…
/**
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;
}
}
}
One Entry It mainly consists of the following parts :
key: Every cache There is one. key As its identifier . At present cache Of key Corresponding to URL Of MD5 character string
cleanFiles/dirtyFiles: every last Entry Corresponding to multiple files , The corresponding number of files is determined by DiskLruCache.valueCount Appoint . The current in OkHttp in valueCount by 2. each cache Corresponding 2 individual cleanFiles,2 individual dirtyFiles. The first cleanFiles/dirtyFiles Record cache Of meta data ( Such as URL, Creation time ,SSL Handshake records, etc ), The second file records cache The real content of .cleanFiles Record a steady state cache result ,dirtyFiles Records that are in the create or update state cache
currentEditor:entry Editor , Yes entry All operations of are done through its editor . A synchronization lock has been added inside the editor
3.3 cleanupRunnable
Clean up threads , Used to rebuild thin logs :
private final Runnable cleanupRunnable = new Runnable() {
public void run() {
synchronized (DiskLruCache.this) {
if (!initialized | closed) {
return; // Nothing to do
}
try {
trimToSize();
} catch (IOException ignored) {
mostRecentTrimFailed = true;
}
try {
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
} catch (IOException e) {
mostRecentRebuildFailed = true;
journalWriter = Okio.buffer(Okio.blackhole());
}
}
}
};
The trigger condition is journalRebuildRequired() In the method :
/**
We only rebuild the journal when it will halve the size of the journal and eliminate at least
2000 ops.
*/
boolean journalRebuildRequired() {
final int redundantOpCompactThreshold = 2000;
return redundantOpCount >= redundantOpCompactThreshold
&& redundantOpCount >= lruEntries.size();
}
When the redundant logs exceed the average number of log files and the total number of logs exceeds 2000 When the
3.4 SnapShot
cache snapshot , Records specific cache Content at a particular moment . Each direction DiskLruCache When requested, all returned are targets cache A snapshot of , The logic is DiskLruCache.get in :
[DiskLruCache.java]
/**
Returns a snapshot of the entry named {@code key}, or null if it doesn’t exist is not currently
readable. If a value is returned, it is moved to the head of the LRU queue.
*/
public synchronized Snapshot get(String key) throws IOException {
initialize();
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null || !entry.readable) return null;
Snapshot snapshot = entry.snapshot();
if (snapshot == null) return null;
redundantOpCount++;
// logging
journalWriter.writeUtf8(READ).writeByte(’ ‘).writeUtf8(key).writeByte(’\n’);
if (journalRebuildRequired()) {
executor.execute(cleanupRunnable);
}
return snapshot;
}
3.5 lruEntries
management cache entry The container of , Its data structure is LinkedHashMap. adopt LinkedHashMap Its own implementation logic reaches cache Of LRU Replace
3.6 FileSystem
Use Okio Yes File Encapsulation , To simplify the I/O operation .
3.7 DiskLruCache.edit
DiskLruCache It can be seen as Cache In the file system layer , Therefore, there is a one-to-one correspondence between the basic operation interfaces :
Cache.get() —>DiskLruCache.get()
Cache.put()—>DiskLruCache.edit() //cache Insert
Cache.remove()—>DiskLruCache.remove()
Cache.update()—>DiskLruCache.edit()//cache to update
among get Operation in 3.4 It has been introduced that ,remove The operation is relatively simple ,put and update Roughly similar in logic , Because of the space limit , This is only about Cache.put Logic of operation , For other operations, just look at the code :
[okhttp3.Cache.java]
CacheRequest put(Response response) {
String requestMethod = response.request().method();
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
if (!requestMethod.equals(“GET”)) {
// Don’t cache non-GET responses. We’re technically allowed to cache
// HEAD requests and some POST requests, but the complexity of doing
// so is high and the benefit is low.
return null;
}
if (HttpHeaders.hasVaryAll(response)) {
return null;
}
Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
entry.writeTo(editor);
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
You can see that the core logic is editor = cache.edit(key(response.request().url()));, Relevant code in DiskLruCache.edit:
[okhttp3.internal.cache.DiskLruCache.java]
synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
initialize();
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
Last
It is said that three years is a barrier for programmers , Whether to promote or improve their core competitiveness , These years have been critical .
Technology is developing so fast , Where to start learning , In order to reach the level of Senior Engineer , At last, we advanced to Android Architects / technician ? I summed up this 5 large ;
I've collected these years Ali , And Tencent , Bytes to beat , Huawei , Interview questions of Xiaomi and other companies , Sort out the interview requirements and technical points into a large and complete “ Android Architects ” interview Xmind( In fact, it took a lot more energy than expected ), Contains the context of knowledge + Branch details .
《Android Architecture video +BAT Interview topics PDF+ Learning notes 》
Online learning Android A lot of information , But if the knowledge learned is not systematic , When you encounter a problem, you just have a taste of it , No further study , So it's hard to really improve the technology . hope ** This systematic technology system ** There is a direction reference for you .
tedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
Last
It is said that three years is a barrier for programmers , Whether to promote or improve their core competitiveness , These years have been critical .
Technology is developing so fast , Where to start learning , In order to reach the level of Senior Engineer , At last, we advanced to Android Architects / technician ? I summed up this 5 large ;
I've collected these years Ali , And Tencent , Bytes to beat , Huawei , Interview questions of Xiaomi and other companies , Sort out the interview requirements and technical points into a large and complete “ Android Architects ” interview Xmind( In fact, it took a lot more energy than expected ), Contains the context of knowledge + Branch details .
《Android Architecture video +BAT Interview topics PDF+ Learning notes 》
[ Outside the chain picture transfer in …(img-JXueyCJZ-1644919790404)]
[ Outside the chain picture transfer in …(img-XhDviVvc-1644919790405)]
[ Outside the chain picture transfer in …(img-xBI6tyen-1644919790406)]
Online learning Android A lot of information , But if the knowledge learned is not systematic , When you encounter a problem, you just have a taste of it , No further study , So it's hard to really improve the technology . hope ** This systematic technology system ** There is a direction reference for you .
2021 In spite of the bumpy road , It's all about Android To fall , however , Don't panic , Make your own plan , Learn from yourself , Competition is everywhere , This is true of every industry . Believe in yourself , Nothing is impossible , Only the unexpected . Wish you all 2021 Everything is going well in the new year .
边栏推荐
- Tsinghua Yaoban chendanqi won Sloan award! He is a classmate with last year's winner Ma Tengyu. His doctoral thesis is one of the hottest in the past decade
- $a && $b = $c what???
- Solution to the problem of multi application routing using thinkphp6.0
- 手机开户哪个证券公司佣金最低?网上开户是否安全么?
- 5,10,15,20-tetra (4-methoxycarbonylphenyl) porphyrin tcmpp purple crystal; Meso-5,10,15,20-tetra (4-methoxyphenyl) porphyrin tmopp|zn[t (4-mop) p] and co[t (4-mop) p] complexes
- Take you three minutes to get started typescript
- Porphyrin based polyimide (ppbpis); Synthesis of crosslinked porphyrin based polyimides (ppbpi CRS) porphyrin products supplied by Qiyue biology
- 【推荐10个 让你轻松的 IDEA 插件,少些繁琐又重复的代码】
- The difference between insert ignore and insert into
- Iron / zinc / copper / platinum metal complexes such as 5,10,15,20-tetra (4-hydroxyphenyl) porphyrin (THPP) / (thppfe) / (thppzn) / (thppcu) / (thpppt) - Qiyue R & D
猜你喜欢
Tsinghua Yaoban chendanqi won Sloan award! He is a classmate with last year's winner Ma Tengyu. His doctoral thesis is one of the hottest in the past decade
MXNet对NIN网络中的网络的实现
Iron / zinc / copper / platinum metal complexes such as 5,10,15,20-tetra (4-hydroxyphenyl) porphyrin (THPP) / (thppfe) / (thppzn) / (thppcu) / (thpppt) - Qiyue R & D
Here is the command to display the disk space usage. Go ahead and pay attention to more wonderful things!
【推荐10个 让你轻松的 IDEA 插件,少些繁琐又重复的代码】
一项听起来大胆,并且非常牛逼的操作——复刻一个 Netflix
PXRD, IR, TGA of two-dimensional porphyrin COF (POR COF) /cof (2D pdpor COF) - supplied by Qiyue
Redis系列——redis启动,客户端day1-2
少年,你可知 Kotlin 协程最初的样子?
Calculate division in Oracle - solve the error report when the divisor is zero
随机推荐
两水先木示身为Unity3D职场人的个人觉悟
Jemter stress test - visualization tool support - [installation]
Which of the top ten securities companies has the lowest commission fee and is the most safe and reliable?
php array_ Merge details
一文分析EventBus-事件总线的使用方法和实现原理
Exploration and practice of incremental data Lake in station B
[recommend an entity class conversion tool mapstruct, which is powerful and easy to use]
Shengshi Haotong enterprise wechat sector creates a digital ecological community
Tetra - (4-pyridyl) porphyrin tpyp and metal complexes zntpyp/fetpyp/mntpyp/cutpyp/nitpyp/cotpyp/ptpyp/pdtpyp/cdtpyp (supplied by Qiyue porphyrin)
Jemter 压力测试 -可视化工具-【使用篇】
Liquid crystal texture diagram of purple solid mm-tpp-10c methacrylic acid decanoxy tetraphenyl porphyrin and mm-tpp-12c methacrylic acid dodecanoxy tetraphenyl porphyrin - Qi Yue display
How to convert Unicode into Chinese characters in Excel
Redis series - five common data types day1-3
Introduction to mapping in ES
B站增量数据湖探索与实践
A bold sounding and awesome operation - remake a Netflix
SQL
Liujinhai, chief architect of zhongang Mining: according to the analysis of fluorite supply and demand, it is estimated that the fluorine coating market has great potential
Porphyrin based polyimide (ppbpis); Synthesis of crosslinked porphyrin based polyimides (ppbpi CRS) porphyrin products supplied by Qiyue biology
手机开户哪个证券公司佣金最低?网上开户是否安全么?