OkHttp深入學習(三)——Cache
Cache.class
該物件擁有一個DiskLruCache引用。 private final DiskLruCache cache; Cache()@Cache.classCache構造器接受兩個引數,意味著如果我們想要建立一個快取必須指定快取檔案儲存的目錄和快取檔案的最大值。下面看兩個常用方法,get()&put()。 get()@Cache.classpublic Cache(File directory, long maxSize) { this(directory, maxSize, FileSystem.SYSTEM); } Cache(File directory, long maxSize, FileSystem fileSystem) { this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize); }
1、Util.md5Hex(request.url().toString());將客戶的請求的url換成成32個字元的MD5字串 2、等價於DiskLruCache.Snapshot = DiskLruCache.get(String)利用前面得到的key從DiskLruCache中獲取到對應的DiskLruCache.Snapshot。該方法底層實現稍後我們看DiskLruCache的程式碼 3、利用前面的Snapshot建立一個Entry物件。Entry是Cache的一個內部類,儲存的內容是響應的Http資料包Header部分的資料。snapshot.getSource得到的是一個Source物件。Response get(Request request) { String key = urlToKey(request); //note 1 DiskLruCache.Snapshot snapshot; Entry entry; snapshot = cache.get(key); //note 2 if (snapshot == null) { return null; } entry = new Entry(snapshot.getSource(ENTRY_METADATA)); //note 3 getEntry Response response = entry.response(snapshot); //note4 if (!entry.matches(request, response)) { //note5 Util.closeQuietly(response.body()); return null; } return response; }
private CacheRequest put(Response response) throws IOException {
String requestMethod = response.request().method();
if (HttpMethod.invalidatesCache(response.request().method())) { //note1
remove(response.request());
return null;
}
if (!requestMethod.equals("GET")) { //note 2
return null;
}
if (OkHeaders.hasVaryAll(response)) { //note3
return null;
}
Entry entry = new Entry(response); //note4
DiskLruCache.Editor editor = null;
try {
editor = cache.edit(urlToKey(response.request()));//note5
if (editor == null) {
return null;
}
entry.writeTo(editor); //note 6
return new CacheRequestImpl(editor); //note 7
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
1、判斷請求如果是"POST"、"PATCH"、"PUT"、"DELETE"、"MOVE"中的任何一個則呼叫DiskLruCache.remove(urlToKey(request));將這個請求從快取中移除出去。
2、判斷請求如果不是Get則不進行快取,直接返回null。官方給的解釋是快取get方法得到的Response效率高,其它方法的Response沒有快取效率低。通常通過get方法獲取到的資料都是固定不變的的,因此快取效率自然就高了。其它方法會根據請求報文引數的不同得到不同的Response,因此快取效率自然而然就低了。
3、判斷請求中的http資料包中headers是否有符號"*"的萬用字元,有則不快取直接返回null
4、由Response物件構建一個Entry物件
5、通過呼叫DiskLruCache.edit(urlToKey(response.request()));方法得到一個DiskLruCache.Editor物件。
6、方法內部是通過Okio.buffer(editor.newSink(ENTRY_METADATA));獲取到一個BufferedSink物件,隨後將Entry中儲存的Http報頭資料寫入到sink流中。
7、構建一個CacheRequestImpl物件,構造器中通過editor.newSink(ENTRY_BODY)方法獲得Sink物件。
這裡我們使用了DiskLruCache.remove(urlToKey(request))移除請求、DiskLruCache.edit(urlToKey(response.request()));獲得一個DiskLruCache.Editor物件,通過Editor獲得一個sink流。同樣的等下面學習DiskLruCache的時候再詳細看該部分的內容。
update()@Cache.class
private void update(Response cached, Response network) {
Entry entry = new Entry(network); //note 1
DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot; //note2
DiskLruCache.Editor editor = null;
try {
editor = snapshot.edit(); // note 3
if (editor != null) {
entry.writeTo(editor); //note4
editor.commit();
}
} catch (IOException e) {
abortQuietly(editor);
}
}
1、首先利用network即我們剛剛從網路得到的響應,構造一個Entry物件
2、從命中的快取中獲取到DiskLruCache.Snapshot
3、從DiskLruCache.Snapshot獲取到DiskLruCache.Editor物件
4、將entry資料寫入到前面的editor中
對Cache暫時就介紹到這裡,梳理回顧一下在該類中我們都對DiskLruCache哪些方法進行了訪問。
DiskLruCache.get(String)獲取DiskLruCache.Snapshot
DiskLruCache.remove(String)移除請求
DiskLruCache.edit(String);獲得一個DiskLruCache.Editor物件,
DiskLruCache.Editor.newSink(int);獲得一個sink流
DiskLruCache.Snapshot.getSource(int);獲取一個Source物件。
DiskLruCache.Snapshot.edit();獲得一個DiskLruCache.Editor物件,
下面我們就來學習一下DiskLruCache中的這些方法。
內部類@DiskLruCache.class
在正式介紹DiskLruCache的上面幾個方法之前,我們先來看看DiskLruCache中的幾個常用內部類。 Entry內部類是實際的用於儲存儲存快取資料的實體,每個url對應一個Entry實體。 [email protected]該內部類有如下的幾個域:
private final String key;
/** 實體對應的快取檔案 */
private final long[] lengths; //檔案位元數
private final File[] cleanFiles;
private final File[] dirtyFiles;
/** 實體可讀該物件為真*/
rivate boolean readable;
/** 實體未被編輯過,則該物件為null*/
private Editor currentEditor;
/** 最近像該Entry提交的序列數 */
private long sequenceNumber;
簡單的看下其構造器
private Entry(String key) {
this.key = key; //note1
lengths = new long[valueCount]; //note2
cleanFiles = new File[valueCount];
dirtyFiles = new File[valueCount];
//note 3
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);
}
}
1、構造器接受一個String key引數,意味著一個url對應一個Entry 2、valueCount在構造DiskLruCache時傳入的引數預設大小為2。好奇的童鞋肯定問,為啥非得是2?我們知道在Cache中有如下的定義: private static final int ENTRY_METADATA = 0; private static final int ENTRY_BODY = 1; private static final int ENTRY_COUNT = 2; 這下應該知道為何是2了吧,每個Entry對應兩個檔案。key.1檔案儲存的是Response的headers,key,2檔案儲存的是Response的body 3、建立valueCount個key.i檔案,和valueCount個key.i.tmp檔案,i的取值為0,1...valueCount 看看其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++) {
sources[i] = fileSystem.source(cleanFiles[i]); //note1
}
return new Snapshot(key, sequenceNumber, sources, lengths);
} catch (FileNotFoundException e) {
//檔案被手動刪除,關閉得到的Source
for (int i = 0; i < valueCount; i++) {
if (sources[i] != null) {
Util.closeQuietly(sources[i]);
} else {
break;
}
}
return null;
}
}
1、獲取cleanFile的Source,用於讀取cleanFile中的資料,並用得到的sources、Entry.key、Entry.lengths、sequenceNumber資料構造一個Snapshot物件。
到此為止Entry還有setLengths(String[] strings)、writeLengths(BufferedSink writer)兩個方法沒有介紹,不過這兩個方法比較簡單,都是對Entry.lengths進行操作的。前者將string[]和long[]之間進行對映,後者是將long[]寫入到一個sink流中。
既然遇到了Snapshot那麼我們就看看該物件是個什麼玩意兒,從名字來看快照,應該適用於從entry中讀取資料的。
[email protected]首先看看它都有哪些域
private final String key; //對應的url的md5值
private final long sequenceNumber; //序列數
private final Source[] sources; //可以讀入資料的流陣列,果然存有這麼多source當然是利用它來從cleanFile中讀取資料了。
private final long[] lengths; //與上面的流數一一對應
構造器內容就是對上面這些域進行賦值
該類中的其它都方法都很簡單,如getSource(int index)就是等於source[index]所以下面只對edit方法進行介紹。
edit方法
public Editor edit() throws IOException {
return DiskLruCache.this.edit(key, sequenceNumber);
}
該方法內部是呼叫DiskLruCache的edit方法,不過引數是跟該Snapshot物件關聯的key和sequenceNumber。限於篇幅問題,這裡就不進入到edit方法內部了,這裡大概講一下它完成的事情。對於各種邏輯判斷和異常處理在此不進行描述,只是介紹它正常情況下是如何執行的。核心程式碼如下:
{
journalWriter.writeUtf8(DIRTY).writeByte(' ').writeUtf8(key).writeByte('\n');
journalWriter.flush();
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
Editor editor = new Editor(entry);
entry.currentEditor = editor;
return editor;
}
首先在日誌報告中寫入DIRTY key這樣一行資料,表明該key對應的Entry當前正被編輯中。
隨後利用該Entry建立一個Editor物件。我了個乖乖,下面又得瞄一眼Editor類,總感覺沒完沒了。
[email protected]首先按照慣例看看它有什麼域
private final Entry entry;
private final boolean[] written;
private boolean hasErrors;
private boolean committed;
好像看不出啥東西,待老夫看一眼構造器
構造器
private Editor(Entry entry) {
this.entry = entry;
this.written = (entry.readable) ? null : new boolean[valueCount];
}
好像也沒什麼卵用。是時候放出它的幾個方法出來鎮鎮場了。
newSource方法
public Source newSource(int index) throws IOException {
.....
return fileSystem.source(entry.cleanFiles[index]);
}
該方法這麼簡單??其實還有很多判斷語句和異常處理,這裡限於篇幅就刪掉了。它核心就是return這句。返回指定idnex的cleanFile的讀入流
newSink方法
public Sink newSink(int index) throws IOException {
if (!entry.readable) {
written[index] = true;
}
File dirtyFile = entry.dirtyFiles[index];
Sink sink;
try {
sink = fileSystem.sink(dirtyFile);
} catch (FileNotFoundException e) {
return NULL_SINK;
}
return new FaultHidingSink(sink) {
@Override protected void onException(IOException e) {
synchronized (DiskLruCache.this) { hasErrors = true; }
}
};
}
方法也還算簡單,首先給Editor的boolean陣列written賦值為true表明該位置對應的檔案已經被寫入新的資料。這裡要注意的是寫入的檔案物件不是cleanFile而是dirtyFiles!
commit方法
public void commit() throws IOException {
synchronized (DiskLruCache.this) {
if (hasErrors) {
completeEdit(this, false);
removeEntry(entry); // The previous entry is stale.
} else {
completeEdit(this, true);
}
committed = true;
}
}
這裡執行的工作是提交寫入資料,通知DiskLruCache重新整理相關資料。Editor還有相關的如abortXX方法等最後都是執行completeEdit(this,
??);成功提交則??等於true否則等於false。這樣的提交都什麼影響呢?
success情況提交:dirty檔案會被更名為clean檔案,entry.lengths[i]值會被更新,DiskLruCache,size會更新(DiskLruCache,size代表的是所有整個快取檔案加起來的總大小),redundantOpCount++,在日誌中寫入一條Clean資訊
failed情況:dirty檔案被刪除,redundantOpCount++,日誌中寫入一條REMOVE資訊
DiskLruCache內部類的基本情況就介紹到這裡。下面我們對在Cache中使用的幾個方法。
DiskLruCache.get(String)獲取DiskLruCache.Snapshot
DiskLruCache.remove(String)移除請求
DiskLruCache.edit(String);獲得一個DiskLruCache.Editor物件,
DiskLruCache.Editor.newSink(int);獲得一個sink流
DiskLruCache.Snapshot.getSource(int);獲取一個Source物件。
DiskLruCache.Snapshot.edit();獲得一個DiskLruCache.Editor物件,
逐一進行介紹。
DiskLruCache.class
private final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true); LinkedHashMap自帶Lru演算法的光環屬性,詳情請看LinkedHashMap原始碼說明 該物件有一個執行緒池,不過該池最多有一個執行緒工作,用於清理,維護快取資料。建立一個DiskLruCache物件的方法是呼叫該方法,而不是直接呼叫構造器。create()@DiskLruCache.class
public static DiskLruCache create(FileSystem fileSystem, File directory, int appVersion,
int valueCount, long maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
// Use a single background thread to evict entries.
Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true)); //建立一個最多容納一條執行緒的執行緒池
return new DiskLruCache(fileSystem, directory, appVersion, valueCount, maxSize, executor);
}
OkHttpClient通過該方法獲取到DiskLruCache的一個例項。DiskLruCache的構造器,只能被包內中類呼叫,因此一般都是通過該方法獲取一個DiskLruCache例項。
DiskLruCache()@DiskLruCache.class
static final String JOURNAL_FILE = "journal";
static final String JOURNAL_FILE_TEMP = "journal.tmp";
static final String JOURNAL_FILE_BACKUP = "journal.bkp"
DiskLruCache(FileSystem fileSystem, File directory, int appVersion, int valueCount, long maxSize, Executor executor) {
this.fileSystem = fileSystem;
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
this.executor = executor;
}
該構造器會在指定的目錄下建立三個檔案,這三個檔案是DiskLruCache的工作日誌檔案。在執行DiskLruCache的任何方法之前都會執行下面的方法完成DiskLruCache的初始化,對於為何不在DiskLruCache的構造器中完成對該方法的呼叫,目的估計是為了延遲初始化,因為該初始化會建立一系列的檔案和物件,所以做延遲初始化處理。
initialize()@DiskLruCache.class
public synchronized void initialize() throws IOException {
assert Thread.holdsLock(this); //note1
if (initialized) {
return; // note2
}
//note3
if (fileSystem.exists(journalFileBackup)) {
// If journal file also exists just delete backup file.
if (fileSystem.exists(journalFile)) {
fileSystem.delete(journalFileBackup);
} else {
fileSystem.rename(journalFileBackup, journalFile);
}
}
//note4
if (fileSystem.exists(journalFile)) {
try {
readJournal();
processJournal();
initialized = true;
return;
} catch (IOException journalIsCorrupt) {
Platform.get().logW("DiskLruCache " + directory + " is corrupt: "
+ journalIsCorrupt.getMessage() + ", removing");
delete();
closed = false;
}
}
rebuildJournal(); //note5
initialized = true; //note6
}
1、這是個斷言語句,當後面的Thread.holdsLock(this)為真,則往下執行否則丟擲異常
2、如果之前已經執行過該方法,那麼這裡就會從這裡返回
3、如果有journalFile則刪除journalFileBackup,沒有journalFile但是有journalFileBackUp則將後者更名為journalFile
4、如果有journalFile檔案則對該檔案進行處理,分別呼叫readJournal方法和processJournal()方法;
- readJournal():
- BufferedSource source = Okio.buffer(fileSystem.source(journalFile))獲取journalFile的讀流
- 對檔案中的內容頭進行驗證判斷日誌是否被破壞;
- 呼叫readJournalLine(source.readUtf8LineStrict())方法;
- 方法引數是從source中取出一行一行的資料,String的格式類似如下CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 即第一個是操作名,第二個是對url進行md5編碼後得到的key,後面則針對操作不同有不同的值,具體內容就是該Entry對應的快取檔案大小(bytes)。
- 方法對讀取到的String進行解析,通過解析結果對LruEntries進行初始化.所以系統重啟,通過日誌檔案可以恢復上次快取的資料。
- 對每次解析的非REMOVE資訊,利用該資料的key建立一個Entry;如果判斷資訊為CLEAN則設定entry.readable = true;表明該entry可讀,設定entry.currentEditor = null表明當前Entry不是處於可編輯狀態,呼叫entry.setLengths(String[]),設定該entry.lengths的初始值。如果判斷為Dirty則設定entry.currentEditor = new Editor(entry);表明當前Entry處於被編輯狀態。
- 隨後記錄redundantOpCount的值,該值的含義就是判斷當前日誌中記錄的行數與lruEntries集合容量的差值。即日誌中多出來的"冗餘"記錄
- processJournal():
- 刪除存在的journalFileTmp檔案
- lruEntries中的Entry資料處理:如果entry.currentEditor != null則表明上次異常關閉,因此該Entry的資料是髒的,不能讀,進而刪除該Entry下的快取檔案,將該Entry從lruEntries中移出;如果entry.currentEditor == null證明該Entry下的快取檔案可用,記錄它所有快取檔案中儲存的快取數。結果賦值給size。
private synchronized void rebuildJournal() throws IOException {
if (journalWriter != null) { //note1
journalWriter.close();
}
BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp)); //note2
try {
//note3
writer.writeUtf8(MAGIC).writeByte('\n');
writer.writeUtf8(VERSION_1).writeByte('\n');
writer.writeDecimalLong(appVersion).writeByte('\n');
writer.writeDecimalLong(valueCount).writeByte('\n');
writer.writeByte('\n');
//note4
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();
}
//note 5
if (fileSystem.exists(journalFile)) {
fileSystem.rename(journalFile, journalFileBackup);
}
fileSystem.rename(journalFileTmp, journalFile);
fileSystem.delete(journalFileBackup);
journalWriter = newJournalWriter();
hasJournalErrors = false;
}
1、對於journalWriter我們只需要知道它是一個跟journalFile繫結的BufferedSink物件即可
2、獲取對journalFileTmp檔案的Sink流並對該流用buffer進行包裝,提高I/O寫入效率
3、寫入日誌頭
4、將lruEntries集合中的Entry物件寫入到檔案中;根據Entry的currentEditor值判斷是CLEN還是DIRTY,隨後寫入該Entry的key,如果是CLEN還會寫入該Entry的每個快取檔案的大小bytes
5、這一段程式碼就是把前面的journalFileTmp更名為journalFile, 然後journalWriter跟該檔案繫結,通過它來向journalWriter寫入資料,設定hasJournalErrors = false;
上面我們把initialize()方法解析完了,終於可以看看之前一直提到的下列方法了
DiskLruCache.get(String)獲取DiskLruCache.Snapshot
DiskLruCache.remove(String)移除請求
DiskLruCache.edit(String);獲得一個DiskLruCache.Editor物件,
DiskLruCache.Editor.newSink(int);獲得一個sink流
DiskLruCache.Snapshot.getSource(int);獲取一個Source物件。
DiskLruCache.Snapshot.edit();獲得一個DiskLruCache.Editor物件,
get(key)@DiskLruCache.class
public synchronized Snapshot get(String key) throws IOException {
initialize(); // note1
checkNotClosed(); //note2
validateKey(key); //note3
Entry entry = lruEntries.get(key);
if (entry == null || !entry.readable) return null;
Snapshot snapshot = entry.snapshot(); //note 4
if (snapshot == null) return null;
redundantOpCount++;
journalWriter.writeUtf8(READ).writeByte(' ').writeUtf8(key).writeByte('\n'); //note3
if (journalRebuildRequired()) { //note4
executor.execute(cleanupRunnable);
}
return snapshot;
}
1、完成初始化工作,這部分之前已經講過就不再說了。
2、該方法其實是對closed進行判斷,如果值為真丟擲異常,為假繼續執行。
3、判斷key是否有效,Pattern規則是 Pattern.compile("[a-z0-9_-]{1,120}");
4、獲取entry.snapshot()
5、向日志文件中寫入讀取日誌
4、redundantOpCount >= redundantOpCompactThreshold && redundantOpCount >= lruEntries.size();簡單說就是當前redundantOpCount值大於2000,而且該值大於等於儲存的快取鍵值對集合的容量。目的是判斷日誌中的資料是不是太多了?太多則開啟執行緒執行清理工作
先來分析一下它是如何維護快取資料的,先找到類中的cleanupRunnable物件,檢視其run方法得知,其主要呼叫了trimToSize()和rebuildJournal()兩個方法對快取資料進行維護的。
trimToSize()@DiskLruCache.class
private void trimToSize() throws IOException {
while (size > maxSize) {
Entry toEvict = lruEntries.values().iterator().next();
removeEntry(toEvict);
}
mostRecentTrimFailed = false;
}
方法邏輯很簡單,如果lruEntries的容量大於門限,則把lruEntries中第一個Entry移出集合,一直迴圈該操作,直到lruEntries的容量小於門限。 maxSize是在建立Cache是得到的。rebuildJournal()方法前面已經講過了這裡就不講了。
remove(String)@DiskLruCache.class
public synchronized boolean remove(String key) throws IOException {
initialize();
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null) return false;
boolean removed = removeEntry(entry); //note1
if (removed && size <= maxSize) mostRecentTrimFailed = false;
return removed;
}
該方法大部分內容之前已經講解過了,這裡只對其中呼叫的removeEntry(entry)方法進行下說明
removeEntry()@DiskLruCache.classprivate boolean removeEntry(Entry entry) throws IOException {
if (entry.currentEditor != null) { //note1
entry.currentEditor.hasErrors = true; // Prevent the edit from completing normally.
}
//note2
for (int i = 0; i < valueCount; i++) {
fileSystem.delete(entry.cleanFiles[i]);
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
//note3
redundantOpCount++;
journalWriter.writeUtf8(REMOVE).writeByte(' ').writeUtf8(entry.key).writeByte('\n');
lruEntries.remove(entry.key);
if (journalRebuildRequired()) {
executor.execute(cleanupRunnable);
}
return true;
}
1、設定該entry對應的editor告訴它我就要掛了,你可以下班了
2、刪除entry中的cleanFiles,不過為啥不刪除dirty檔案呢?然後改變DiskLruCach.size的大小
3、向日志中寫入一條REMOVE訊息
4、檢查是否有必要維護一下快取資料。
edit()@DiskLruCache.class
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
initialize();
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key); //note1
......
journalWriter.writeUtf8(DIRTY).writeByte(' ').writeUtf8(key).writeByte('\n'); //note2
journalWriter.flush();
if (hasJournalErrors) {
return null; // Don't edit; the journal can't be written.
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry);
}
Editor editor = new Editor(entry); //note3
entry.currentEditor = editor;
return editor;
}
1、根據key獲取到entry
2、寫日誌,誒跟雷鋒一樣啊,做一件事都得寫個日誌
3、建立Editor
至此我們對okhttp的快取機制理解的差不多了,下面我們對上面的分析做一下小節:
構建一個Cache時需要我們指定一個快取檔案的存放目錄,快取檔案的最大值(單位byte)。
DiskLruCache有一個執行緒池,該執行緒池最多隻有一條執行緒執行,執行的任務也簡單,主要完成兩個任務,其一移除lruEntries集合中多餘的Entry,使其小於maxSize,並刪除相關的快取檔案;其二如有必要重建工作日誌。
DiskLruCache的lruEntries採用LinkedHashMap實現,該集合自帶Lru光環屬性,無需任何額外程式設計,集合內部採用lru演算法實現。
DiskLruCache會在快取目錄下建立日誌檔案,用於對每次的獲取、刪除、編輯等操作都會進行相應的記錄,該日誌也用於應用重啟後恢復快取資訊,初始化lruEntries快取集合。
DiskLruCache具體的快取資訊存放物件是DiskLruCache.Entry.class,該物件存放valueCount個檔案的引用,預設是兩個分別儲存Response的headers和body,一個url對應一個Entry物件,對於Snapshot和Editor都是從Entry獲取到的,Snapshot主要是讀取Entry內容,Editor主要是向Entry寫入資料。Entry物件引用的檔案其命名格式為key.i。
對於okhttp的Cache的理解暫時就到這裡了。下一節會對okhttp的最後一個內容okio進行深入的學習,詳情請看《OkHttp深入學習(四)——0kio》
相關推薦
OkHttp深入學習(三)——Cache
兩節的學習基本上對於okhttp的使用和實現有了一定的瞭解,不過還有一些比較重要的概念如快取、ConnectionPool和OkHttpClient等都沒有進行詳細的說明。因此本節對okh
spring深入學習(三)IOC 之 載入 Bean
先看一段熟悉的程式碼: ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanF
OkHttp深入學習(二)——網路
getResponseWithInterceptorChain()@RealCall.class private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException { Intercep
Maven深入學習(三)- 聚合與繼承
1.聚合 在使用Maven的過程中,手邊可能有很多個專案,都需要打包,或者同時進行一些操作,這時候,如果一個一個手動的去操作, 就會顯得很麻煩。這時候,使用聚合就可以解決問題了。 假設,現在已有專案brother01,brother02,我們想要同時將這兩個專
Android Gallery3D原始碼學習總結(三)——Cache快取及資料處理流程
第一,在應用程式中有三個執行緒存在:主執行緒(隨activity的宣告週期啟動銷燬)、feed初始化執行緒(進入程式時只執行一次,用於載入相簿初始資訊)、feed監聽執行緒(一直在跑,監聽相簿和相片的變更)。 第二,不考慮CacheService 啟動的主要流程歸納如下: 1
爬蟲庫之BeautifulSoup學習(三)
子節點 rom lac repr 文檔 strong 爬蟲 time contents 遍歷文檔樹: 1、查找子節點 .contents tag的.content屬性可以將tag的子節點以列表的方式輸出。 print soup.body.cont
Java學習(三)JSP學習1
rtm 斷開 三大指令 保持 web應用 對比 c語言 let 新建 一、 理解JSP技術 JSP全名為Java Server Pages,中文名叫java服務器頁面,其根本是一個簡化的Servlet設計,它 是由Sun Microsystems公司倡導、許多公司參
java學習(三)類
con void pub oid 修改密碼 tro int str 用戶 類 public class Dog{ String breed; int age; String color; void barking(){ } void hungr
Qt Installer Framework的學習(三)
科技 released his 表示 star online 解壓 dem 普通 Qt Installer Framework的學習(三) Qt Installer Framework的樣例中。通常是這種:config目錄一般放了一個config.xml文件,包括的是安裝
PYTHON學習(三)之利用python進行數據分析(1)---準備工作
-- 下載 rip 安裝包 png 要求 eight code 電腦 學習一門語言就是不斷實踐,python是目前用於數據分析最流行的語言,我最近買了本書《利用python進行數據分析》(Wes McKinney著),還去圖書館借了本《Python數據分析基礎教程--N
Python學習(三) 八大排序算法的實現(下)
ram tty adjust 二叉樹 turn bre python 使用 元素 本文Python實現了插入排序、基數排序、希爾排序、冒泡排序、高速排序、直接選擇排序、堆排序、歸並排序的後面四種。 上篇:Python學習(三) 八大排序算法的實現(上)
RabbitMQ學習(三)訂閱/發布
cto submit actor nal chan true exec oid lsp RabbitMQ學習(三)訂閱/發布 1.RabbitMQ模型 前面所學都只用到了生產者、隊列、消費者。如上圖所示,其實生產者並不直接將信息傳輸到隊列中,在生產者和隊列
C++學習(三)入門篇——函數
image clu square src 函數接口 值類型 使用 mes 技術分享 C++函數分兩種:有返回值的和沒返回值的 1.有返回值的函數 調用函數流程 如圖,sqrt(6.25)為函數調用,
python學習(三)
操作數 sdf dfs 查找子串 索引 start val 成員 放置 第三章 使用字符串
【轉】JMeter學習(三)元件的作用域與執行順序
ces ner 處理器 規則 fig 子節點 控制器 conf 節點 1.元件的作用域 JMeter中共有8類可被執行的元件(測試計劃與線程組不屬於元件),這些元件中,取樣器是典型的不與其它元件發生交互作用的元件,邏輯控制器只對其子節點的取樣器有效,而其它元件(config
vue移動音樂app開發學習(三):輪播圖組件的開發
hub out webapp width eth reat slot utc -1 本系列文章是為了記錄學習中的知識點,便於後期自己觀看。如果有需要的同學請登錄慕課網,找到Vue 2.0 高級實戰-開發移動端音樂WebApp進行觀看,傳送門。 完成後的頁面狀態以及項目結構如
JavaScript深入理解(三)
有一點 相同 定義 怎麽辦 turn 如何 nbsp 屬性。 fff 強大的原型和原型鏈 前言 JavaScript 不包含傳統的類繼承模型,而是使用 prototypal 原型模型。 雖然這經常被當作是 JavaScript 的缺點被提及,其實基於原型的繼承模型比傳
selenium + python自動化測試unittest框架學習(三)webdriver對頁面其他控件操作(三)
文件的 文件路徑 內容 option selenium script web 對話 對話框 1.對話框,下拉框 (1)對話框的有兩種,一種是iframe格式的,需要switch_to_iframe()進行定位,現在大部分的對話框是div格式的,這種格式的可以通過層級定位來定
selenium + python自動化測試unittest框架學習(三)webdriver元素定位(一)
倒數 節點 大於 文本框 webdriver 而且 單標簽 unit 遍歷 1.Webdriver原理 webdirver是一款web自動化操作工具,為瀏覽器提供統一的webdriver接口,由client也就是我們的測試腳本提交請求,remote server瀏覽器進行響
Spring Boot學習(三)
src pack art tin pre size -s script jar Spring boot實戰 —— Hello Word 1、創建maven項目 2、pom.xml文件 <?xml version="1.0" encoding="UTF-8"?>