1.okhttp源碼分析(一)——基本流程(超詳細(xì))
2.okhttp源碼分析(二)——RetryAndFollowUpInterceptor過(guò)濾器
3.okhttp源碼分析(三)——CacheInterceptor過(guò)濾器
4.okhttp源碼分析(四)——ConnectInterceptor過(guò)濾器
5.okhttp源碼分析(五)——CallServerInterceptor過(guò)濾器
前言
前一篇博客分析了RetryAndFollowUpInterceptor過(guò)濾器,緊接著下一個(gè)過(guò)濾器應(yīng)該是BridgeInterceptor,但是這個(gè)過(guò)濾器的作用主要是在對(duì)Request和Resposne的封裝,源碼理解起來(lái)也比較好理解,所以就沒(méi)有分析這個(gè)過(guò)濾器。直接分析下一個(gè)過(guò)濾器CacheInterceptor,其實(shí)從名字就可以看出這個(gè)過(guò)濾器的主要作用就是緩存。
分析
1.宏觀流程
和上一篇博客的分析相同,按照我的理解,我將過(guò)濾器中最關(guān)鍵的方法刪減了一下,有助于從宏觀上大體對(duì)這個(gè)過(guò)濾器進(jìn)行理解。
@Override public Response intercept(Chain chain) throws IOException {
//1
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
//2
if (networkRequest == null && cacheResponse == null) {
return new Response;
}
//3
if (networkRequest == null) {
return cacheResponse;
}
//4
networkResponse = chain.proceed(networkRequest);
//5
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
return response;
}
}
//6
Response response = networkResponse;
//7
cache.put(response);
return response;
}
好吧,刪了點(diǎn)還是比較多的,但是剩下的代碼已經(jīng)比較直白了,很好理解了。其實(shí)看過(guò)濾器看多了也基本上掌握了基本法,找最關(guān)鍵的那行代碼chain.proceed(networkRequest);,上面就是請(qǐng)求前過(guò)濾器做的事,下面就是請(qǐng)求后過(guò)濾器做的事。
總體上看:
//1
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
1.嘗試通過(guò)這個(gè)Request拿緩存。
//2
if (networkRequest == null && cacheResponse == null) {
return new Response.code(504);
}
2.如果不允許使用網(wǎng)絡(luò)并且緩存為空,新建一個(gè)504的Resposne返回。
//3
if (networkRequest == null) {
return cacheResponse;
}
3.如果不允許使用網(wǎng)絡(luò),但是有緩存,返回緩存。
//4
networkResponse = chain.proceed(networkRequest);
4.鏈?zhǔn)秸{(diào)用下一個(gè)過(guò)濾器。
//5
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
return response;
}
}
5.如果緩存不為空,但是網(wǎng)絡(luò)請(qǐng)求得來(lái)的返回碼是304(如果返回碼是304,客戶端有緩沖的文檔并發(fā)出了一個(gè)條件性的請(qǐng)求(一般是提供If-Modified-Since頭表示客戶只想比指定日期更新的文檔)。服務(wù)器告訴客戶,原來(lái)緩沖的文檔還可以繼續(xù)使用。)則使用緩存的響應(yīng)。
//6
Response response = networkResponse;
//7
cache.put(response);
return response;
6、7.使用網(wǎng)絡(luò)請(qǐng)求得到的Resposne,并且將這個(gè)Resposne緩存起來(lái)(前提當(dāng)然是能緩存)。
接下來(lái)就是腦袋都大的細(xì)節(jié)了,我也不敢說(shuō)分析的十分詳細(xì),只能就我的理解總體分析,學(xué)習(xí)。
2.過(guò)程細(xì)節(jié)
@Override public Response intercept(Chain chain) throws IOException {
//默認(rèn)cache為null,可以配置cache,不為空嘗試獲取緩存中的response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//根據(jù)response,time,request創(chuàng)建一個(gè)緩存策略,用于判斷怎樣使用緩存
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
//如果緩存策略中禁止使用網(wǎng)絡(luò),并且緩存又為空,則構(gòu)建一個(gè)Resposne直接返回,注意返回碼=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 we don't need the network, we're done.
//不使用網(wǎng)絡(luò),但是又緩存,直接返回緩存
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//直接走后續(xù)過(guò)濾器
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 we have a cache response too, then we're doing a conditional get.
//當(dāng)緩存響應(yīng)和網(wǎng)絡(luò)響應(yīng)同時(shí)存在的時(shí)候,選擇用哪個(gè)
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//如果返回碼是304,客戶端有緩沖的文檔并發(fā)出了一個(gè)條件性的請(qǐng)求(一般是提供If-Modified-Since頭表示客戶
// 只想比指定日期更新的文檔)。服務(wù)器告訴客戶,原來(lái)緩沖的文檔還可以繼續(xù)使用。
//則使用緩存的響應(yīng)
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());
}
}
//使用網(wǎng)絡(luò)響應(yīng)
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//所以默認(rèn)創(chuàng)建的OkHttpClient是沒(méi)有緩存的
if (cache != null) {
//將響應(yīng)緩存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
//緩存Resposne的Header信息
CacheRequest cacheRequest = cache.put(response);
//緩存body
return cacheWritingResponse(cacheRequest, response);
}
//只能緩存GET....不然移除request
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
首先看第一行代碼,一開(kāi)始以為很簡(jiǎn)單,但看了后才發(fā)現(xiàn)里面涉及的流程非常麻煩。
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
從邏輯上看,這里還是比較好理解的,通過(guò)Request嘗試從緩存成功拿對(duì)應(yīng)的緩存Resposne,如果拿到了則賦值,沒(méi)有則為null。這里重點(diǎn)就要看怎么取緩存了。其實(shí)CacheInterceptor重點(diǎn)比較難以理解的就是:拿緩存,緩存策略,存緩存
public final class CacheInterceptor implements Interceptor {
final InternalCache cache;
public CacheInterceptor(InternalCache cache) {
this.cache = cache;
}
}
//======================RealCall.java======================
interceptors.add(new CacheInterceptor(client.internalCache()));
首先可以看到這里的cache是InternalCache類型,而且是在構(gòu)造函數(shù)的時(shí)候調(diào)用的。并且通過(guò)RealCall也可以看到,構(gòu)造這個(gè)過(guò)濾器的時(shí)候傳入的是我們構(gòu)造的OkHttpClient中設(shè)置的interanlCache,而當(dāng)我們用默認(rèn)方式構(gòu)造OkHttpClient的時(shí)候是不會(huì)創(chuàng)建緩存的,也就是internalCache=null的
public interface InternalCache {
Response get(Request request) throws IOException;
CacheRequest put(Response response) throws IOException;
void remove(Request request) throws IOException;
void update(Response cached, Response network);
void trackConditionalCacheHit();
void trackResponse(CacheStrategy cacheStrategy);
}
不出意外,InternalCache是一個(gè)接口,OkHttp充分貫徹了面向接口編程。接著查找OkHttp中哪個(gè)實(shí)現(xiàn)了或者說(shuō)使用了這個(gè)接口,對(duì)應(yīng)找到了Cache這個(gè)類。
public final class Cache implements Closeable, Flushable {
final InternalCache internalCache = new InternalCache() {
@Override public Response get(Request request) throws IOException {
return Cache.this.get(request);
}
};
@Nullable Response get(Request request) {
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
snapshot = cache.get(key);
if (snapshot == null) {
//沒(méi)拿到,返回null
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
//創(chuàng)建一個(gè)Entry,這里其實(shí)傳入的是CleanFiles數(shù)組的第一個(gè)(ENTRY_METADATA = 0)得到是頭信息,也就是key.0
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
//得到緩存構(gòu)建得到的response
Response response = entry.response(snapshot);
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
return response;
}
可以看到,Cache中實(shí)現(xiàn)了InternalCache這個(gè)接口,get()方法對(duì)應(yīng)調(diào)用的是Cache類中的get方法。所以現(xiàn)在就要看get方法了。
String key = key(request.url());
首先,通過(guò)這行代碼我們了解到,緩存的Key是和request的url直接相關(guān)的。這里通過(guò)url,得到了緩存的key。
final DiskLruCache cache;
=============================
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
snapshot = cache.get(key);
if (snapshot == null) {
//沒(méi)拿到,返回null
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
下面剛開(kāi)始看到的時(shí)候?qū)Ω鞣N變量是很難理解的,這里就先不要管,隨著后面分析的深入,會(huì)理解這里的snapshot變量。可以看到這里得到key后,又會(huì)走cache.get()方法,好吧,又要再進(jìn)入看了。首先要明白,這里的cache對(duì)應(yīng)的類型是DiskLruCache。
public synchronized Snapshot get(String key) throws IOException {
//總結(jié)來(lái)說(shuō)就是對(duì)journalFile文件的操作,有則刪除無(wú)用冗余的信息,構(gòu)建新文件,沒(méi)有則new一個(gè)新的
initialize();
//判斷是否關(guān)閉,如果緩存損壞了,會(huì)被關(guān)閉
checkNotClosed();
//檢查key是否滿足格式要求,正則表達(dá)式
validateKey(key);
//獲取key對(duì)應(yīng)的entry
Entry entry = lruEntries.get(key);
if (entry == null || !entry.readable) return null;
//獲取entry里面的snapshot的值
Snapshot snapshot = entry.snapshot();
if (snapshot == null) return null;
//有則計(jì)數(shù)器+1
redundantOpCount++;
//把這個(gè)內(nèi)容寫(xiě)入文檔中
journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n');
//判斷是否達(dá)清理?xiàng)l件
if (journalRebuildRequired()) {
executor.execute(cleanupRunnable);
}
return snapshot;
}
進(jìn)入到DisLruCache內(nèi)部,首先執(zhí)行的是initialize()
方法。
public synchronized void initialize() throws IOException {
//斷言,當(dāng)持有自己鎖的時(shí)候。繼續(xù)執(zhí)行,沒(méi)有持有鎖,直接拋異常
assert Thread.holdsLock(this);
//如果初始化過(guò),則直接跳出
if (initialized) {
return; // Already initialized.
}
// If a bkp file exists, use it instead.
//如果有journalFileBackup這個(gè)文件
if (fileSystem.exists(journalFileBackup)) {
// If journal file also exists just delete backup file.
//如果有journalFile這個(gè)文件
if (fileSystem.exists(journalFile)) {
//刪除journalFileBackup這個(gè)文件
fileSystem.delete(journalFileBackup);
} else {
//沒(méi)有journalFile這個(gè)文件,并且有journalFileBackup這個(gè)文件,則將journalFileBackup改名為journalFile
fileSystem.rename(journalFileBackup, journalFile);
}
}
//最后的結(jié)果只有兩種:1.什么都沒(méi)有2.有journalFile文件
// Prefer to pick up where we left off.
if (fileSystem.exists(journalFile)) {
//如果有journalFile文件
try {
readJournal();
processJournal();
//標(biāo)記初始化完成
initialized = true;
return;
} catch (IOException journalIsCorrupt) {
Platform.get().log(WARN, "DiskLruCache " + directory + " is corrupt: "
+ journalIsCorrupt.getMessage() + ", removing", journalIsCorrupt);
}
// The cache is corrupted, attempt to delete the contents of the directory. This can throw and
// we'll let that propagate out as it likely means there is a severe filesystem problem.
try {
//有緩存損壞導(dǎo)致異常,則刪除緩存目錄下所有文件
delete();
} finally {
closed = false;
}
}
//如果沒(méi)有則重新創(chuàng)建一個(gè)
rebuildJournal();
//標(biāo)記初始化完成,無(wú)論有沒(méi)有journal文件,initialized都會(huì)標(biāo)記為true,只執(zhí)行一遍
initialized = true;
}
總算不用再進(jìn)入看了,這里assert斷言保證這個(gè)方法是線程安全的。接著通過(guò)對(duì)initialized變量來(lái)判斷,如果初始化過(guò),則直接return。
//如果有journalFileBackup這個(gè)文件
if (fileSystem.exists(journalFileBackup)) {
// If journal file also exists just delete backup file.
//如果有journalFile這個(gè)文件
if (fileSystem.exists(journalFile)) {
//刪除journalFileBackup這個(gè)文件
fileSystem.delete(journalFileBackup);
} else {
//沒(méi)有journalFile這個(gè)文件,并且有journalFileBackup這個(gè)文件,則將journalFileBackup改名為journalFile
fileSystem.rename(journalFileBackup, journalFile);
}
}
這里首先說(shuō)明一下journalFile指的是日志文件,是對(duì)緩存一系列操作的記錄,不影響緩存的執(zhí)行流程。
可以看到這里有兩個(gè)文件journalFile和journalFileBackup,從名字上可以確定,一個(gè)是備份文件,一個(gè)是記錄文件,隨著后面的分析,會(huì)發(fā)現(xiàn)緩存中充分利用的兩個(gè)文件,這種形式,一個(gè)用于保存,一個(gè)用于編輯操作。
這里的判斷就很好理解了,如果有journalFileBackup這個(gè)文件,并且有journalFile這個(gè)文件,則刪除journalFileBackup這個(gè)沒(méi)用的文件;如果沒(méi)有journalFile但是有journalFileBackup這個(gè)文件,則將journalFileBackup命名為journalFile。最終可以得出,最后用于保存的其實(shí)是journalFile文件。這里執(zhí)行完后最后的結(jié)果只有兩種:1.什么都沒(méi)有2.有journalFile文件
if (fileSystem.exists(journalFile)) {
//如果有journalFile文件
try {
readJournal();
processJournal();
//標(biāo)記初始化完成
initialized = true;
return;
} catch (IOException journalIsCorrupt) {
Platform.get().log(WARN, "DiskLruCache " + directory + " is corrupt: "
+ journalIsCorrupt.getMessage() + ", removing", journalIsCorrupt);
}
// The cache is corrupted, attempt to delete the contents of the directory. This can throw and
// we'll let that propagate out as it likely means there is a severe filesystem problem.
try {
//有緩存損壞導(dǎo)致異常,則刪除緩存目錄下所有文件
delete();
} finally {
closed = false;
}
}
當(dāng)存在journalFile,執(zhí)行readJournal(),讀取journalFile文件。
private void readJournal() throws IOException {
//利用Okio讀取journalFile文件
BufferedSource source = Okio.buffer(fileSystem.source(journalFile));
try {
String magic = source.readUtf8LineStrict();
String version = source.readUtf8LineStrict();
String appVersionString = source.readUtf8LineStrict();
String valueCountString = source.readUtf8LineStrict();
String blank = source.readUtf8LineStrict();
//保證和默認(rèn)值相同
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+ valueCountString + ", " + blank + "]");
}
int lineCount = 0;
while (true) {
try {
//逐行讀取,并根據(jù)每行的開(kāi)頭,不同的狀態(tài)執(zhí)行不同的操作,主要就是往lruEntries里面add,或者remove
readJournalLine(source.readUtf8LineStrict());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
//日志操作的記錄數(shù)=總行數(shù)-lruEntries中實(shí)際add的行數(shù)
redundantOpCount = lineCount - lruEntries.size();
//source.exhausted()表示是否還多余字節(jié),如果沒(méi)有多余字節(jié),返回true,有多余字節(jié)返回false
// If we ended on a truncated line, rebuild the journal before appending to it.
if (!source.exhausted()) {
//如果有多余的字節(jié),則重新構(gòu)建下journal文件
rebuildJournal();
} else {
//獲取這個(gè)文件的Sink,以便Writer
journalWriter = newJournalWriter();
}
} finally {
Util.closeQuietly(source);
}
}
可以看到這里用到了使用OkHttp必須要依賴的庫(kù)Okio,這個(gè)庫(kù)內(nèi)部對(duì)輸入輸出流進(jìn)行了很多優(yōu)化,分幀讀取寫(xiě)入,幀還有池的概念,具體原理可以網(wǎng)上去學(xué)習(xí)。
String magic = source.readUtf8LineStrict();
String version = source.readUtf8LineStrict();
String appVersionString = source.readUtf8LineStrict();
String valueCountString = source.readUtf8LineStrict();
String blank = source.readUtf8LineStrict();
//保證和默認(rèn)值相同
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
|| !Integer.toString(valueCount).equals(valueCountString)
|| !"".equals(blank)) {
throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+ valueCountString + ", " + blank + "]");
}
這里利用Okio讀取journalFile,前面主要是逐行讀取一些參數(shù),進(jìn)行校驗(yàn),保證這些參數(shù)的正確性。
int lineCount = 0;
while (true) {
try {
//逐行讀取,并根據(jù)每行的開(kāi)頭,不同的狀態(tài)執(zhí)行不同的操作,主要就是往lruEntries里面add,或者remove
readJournalLine(source.readUtf8LineStrict());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
校驗(yàn)成功了,就進(jìn)行逐行讀取,所以這里需要看一下readJournalLine()方法。
private void readJournalLine(String line) throws IOException {
//記錄第一個(gè)空串的位置
int firstSpace = line.indexOf(' ');
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
//記錄第二個(gè)空串的位置
int secondSpace = line.indexOf(' ', keyBegin);
final String key;
if (secondSpace == -1) {
//如果中間沒(méi)有空串,則直接截取得到key
key = line.substring(keyBegin);
//如果解析出來(lái)的是"REMOVE skjdglajslkgjl"這樣以REMOVE開(kāi)頭
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
//移除這個(gè)key,lruEntries是LinkedHashMap
lruEntries.remove(key);
return;
}
} else {
//解析兩個(gè)空格間的字符串為key
key = line.substring(keyBegin, secondSpace);
}
//取出Entry對(duì)象
Entry entry = lruEntries.get(key);
//如果Enty對(duì)象為null
if (entry == null) {
//new一個(gè)Entry,put進(jìn)去
entry = new Entry(key);
lruEntries.put(key, entry);
}
//如果是“CLEAN 1 2”這樣的以CLAEN開(kāi)頭
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
//取第二個(gè)空格后面的字符串,parts變成[1,2]
String[] parts = line.substring(secondSpace + 1).split(" ");
//可讀
entry.readable = true;
//不被編輯
entry.currentEditor = null;
//設(shè)置長(zhǎng)度
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
//如果是“DIRTY lskdjfkl”這樣以DIRTY開(kāi)頭,新建一個(gè)Editor
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
//如果是“READ slkjl”這樣以READ開(kāi)頭,不需要做什么事
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " + line);
}
}
這里一開(kāi)始可能比較難以理解,說(shuō)明一下journalFile每一行的保存格式是這樣的:REMOVE sdkjlg 2341 1234
第一個(gè)空格前面代表這條日志的操作內(nèi)容,后面的第一個(gè)個(gè)保存的是key,后面這兩個(gè)內(nèi)容根據(jù)前面的操作存入緩存內(nèi)容對(duì)應(yīng)的length...
if (secondSpace == -1) {
//如果中間沒(méi)有空串,則直接截取得到key
key = line.substring(keyBegin);
//如果解析出來(lái)的是"REMOVE skjdglajslkgjl"這樣以REMOVE開(kāi)頭
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
//移除這個(gè)key,lruEntries是LinkedHashMap
lruEntries.remove(key);
return;
}
} else {
//解析兩個(gè)空格間的字符串為key
key = line.substring(keyBegin, secondSpace);
}
如果沒(méi)有第二個(gè)空格,那么數(shù)據(jù)格式就是這樣的REMOVE skjdglajslkgjl
截取第一個(gè)空格后面的內(nèi)容作為key,如果是以REMOVE開(kāi)頭,則從lruEntries中移除這個(gè)key對(duì)應(yīng)的緩存。
final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true);
這里說(shuō)明一下,使用一個(gè)LinkedHashMap保存的。
如果有第二個(gè)空格,則還是去第一個(gè)和第二個(gè)空格之間的內(nèi)容當(dāng)做key。
//取出Entry對(duì)象
Entry entry = lruEntries.get(key);
//如果Enty對(duì)象為null
if (entry == null) {
//new一個(gè)Entry,put進(jìn)去
entry = new Entry(key);
lruEntries.put(key, entry);
}
并且嘗試取這個(gè)key對(duì)應(yīng)的Entry,如果沒(méi)有,則new一個(gè)put進(jìn)入。
//如果是“CLEAN jklldsg 2 5”這樣的以CLAEN開(kāi)頭
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
//取第二個(gè)空格后面的字符串,parts變成[1,2]
String[] parts = line.substring(secondSpace + 1).split(" ");
//可讀
entry.readable = true;
//不被編輯
entry.currentEditor = null;
//設(shè)置長(zhǎng)度
entry.setLengths(parts);
}
CLEAN jklldsg 2 5如果是以CLEAN開(kāi)頭的話,則將取出key后面的數(shù)組,設(shè)置可讀,不可編輯,設(shè)置entry的長(zhǎng)度。這里先說(shuō)明一下Entry類。
private final class Entry {
final String key;
/** Lengths of this entry's files. */
final long[] lengths;
//用于保存持久數(shù)據(jù),作用是讀取 最后的格式:key.0
final File[] cleanFiles;
//用于保存編輯的臨時(shí)數(shù)據(jù),作用是寫(xiě),最后的格式:key.0.tmp
final File[] dirtyFiles;
}
我的理解是,Entry中有兩個(gè)數(shù)組,cleanFile是用于保存持久性數(shù)據(jù),用于讀取,dirtyFiles是用于進(jìn)行編輯,當(dāng)編輯完成后會(huì)執(zhí)行commit操作,將dirtyFile賦值給cleanFile。length適用于保存Entry中每個(gè)數(shù)組對(duì)應(yīng)的file的數(shù)量。
所以當(dāng)CLEAN jklldsg 2 5如果是以CLEAN開(kāi)頭的話,cleanFiles對(duì)應(yīng)的size就是2,dirtyFiles對(duì)應(yīng)的數(shù)量是5(默認(rèn)都是2個(gè))
else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
//如果是“DIRTY lskdjfkl”這樣以DIRTY開(kāi)頭,新建一個(gè)Editor
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
//如果是“READ slkjl”這樣以READ開(kāi)頭,不需要做什么事
// This work was already done by calling lruEntries.get().
} else {
throw new IOException("unexpected journal line: " + line);
}
上面理解了,后面其實(shí)也就對(duì)應(yīng)很好理解了,如果是以DIRTY開(kāi)頭,則新建一個(gè)Editor表示這個(gè)Entry可以編輯。如果是READ開(kāi)頭,則不需要做任何事。
到此結(jié)束了對(duì)readJournalLine()方法的分析,總結(jié)一下這個(gè)方法的作用:逐行讀取,并根據(jù)每行的開(kāi)頭,不同的狀態(tài)執(zhí)行不同的操作,主要就是往lruEntries里面add,或者remove。接著返回到readJournal()方法中。
while (true) {
try {
//逐行讀取,并根據(jù)每行的開(kāi)頭,不同的狀態(tài)執(zhí)行不同的操作,主要就是往lruEntries里面add,或者remove
readJournalLine(source.readUtf8LineStrict());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
可以看到這里,利用lineCount記錄讀取的行數(shù)。
//日志中操作的記錄數(shù)=總行數(shù)-lruEntries中實(shí)際add的行數(shù)
redundantOpCount = lineCount - lruEntries.size();
//source.exhausted()表示是否還多余字節(jié),如果沒(méi)有多余字節(jié),返回true,有多余字節(jié)返回false
// If we ended on a truncated line, rebuild the journal before appending to it.
if (!source.exhausted()) {
//如果有多余的字節(jié),則重新構(gòu)建下journal文件
rebuildJournal();
} else {
//獲取這個(gè)文件的Sink,以便Writer
journalWriter = newJournalWriter();
}
讀取完畢后會(huì)計(jì)算日志中操作的記錄數(shù),日志中操作的記錄數(shù)=讀取的總行數(shù)-lruEntries中實(shí)際保存的行數(shù)。
接下來(lái)source.exhausted()是表示是否還多余字節(jié),如果沒(méi)有多余字節(jié),返回true,有多余字節(jié)返回false,如果有多余的字節(jié)則需要執(zhí)行rebuildJournal(),沒(méi)有則獲得這個(gè)文件的Sink,用于Write操作。
synchronized void rebuildJournal() throws IOException {
if (journalWriter != null) {
journalWriter.close();
}
BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp));
try {
//寫(xiě)入校驗(yàn)信息
writer.writeUtf8(MAGIC).writeByte('\n');
writer.writeUtf8(VERSION_1).writeByte('\n');
writer.writeDecimalLong(appVersion).writeByte('\n');
writer.writeDecimalLong(valueCount).writeByte('\n');
writer.writeByte('\n');
//利用剛才逐行讀的內(nèi)容按照格式重新構(gòu)建
for (Entry entry : lruEntries.values()) {
if (entry.currentEditor != null) {
writer.writeUtf8(DIRTY).writeByte(' ');
writer.writeUtf8(entry.key);
writer.writeByte('\n');
} else {
writer.writeUtf8(CLEAN).writeByte(' ');
writer.writeUtf8(entry.key);
entry.writeLengths(writer);
writer.writeByte('\n');
}
}
} finally {
writer.close();
}
//用新構(gòu)建的journalFileTmp替換當(dāng)前的journalFile文件
if (fileSystem.exists(journalFile)) {
fileSystem.rename(journalFile, journalFileBackup);
}
fileSystem.rename(journalFileTmp, journalFile);
fileSystem.delete(journalFileBackup);
journalWriter = newJournalWriter();
hasJournalErrors = false;
mostRecentRebuildFailed = false;
}
可以看到這里主要是將lruEntries中保存的內(nèi)容逐行寫(xiě)成一個(gè)journalFileTmp,將新構(gòu)建的journalFileTmp替換當(dāng)前包含冗余信息的journalFile文件,達(dá)到重新構(gòu)建的效果。
到這里readJournal()方法分析完了,總結(jié)下這個(gè)方法的作用:主要是讀取journalFile,根據(jù)日志文件中的日志信息,過(guò)濾無(wú)用冗余的信息,有冗余的則重新構(gòu)建,最后保證journalFile日志文件沒(méi)有冗余信息。
執(zhí)行完readJournal()方法,回到initialize()方法中。
try {
readJournal();
processJournal();
//標(biāo)記初始化完成
initialized = true;
return;
} catch (IOException journalIsCorrupt) {
Platform.get().log(WARN, "DiskLruCache " + directory + " is corrupt: "
+ journalIsCorrupt.getMessage() + ", removing", journalIsCorrupt);
}
這里需要看一下processJournal()方法
private void processJournal() throws IOException {
//刪除journalFileTmp文件
fileSystem.delete(journalFileTmp);
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
Entry entry = i.next();
if (entry.currentEditor == null) {
//表明數(shù)據(jù)是CLEAN,循環(huán)記錄SIZE
for (int t = 0; t < valueCount; t++) {
size += entry.lengths[t];
}
} else {
//表明數(shù)據(jù)是DIRTY,刪除
entry.currentEditor = null;
for (int t = 0; t < valueCount; t++) {
fileSystem.delete(entry.cleanFiles[t]);
fileSystem.delete(entry.dirtyFiles[t]);
}
//移除Entry
i.remove();
}
}
}
可以看到,這里刪除了剛才創(chuàng)建的journalFileTmp文件,并且遍歷lruEntries,記錄不可編輯的數(shù)據(jù)長(zhǎng)度size(也就是CLEAN),刪除DIRTY數(shù)據(jù),也就是只保留CLEAN持久性數(shù)據(jù),刪除編輯的數(shù)據(jù)。
try {
readJournal();
processJournal();
//標(biāo)記初始化完成
initialized = true;
return;
} catch (IOException journalIsCorrupt) {
Platform.get().log(WARN, "DiskLruCache " + directory + " is corrupt: "
+ journalIsCorrupt.getMessage() + ", removing", journalIsCorrupt);
}
可以看到到這里,接下來(lái)的就是將initialized標(biāo)記為true,表示初始化完成。到這里其實(shí)initialize已經(jīng)完成了,繼續(xù)看initialize()方法。
后面就比較簡(jiǎn)單了,當(dāng)沒(méi)有journalFile,則會(huì)調(diào)用我們剛才分析過(guò)的方法rebuildJournal()重新創(chuàng)建一個(gè)日志文件,仍然將initialized標(biāo)記為true,說(shuō)明無(wú)論有沒(méi)有journal文件,initialized都會(huì)標(biāo)記為true,只執(zhí)行一遍。
到這里總算將initialize()分析完了,這里總結(jié)一下這個(gè)方法:
1.這個(gè)方法線程安全
2.如果初始化過(guò)了,則什么都不干,只初始化一遍
3.如果有journalFile日志文件,則對(duì)journalFile文件和lruEntries進(jìn)行初始化操作,主要是刪除冗余信息,和DIRTY信息。
4.沒(méi)有則構(gòu)建一個(gè)journalFile文件。
到這initialize()方法總算分析完了,接下來(lái)回到get()方法中,剩下的其實(shí)就容易點(diǎn)了。
接下來(lái)這些都沒(méi)有什么重要的地方,我注釋都寫(xiě)的很清楚,總結(jié)一下get()方法的主要操作:
1.初始化日志文件和lruEntries
2.檢查保證key正確后獲取緩存中保存的Entry。
3.操作計(jì)數(shù)器+1
4.往日志文件中寫(xiě)入這次的READ操作。
5.根據(jù)redundantOpCount判斷是否需要清理日志信息。
6.需要?jiǎng)t開(kāi)啟線程清理。
7.不需要?jiǎng)t返回緩存。
這里看一下兩個(gè)地方,第一個(gè)journalRebuildRequired()用于判斷是否需要清理緩存。
boolean journalRebuildRequired() {
final int redundantOpCompactThreshold = 2000;
//清理的條件是當(dāng)前redundantOpCount大于2000,并且redundantOpCount的值大于linkedList里面的size
return redundantOpCount >= redundantOpCompactThreshold
&& redundantOpCount >= lruEntries.size();
}
可以看到清理的條件是當(dāng)前redundantOpCount大于2000,并且redundantOpCount的值大于linkedList里面的size。
下面一個(gè)需要看的地方就是清理線程cleanupRunnable。
private final Runnable cleanupRunnable = new Runnable() {
public void run() {
synchronized (DiskLruCache.this) {
//如果沒(méi)有初始化或者已經(jīng)關(guān)閉了,則不需要清理,這里注意|和||的區(qū)別,|會(huì)兩個(gè)條件都檢查
if (!initialized | closed) {
return; // Nothing to do
}
try {
//清理
trimToSize();
} catch (IOException ignored) {
mostRecentTrimFailed = true;
}
try {
if (journalRebuildRequired()) {
//如果還要清理,重新構(gòu)建
rebuildJournal();
//計(jì)數(shù)器置0
redundantOpCount = 0;
}
} catch (IOException e) {
//如果拋異常了,設(shè)置最近的一次構(gòu)建失敗
mostRecentRebuildFailed = true;
journalWriter = Okio.buffer(Okio.blackhole());
}
}
}
};
這里先總結(jié)一下這里的操作流程:
1.如果還沒(méi)有初始化或者緩存關(guān)閉了,則不清理。
2.執(zhí)行清理操作。
3.如果清理完了還是判斷后還需要清理,只能重新構(gòu)建日志文件,并且日志記錄器記0。
這里主要就需要看一下清理操作trimToSize()
void trimToSize() throws IOException {
//遍歷直到滿足大小
while (size > maxSize) {
Entry toEvict = lruEntries.values().iterator().next();
removeEntry(toEvict);
}
mostRecentTrimFailed = false;
}
可以看到這里就是一個(gè)遍歷,知道滿足maxSize條件,這里的maxSize是可以設(shè)置的。
boolean removeEntry(Entry entry) throws IOException {
if (entry.currentEditor != null) {
//結(jié)束editor
entry.currentEditor.detach(); // Prevent the edit from completing normally.
}
for (int i = 0; i < valueCount; i++) {
//清除用于保存文件的cleanFiles
fileSystem.delete(entry.cleanFiles[i]);
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
//計(jì)數(shù)器加1
redundantOpCount++;
//增加一條刪除日志
journalWriter.writeUtf8(REMOVE).writeByte(' ').writeUtf8(entry.key).writeByte('\n');
//移除entry
lruEntries.remove(entry.key);
//如果需要重新清理一下,邊界情況
if (journalRebuildRequired()) {
//清理
executor.execute(cleanupRunnable);
}
return true;
}
這里的執(zhí)行流程:
1.停止編輯操作
2.清楚用于保存的cleanFiles
3.增加一條清楚日志記錄,計(jì)數(shù)器+1
4.移除對(duì)應(yīng)key的entry
5.由于增加了一條日志,判斷是否需要清理,不然可能會(huì)越清越多...
至此,get()方法終于分析完成了,接著就要返回Cache中的get()方法繼續(xù)看。
@Nullable Response get(Request request) {
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
snapshot = cache.get(key);
if (snapshot == null) {
//沒(méi)拿到,返回null
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
//創(chuàng)建一個(gè)Entry,這里其實(shí)傳入的是CleanFiles數(shù)組的第一個(gè)(ENTRY_METADATA = 0)得到是頭信息,也就是key.0
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
//得到緩存構(gòu)建得到的response
Response response = entry.response(snapshot);
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
return response;
}
這里一樣,先總結(jié)一下get()方法的具體流程。
1.通過(guò)執(zhí)行DiskLruCache的get方法拿到snapshot信息。
2.通過(guò)拿到的snapshot信息,取cleanFiles[0]中保存的頭信息,構(gòu)建頭相關(guān)的信息的Entry.
3.通過(guò)snapshot中的cleanFiles[1]構(gòu)建body信息,最終構(gòu)建成緩存中保存的Response。
4.返回緩存中保存的Resposne。
可以看到這里的重點(diǎn)是先構(gòu)建header,再構(gòu)建body,最后組合成Resposne。對(duì)應(yīng)的兩個(gè)流程這里分析一下,
try {
//創(chuàng)建一個(gè)Entry,這里其實(shí)傳入的是CleanFiles數(shù)組的第一個(gè)(ENTRY_METADATA = 0)得到是頭信息,也就是key.0
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
可以看到,這里通過(guò)得到的snapshot.getSource構(gòu)建了Entry(這個(gè)Entry是Cache的內(nèi)部類,不是DiskLruCache的內(nèi)部類)。這里注意一個(gè)地方,這里的ENTRY_METADATA = 0。
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 Source getSource(int index) {
return sources[index];
}
}
可以看到這里的getSource其實(shí)就是返回Source數(shù)組中的元素,而Source數(shù)組是在Snapshot的構(gòu)造函數(shù)的時(shí)候賦值,所以對(duì)應(yīng)的可以找構(gòu)造Snapshot的地方。
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++) {
//可以看到這里其實(shí)是將cleanFiles傳給了sources
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;
}
}
看到這個(gè)方法,其實(shí)應(yīng)該注意到這個(gè)就是我們?cè)谡{(diào)用DiskLruCache中的get()方法時(shí)最后返回Snapshot調(diào)用的方法,具體下方代碼貼出了,這時(shí)候可以看到source數(shù)組其實(shí)是將entry中的cleanfile數(shù)組對(duì)應(yīng)的保存到source數(shù)組中,這也驗(yàn)證了我們前面說(shuō)的clean數(shù)組是用來(lái)保存持久性數(shù)據(jù),也就是真正用來(lái)存東西的地方,而且記得前面提到ENTRY_METADATA = 0,所以對(duì)應(yīng)的取的也是clean數(shù)組中的第一個(gè)文件,也驗(yàn)證了前面說(shuō)的clean數(shù)組分兩部分,第一部分保存頭,第二部分保存body
public synchronized Snapshot get(String key) throws IOException {
...
Snapshot snapshot = entry.snapshot();
...
}
getSource看完了,這時(shí)候來(lái)看一下Cache中Entry這個(gè)內(nèi)部類,注意不是DiskLruCache中的Entry
Entry(Source in) throws IOException {
try {
BufferedSource source = Okio.buffer(in);
url = source.readUtf8LineStrict();
requestMethod = source.readUtf8LineStrict();
//得到cleanfiles[0]來(lái)構(gòu)建頭信息
Headers.Builder varyHeadersBuilder = new Headers.Builder();
int varyRequestHeaderLineCount = readInt(source);
for (int i = 0; i < varyRequestHeaderLineCount; i++) {
varyHeadersBuilder.addLenient(source.readUtf8LineStrict());
}
varyHeaders = varyHeadersBuilder.build();
StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
protocol = statusLine.protocol;
code = statusLine.code;
message = statusLine.message;
Headers.Builder responseHeadersBuilder = new Headers.Builder();
int responseHeaderLineCount = readInt(source);
for (int i = 0; i < responseHeaderLineCount; i++) {
responseHeadersBuilder.addLenient(source.readUtf8LineStrict());
}
String sendRequestMillisString = responseHeadersBuilder.get(SENT_MILLIS);
String receivedResponseMillisString = responseHeadersBuilder.get(RECEIVED_MILLIS);
responseHeadersBuilder.removeAll(SENT_MILLIS);
responseHeadersBuilder.removeAll(RECEIVED_MILLIS);
sentRequestMillis = sendRequestMillisString != null
? Long.parseLong(sendRequestMillisString)
: 0L;
receivedResponseMillis = receivedResponseMillisString != null
? Long.parseLong(receivedResponseMillisString)
: 0L;
//構(gòu)建了header
responseHeaders = responseHeadersBuilder.build();
if (isHttps()) {
String blank = source.readUtf8LineStrict();
if (blank.length() > 0) {
throw new IOException("expected \"\" but was \"" + blank + "\"");
}
String cipherSuiteString = source.readUtf8LineStrict();
CipherSuite cipherSuite = CipherSuite.forJavaName(cipherSuiteString);
List<Certificate> peerCertificates = readCertificateList(source);
List<Certificate> localCertificates = readCertificateList(source);
TlsVersion tlsVersion = !source.exhausted()
? TlsVersion.forJavaName(source.readUtf8LineStrict())
: TlsVersion.SSL_3_0;
handshake = Handshake.get(tlsVersion, cipherSuite, peerCertificates, localCertificates);
} else {
handshake = null;
}
} finally {
in.close();
}
}
這里通過(guò)Entry的構(gòu)造方法更能說(shuō)明clean數(shù)組中的第一項(xiàng)是用來(lái)保存header信息的,從代碼中可以看到利用Header.builder對(duì)傳入進(jìn)來(lái)的Source(也就是clean[0])進(jìn)行構(gòu)建,最后利用build()方法構(gòu)建了header信息。
頭構(gòu)建完了,現(xiàn)在就需要找構(gòu)建body的地方,因?yàn)槭O碌拇a只剩下
//得到緩存構(gòu)建得到的response
Response response = entry.response(snapshot);
//Entry內(nèi)部類
public Response response(DiskLruCache.Snapshot snapshot) {
String contentType = responseHeaders.get("Content-Type");
String contentLength = responseHeaders.get("Content-Length");
Request cacheRequest = new Request.Builder()
.url(url)
.method(requestMethod, null)
.headers(varyHeaders)
.build();
return new Response.Builder()
.request(cacheRequest)
.protocol(protocol)
.code(code)
.message(message)
.headers(responseHeaders)
.body(new CacheResponseBody(snapshot, contentType, contentLength))
.handshake(handshake)
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(receivedResponseMillis)
.build();
}
大體上一看,這個(gè)方法的作用基本上就是利用Resposne.builder構(gòu)建緩存中的Resposne了,但是沒(méi)有找到明顯的寫(xiě)入Body的地方,唯一由body的就是
.body(new CacheResponseBody(snapshot, contentType, contentLength))
,所以只能進(jìn)入CacheResponseBody的構(gòu)造函數(shù)中。
CacheResponseBody(final DiskLruCache.Snapshot snapshot,
String contentType, String contentLength) {
this.snapshot = snapshot;
this.contentType = contentType;
this.contentLength = contentLength;
//這里ENTRY_BODY=1,同樣拿的是CleanFiles數(shù)組,構(gòu)建Responsebody
Source source = snapshot.getSource(ENTRY_BODY);
bodySource = Okio.buffer(new ForwardingSource(source) {
@Override public void close() throws IOException {
snapshot.close();
super.close();
}
});
}
可以看到終于發(fā)現(xiàn)了和剛才Header一樣的代碼,這里ENTRY_BODY=1,對(duì)應(yīng)的還是取source數(shù)組中的下標(biāo)為1的地方,構(gòu)建body。到現(xiàn)在可以看出來(lái),clean數(shù)組的0對(duì)應(yīng)保存的Header信息,1對(duì)應(yīng)保存的BODY信息。
這里分析完構(gòu)建header和body得到對(duì)應(yīng)的緩存的Resposne后,對(duì)應(yīng)非常長(zhǎng)長(zhǎng)長(zhǎng)的從緩存中拿緩存的Resposne流程終于結(jié)束。
其實(shí)到這里,緩存的主要思想其實(shí)已經(jīng)理解大概了后面的的其實(shí)就比較好理解了。這里get()方法結(jié)束了,也終于要回到CacheInterceptor的主要方法中了。這里再放一遍代碼,因?yàn)榉先ヌL(zhǎng)了。。。
@Override public Response intercept(Chain chain) throws IOException {
//默認(rèn)cache為null,可以配置cache,不為空嘗試獲取緩存中的response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//根據(jù)response,time,request創(chuàng)建一個(gè)緩存策略,用于判斷怎樣使用緩存
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
//如果緩存策略中禁止使用網(wǎng)絡(luò),并且緩存又為空,則構(gòu)建一個(gè)Resposne直接返回,注意返回碼=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 we don't need the network, we're done.
//不使用網(wǎng)絡(luò),但是又緩存,直接返回緩存
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//直接走后續(xù)過(guò)濾器
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 we have a cache response too, then we're doing a conditional get.
//當(dāng)緩存響應(yīng)和網(wǎng)絡(luò)響應(yīng)同時(shí)存在的時(shí)候,選擇用哪個(gè)
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//如果返回碼是304,客戶端有緩沖的文檔并發(fā)出了一個(gè)條件性的請(qǐng)求(一般是提供If-Modified-Since頭表示客戶
// 只想比指定日期更新的文檔)。服務(wù)器告訴客戶,原來(lái)緩沖的文檔還可以繼續(xù)使用。
//則使用緩存的響應(yīng)
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());
}
}
//使用網(wǎng)絡(luò)響應(yīng)
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//所以默認(rèn)創(chuàng)建的OkHttpClient是沒(méi)有緩存的
if (cache != null) {
//將響應(yīng)緩存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
//緩存Resposne的Header信息
CacheRequest cacheRequest = cache.put(response);
//緩存body
return cacheWritingResponse(cacheRequest, response);
}
//只能緩存GET....不然移除request
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
這里就可以分析CacheInterceptor的主要流程了。
1.通過(guò)Request嘗試到Cache中拿緩存(里面非常多流程),當(dāng)然前提是OkHttpClient中配置了緩存,默認(rèn)是不支持的。
2.根據(jù)response,time,request創(chuàng)建一個(gè)緩存策略,用于判斷怎樣使用緩存。
3.如果緩存策略中設(shè)置禁止使用網(wǎng)絡(luò),并且緩存又為空,則構(gòu)建一個(gè)Resposne直接返回,注意返回碼=504
4.緩存策略中設(shè)置不使用網(wǎng)絡(luò),但是又緩存,直接返回緩存
5.接著走后續(xù)過(guò)濾器的流程,chain.proceed(networkRequest)
6.當(dāng)緩存存在的時(shí)候,如果網(wǎng)絡(luò)返回的Resposne為304,則使用緩存的Resposne。
7.構(gòu)建網(wǎng)絡(luò)請(qǐng)求的Resposne
8.當(dāng)在OKHttpClient中配置了緩存,則將這個(gè)Resposne緩存起來(lái)。
9.緩存起來(lái)的步驟也是先緩存header,再緩存body。
10.返回Resposne。
這里要注意的是2,9兩個(gè)點(diǎn)。其中2對(duì)應(yīng)的是CacheStrategy這個(gè)類,里面主要涉及Http協(xié)議中緩存的相關(guān)設(shè)置,具體的我也沒(méi)太搞明白,準(zhǔn)備入手一本Http的書(shū)好好研究研究。但是這里不影響理解主要流程。
下面就是對(duì)9,也就是存緩存這個(gè)步驟的分析了,其實(shí)前面的取緩存的分析結(jié)束后,這里對(duì)存緩存不難猜測(cè)其實(shí)是想對(duì)應(yīng)的,也就比較好理解了,對(duì)應(yīng)的大體應(yīng)該是header存入clean[0],body存入clean[1]。這里詳細(xì)看一下。
if (cache != null) {
//將響應(yīng)緩存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
//緩存Resposne的Header信息
CacheRequest cacheRequest = cache.put(response);
//緩存body
return cacheWritingResponse(cacheRequest, response);
}
//只能緩存GET....不然移除request
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
當(dāng)可以緩存的時(shí)候,這里用了cache.put(response)方法。
@Nullable CacheRequest put(Response response) {
String requestMethod = response.request().method();
if (HttpMethod.invalidatesCache(response.request().method())) {
//OKhttp只能緩存GET請(qǐng)求!。。。
try {
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
if (!requestMethod.equals("GET")) {
//OKhttp只能緩存GET請(qǐng)求!。。。
// 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;
}
//緩存了Header信息
entry.writeTo(editor);
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
可以看到這里先有幾種可能返回null,也就是對(duì)應(yīng)的不能緩存,這里會(huì)驚訝的發(fā)現(xiàn)!OkHttpClient源碼中支持GET形式的緩存。
if (!requestMethod.equals("GET")) {
//OKhttp只能緩存GET請(qǐng)求!。。。
// 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;
}
public static boolean invalidatesCache(String method) {
return method.equals("POST")
|| method.equals("PATCH")
|| method.equals("PUT")
|| method.equals("DELETE")
|| method.equals("MOVE"); // WebDAV
}
通過(guò)注釋其實(shí)也可以看到,這里okHttp的開(kāi)發(fā)者認(rèn)為,從效率角度考慮,最好不要支持POST請(qǐng)求的緩存,暫時(shí)只要支持GET形式的緩存。(如果需要支持,對(duì)應(yīng)的其實(shí)也就是修改源碼,將這里的判斷給刪除,其實(shí)還有一處判斷,具體方法Google,Baidu)
//緩存了Header信息
entry.writeTo(editor);
//Entry的writeTo方法===============================
public void writeTo(DiskLruCache.Editor editor) throws IOException {
//往dirty中寫(xiě)入header信息,ENTRY_METADATA=0,所以是dirtyFiles[0]
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();
}
對(duì)應(yīng)寫(xiě)緩存的地方可以看到調(diào)用了Entry的writeTo方法,這里別看那么長(zhǎng),其實(shí)主要看到一行代碼就了解了這個(gè)方法的功能了。
//往dirty中寫(xiě)入header信息,ENTRY_METADATA=0,所以是dirtyFiles[0]
BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));
還是原來(lái)的配方,還是原來(lái)的味道,又看到了剛才的參數(shù)ENTRY_METADATA=0,可以看到這里對(duì)應(yīng)的其實(shí)就是往dirtyFiles[0]中寫(xiě)入header信息,這里其實(shí)可以根據(jù)前面的分析對(duì)應(yīng),dirty是用于保存編輯更新等不是持久的數(shù)據(jù),而對(duì)應(yīng)的0對(duì)應(yīng)的header,1對(duì)應(yīng)的body。
//緩存了Header信息
entry.writeTo(editor);
return new CacheRequestImpl(editor);
寫(xiě)完header后,繼續(xù)找寫(xiě)body的地方,這里返回了一個(gè)CacheRequestImpl對(duì)象,一定不要忽略,不然就找不到寫(xiě)body的地方了。
CacheRequestImpl(final DiskLruCache.Editor editor) {
this.editor = editor;
//ENTRY_BODY = 1
this.cacheOut = editor.newSink(ENTRY_BODY);
this.body = new ForwardingSink(cacheOut) {
@Override public void close() throws IOException {
synchronized (Cache.this) {
if (done) {
return;
}
done = true;
writeSuccessCount++;
}
super.close();
editor.commit();
}
};
}
看到ENTRY_BODY就放心, 這里對(duì)應(yīng)的ENTRY_BODY=1對(duì)應(yīng)的就是數(shù)組的第二個(gè)位置。那到了數(shù)據(jù)源,接著就要找寫(xiě)入的地方,這里還要注意一個(gè)地方editor.commit();
//CacheIntercetor中==========================
//緩存Resposne的Header信息
CacheRequest cacheRequest = cache.put(response);
//緩存body
return cacheWritingResponse(cacheRequest, response);
可以看到返回了一個(gè)CacheRequestImpl對(duì)象后,最終執(zhí)行了一個(gè)cacheWritingResponse方法。
private Response cacheWritingResponse(final CacheRequest cacheRequest, Response response)
throws IOException {
// Some apps return a null body; for compatibility we treat that like a null cache request.
if (cacheRequest == null) return response;
Sink cacheBodyUnbuffered = cacheRequest.body();
if (cacheBodyUnbuffered == null) return response;
//獲得body
final BufferedSource source = response.body().source();
final BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered);
Source cacheWritingSource = new Source() {
boolean cacheRequestClosed;
@Override public long read(Buffer sink, long byteCount) throws IOException {
long bytesRead;
try {
bytesRead = source.read(sink, byteCount);
} catch (IOException e) {
if (!cacheRequestClosed) {
cacheRequestClosed = true;
cacheRequest.abort(); // Failed to write a complete cache response.
}
throw e;
}
if (bytesRead == -1) {
if (!cacheRequestClosed) {
cacheRequestClosed = true;
cacheBody.close(); // The cache response is complete!
}
return -1;
}
//讀的時(shí)候會(huì)將body寫(xiě)入
sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead);
cacheBody.emitCompleteSegments();
return bytesRead;
}
@Override public Timeout timeout() {
return source.timeout();
}
@Override public void close() throws IOException {
if (!cacheRequestClosed
&& !discard(this, HttpCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
cacheRequestClosed = true;
//關(guān)閉的時(shí)候會(huì)執(zhí)行commit操作,最終合并header和body,完成緩存
cacheRequest.abort();
}
source.close();
}
};
String contentType = response.header("Content-Type");
long contentLength = response.body().contentLength();
return response.newBuilder()
.body(new RealResponseBody(contentType, contentLength, Okio.buffer(cacheWritingSource)))
.build();
}
這里分析一下主要主要流程。
1.獲得Resposne中的body
//獲得body
final BufferedSource source = response.body().source();
2.將Resposne中獲得的body寫(xiě)入緩存中,也就是剛在拿到的dirtyfile[1]
//讀的時(shí)候會(huì)將body寫(xiě)入
sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead);
可以看到這里bytesRead就是讀的Body,最后利用sink.copyTo,寫(xiě)入cacheBody.buffer()中,也就是剛在拿到的dirtyfile[1]。
3.在close中會(huì)執(zhí)行abort操作,對(duì)應(yīng)的里面會(huì)執(zhí)行commit的操作,會(huì)將dirtyfile寫(xiě)入cleanfile中,完成持久化保存。
@Override public void close() throws IOException {
if (!cacheRequestClosed
&& !discard(this, HttpCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
cacheRequestClosed = true;
//關(guān)閉的時(shí)候會(huì)執(zhí)行commit操作,最終合并header和body,完成緩存
cacheRequest.abort();
}
source.close();
}
可以看到這里再關(guān)閉時(shí)會(huì)執(zhí)行abort方法。
@Override public void abort() {
synchronized (Cache.this) {
if (done) {
return;
}
done = true;
writeAbortCount++;
}
Util.closeQuietly(cacheOut);
try {
editor.abort();
} catch (IOException ignored) {
}
}
對(duì)應(yīng)執(zhí)行了editor的abort()方法。
public void abort() throws IOException {
synchronized (DiskLruCache.this) {
if (done) {
throw new IllegalStateException();
}
if (entry.currentEditor == this) {
completeEdit(this, false);
}
done = true;
}
}
可以看到這里執(zhí)行了completeEdite方法。
synchronized void completeEdit(Editor editor, boolean success) throws IOException {
...
for (int i = 0; i < valueCount; i++) {
File dirty = entry.dirtyFiles[i];
if (success) {
if (fileSystem.exists(dirty)) {
File clean = entry.cleanFiles[i];
fileSystem.rename(dirty, clean);
long oldLength = entry.lengths[i];
long newLength = fileSystem.size(clean);
entry.lengths[i] = newLength;
size = size - oldLength + newLength;
}
} else {
fileSystem.delete(dirty);
}
}
...
}
這里刪點(diǎn)代碼吧,貼的代碼太多了,這里只放了最重要的一條,可以看到將dirtyFiles數(shù)組保存賦值到cleanFiles數(shù)組中,完成了最終的持久化保存。
數(shù)一下這里的重點(diǎn)吧
1.緩存中是有日志文件用于保存操作記錄
2.緩存中的Entry有用CleanFiles和DirtyFiles,其中Clean是用于保存持久性數(shù)據(jù)的,也就是真正保存數(shù)據(jù)的地方,Dirty是用于保存編輯過(guò)程中的數(shù)據(jù)的。
3.CleanFiles[]大小為2,第一個(gè)保存Header,第二個(gè)保存Body,最終保存緩存。
到此。。。結(jié)束了。。。沒(méi)有結(jié)束語(yǔ)。