Cache.class
public 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);
}
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;
}
1、Util.md5Hex(request.url().toString());將客戶的請求的url換成成32個字符的MD5字符串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));將這個請求從緩存中移除出去。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對象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
該內部類有如下的幾個域:
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
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;
}
}
private final String key; //對應的url的md5值
private final long sequenceNumber; //序列數
private final Source[] sources; //可以讀入數據的流數組,果然存有這麼多source當然是利用它來從cleanFile中讀取數據了。
private final long[] lengths; //與上面的流數一一對應
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;
}
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];
}
public Source newSource(int index) throws IOException {
.....
return fileSystem.source(entry.cleanFiles[index]);
}
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; }
}
};
}
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.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
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);
}
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;
}
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
}
- 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;
}
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對象,
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;
}
private void trimToSize() throws IOException {
while (size > maxSize) {
Entry toEvict = lruEntries.values().iterator().next();
removeEntry(toEvict);
}
mostRecentTrimFailed = false;
}
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;
}
private 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;
}
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;
}