当前位置:网站首页>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 .

  1. 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 .

原网站

版权声明
本文为[m0_ sixty-six million two hundred and sixty-five thousand and o]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202171039585853.html

随机推荐