前言
在之前的文章Glide源碼解析之MemoryCache介紹了Glide的二級緩存MemoryCache,現在讓我們來看下三級緩存DiskCache。
獲取DiskCache
在上文Glide源碼解析之DecodeHelper我們講到DecodeHelper主要是起到了一個提供數據的作用, DiskCache同樣是通過DecoderHelper來通過diskCacheProvider獲取的,而diskCacheProvider的實現類為LazyDiskCacheProvider,是由Engine在構造函數里創建并傳遞給DecoderHelper的。LazyDiskCacheProvider的構造函數參數為DiskCache.Factory,它的實現類為InternalCacheDiskCacheFactory,是在GlideBuilder的builde()里面創建并傳給Engine的。
//DecodeHelper
DiskCache getDiskCache() {
return diskCacheProvider.getDiskCache();
}
//Engine
Engine(MemoryCache cache,
DiskCache.Factory diskCacheFactory,
xxx) {
this.diskCacheProvider = new LazyDiskCacheProvider(diskCacheFactory);
}
//GlideBuilder
Glide builde(Context context){
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
if (engine == null) {
engine =
new Engine(
memoryCache,
diskCacheFactory,
diskCacheExecutor,
sourceExecutor,
GlideExecutor.newUnlimitedSourceExecutor(),
animationExecutor,
isActiveResourceRetentionAllowed);
}
}
LazyDiskCacheProvider
這里對DiskCache用了單例模式來保存實例,它的創建則是由DiskCache.Factory來提供,也就是上面說到的InternalCacheDiskCacheFactory。InternalCacheDiskCacheFactory繼承于DiskLruCacheFactory,DiskLruCacheFactory實現了DiskCache.Factory的接口,待會再看下這兩個類。
private static class LazyDiskCacheProvider implements DecodeJob.DiskCacheProvider {
private final DiskCache.Factory factory;
private volatile DiskCache diskCache;
LazyDiskCacheProvider(DiskCache.Factory factory) {
this.factory = factory;
}
@VisibleForTesting
synchronized void clearDiskCacheIfCreated() {
if (diskCache == null) {
return;
}
diskCache.clear();
}
@Override
public DiskCache getDiskCache() {
if (diskCache == null) {
synchronized (this) {
if (diskCache == null) {
diskCache = factory.build(); //由DiskLruCacheFactory去實例化DiskCache
}
if (diskCache == null) {
diskCache = new DiskCacheAdapter();
}
}
}
return diskCache;
}
}
DiskCache.Factory
InternalCacheDiskCacheFactory只是提供了構造函數,并最終調用了DiskLruCacheFactory的構造函數,給它提供了diskCacheSize和cacheDirectoryGetter,build()還是由DiskLruCacheFactory來實現的。
在builde()里面首先檢查緩存目錄是否為null,這個一般不為null,目錄路徑為data/data/你的app/cache/image_manager_disk_cache。最終返回的DiskCache實現類則是DiskLruCacheWrapper。
//DiskLruCacheFactory
public DiskCache build() {
File cacheDir = cacheDirectoryGetter.getCacheDirectory();
if (cacheDir == null) {
return null;
}
if (!cacheDir.mkdirs() && (!cacheDir.exists() || !cacheDir.isDirectory())) {
return null;
}
return DiskLruCacheWrapper.create(cacheDir, diskCacheSize);
}
//DiskLruCacheWrapper
public static DiskCache create(File directory, long maxSize) {
return new DiskLruCacheWrapper(directory, maxSize);
}
//InternalCacheDiskCacheFactory
public final class InternalCacheDiskCacheFactory extends DiskLruCacheFactory {
public InternalCacheDiskCacheFactory(Context context) {
this(context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR/*image_manager_disk_cache*/,
DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE/*250M*/);
}
public InternalCacheDiskCacheFactory(final Context context, final String diskCacheName,
long diskCacheSize) {
super(new CacheDirectoryGetter() {
@Override
public File getCacheDirectory() {
File cacheDirectory = context.getCacheDir();
if (cacheDirectory == null) {
return null;
}
if (diskCacheName != null) {
//緩存目錄為cache/image_manager_disk_cache
return new File(cacheDirectory, diskCacheName);
}
return cacheDirectory;
}
}, diskCacheSize);
}
}
DiskCache
DiskCache的實現類為DiskLruCacheWrapper,但是它并不進行實際的磁盤緩存,具體的操作是由DiskLruCache來實現的。由名字可以看出這里使用了裝飾模式來對DiskLruCache增加一些功能。
public interface DiskCache {
/**
* 根據key獲取緩存文件
*/
@Nullable
File get(Key key);
/**
* 寫入緩存文件
*/
void put(Key key, Writer writer);
/**
* 刪除緩存文件
*/
void delete(Key key);
/**
* 清空緩存文件(全刪)
*/
void clear();
}
public class DiskLruCacheWrapper implements DiskCache {
private static final int APP_VERSION = 1;
private static final int VALUE_COUNT = 1;
private final SafeKeyGenerator safeKeyGenerator; //用來構建String類型的磁盤緩存Key
private final File directory;
private final long maxSize;
private final DiskCacheWriteLocker writeLocker = new DiskCacheWriteLocker();
private DiskLruCache diskLruCache;
private synchronized DiskLruCache getDiskCache() throws IOException {
if (diskLruCache == null) {
diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize);
}
return diskLruCache;
}
}
DiskLruCache
DiskLruCache和MemoryCache一樣是使用LinkedHashMap來實現LRU算法,但是LinkedHashMap只能對內存數據進行處理,但是要是App關掉下次再打開不就是沒最近使用記錄了? 所以DiskLruCache將對數據的操作都寫進了一個日志文件里,當初始化時先從日志文件中獲取歷史緩存以及讀取順序,之后再操作時也會同步更新到日志文件。
public final class DiskLruCache implements Closeable {
//日志文件相關
static final String JOURNAL_FILE = "journal";
static final String JOURNAL_FILE_TEMP = "journal.tmp";
static final String JOURNAL_FILE_BACKUP = "journal.bkp";
static final String MAGIC = "libcore.io.DiskLruCache";
static final String VERSION_1 = "1";
static final long ANY_SEQUENCE_NUMBER = -1;
private static final String CLEAN = "CLEAN";
private static final String DIRTY = "DIRTY";
private static final String REMOVE = "REMOVE";
private static final String READ = "READ";
private final File directory; //圖片緩存文件夾
private final File journalFile; //日志文件
private final File journalFileTmp; //臨時日志文件
private final File journalFileBackup; //日志備份文件
private final int appVersion;
private long maxSize; //最大緩存大小
private long size = 0; //當前緩存大小
private final int valueCount; //值為1
private Writer journalWriter; //快速讀寫文件使用 Writer
private final LinkedHashMap<String, Entry> lruEntries =
new LinkedHashMap<String, Entry>(0, 0.75f, true);
}
//日志文件格式
libcore.io.DiskLruCache //MAGIC
1 //VERSION_1
1 //appVersion
1 //valueCount
DIRTY 4244bd8b60e86cb88a8d24052c5a3d52da7091a289a9d3c09b98531260ce0171 //新寫入LinkedHashMap的數據,狀態為DIRTY 后面跟著的一串是key
CLEAN 4244bd8b60e86cb88a8d24052c5a3d52da7091a289a9d3c09b98531260ce0171 8441 //將數據寫進磁盤后狀態為CLEAN,最后的數字是圖片大小
READ 4244bd8b60e86cb88a8d24052c5a3d52da7091a289a9d3c09b98531260ce0171 //讀了之后狀態為READ
REMOVE 64af945d99537d3f777a76dc62012d9c2368f33f145bbf592fa11f28489f8142 //刪除的狀態為REMOVE
//日志文件記錄的是操作狀態,執行了新的操作之后并不會把之前的刪了,只是會添加一條新的記錄。
open()
在這里創建DiskLruCache
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
throws IOException {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
if (valueCount <= 0) {
throw new IllegalArgumentException("valueCount <= 0");
}
File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
if (backupFile.exists()) {
//如果有日志備份文件
File journalFile = new File(directory, JOURNAL_FILE);
if (journalFile.exists()) {
// 如果日志文件存在則刪除備份文件
backupFile.delete();
} else {
//如果日志文件不存在則把備份文件重命名為日志文件
renameTo(backupFile, journalFile, false);
}
}
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
if (cache.journalFile.exists()) {
try {
cache.readJournal(); //讀取日志文件
cache.processJournal(); //處理日志文件
return cache; //如果沒有異常的話直接返回
} catch (IOException journalIsCorrupt) {
System.out
.println("DiskLruCache "
+ directory
+ " is corrupt: "
+ journalIsCorrupt.getMessage()
+ ", removing");
cache.delete(); //刪除全部緩存文件
}
}
//上面出現異常后會刪除全部緩存文件,這里重新生成DiskLruCache以及日志文件
directory.mkdirs();
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
readJournal()
在這里讀取日志文件,將日志文件記錄的數據寫入LinkedHashMap。
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
try {
String magic = reader.readLine();
String version = reader.readLine();
String appVersionString = reader.readLine();
String valueCountString = reader.readLine();
String blank = reader.readLine();
//檢查格式是否正確
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 {
//一行行讀取日志文件
readJournalLine(reader.readLine());
lineCount++;
} catch (EOFException endOfJournal) {
break;
}
}
redundantOpCount = lineCount - lruEntries.size();
if (reader.hasUnterminatedLine()) {
rebuildJournal();
} else {
journalWriter = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(journalFile, true), Util.US_ASCII));
}
} finally {
Util.closeQuietly(reader);
}
}
private void readJournalLine(String line) throws IOException {
int firstSpace = line.indexOf(' '); //第一個空格的位置,它的前面是狀態,后面是key
if (firstSpace == -1) {
throw new IOException("unexpected journal line: " + line);
}
int keyBegin = firstSpace + 1;
int secondSpace = line.indexOf(' ', keyBegin); //如果狀態是CLEAN則會有兩個空格,它的前面是key,后面是圖片大小
final String key;
if (secondSpace == -1) {
key = line.substring(keyBegin);
//如果狀態是REMOVE則從LinkedHashMap中刪除
if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
lruEntries.remove(key);
return;
}
} else {
key = line.substring(keyBegin, secondSpace);
}
Entry entry = lruEntries.get(key);
if (entry == null) {
//Entry里面根據key來獲取緩存文件
entry = new Entry(key);
//將獲取到的緩存文件添加到LinkedHashMap中
lruEntries.put(key, entry);
}
if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
//如果狀態是CLEAN則代表圖片已經寫入緩存文件了
String[] parts = line.substring(secondSpace + 1).split(" ");
entry.readable = true;
entry.currentEditor = null;
entry.setLengths(parts);
} else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
//狀態是DIRTY則以后會通過Editor來寫入緩存文件,也會通過判斷currentEditor是否為null來來確定是否是DIRTY的數據
entry.currentEditor = new Editor(entry);
} else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
// 因為上面調用過lruEntries.get(key),LinkedHashMap會自動調整訪問順序,所以這里不需要再執行其他操作
} else {
throw new IOException("unexpected journal line: " + line);
}
}
put()
首先會使用SafeKeyGenerator來對key進行sha256BytesToHex()計算,接著判斷是否已經有緩存數據,如果沒有則會先把圖片寫進DirtyFile,最后再重命名為CleanFile。
@Override
public void put(Key key, Writer writer) {
String safeKey = safeKeyGenerator.getSafeKey(key);
//使用磁盤緩存寫鎖同步代碼
writeLocker.acquire(safeKey);
try {
try {
// 如果已經有緩存數據了,則不覆蓋,直接返回。因為key計算的唯一性,使得一個key只會對應一個value。
DiskLruCache diskCache = getDiskCache();
Value current = diskCache.get(safeKey);
if (current != null) {
return;
}
DiskLruCache.Editor editor = diskCache.edit(safeKey);
if (editor == null) {
throw new IllegalStateException("Had two simultaneous puts for: " + safeKey);
}
try {
//這里獲取的是DirtyFile
File file = editor.getFile(0);
//將圖片寫進DirtyFile
if (writer.write(file)) {
editor.commit(); //將DirtyFile重命名為CleanFile
}
} finally {
editor.abortUnlessCommitted();
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unable to put to disk cache", e);
}
}
} finally {
writeLocker.release(safeKey);
}
}
//DiskLruCache
public Editor edit(String key) throws IOException {
return edit(key, ANY_SEQUENCE_NUMBER);
}
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
|| entry.sequenceNumber != expectedSequenceNumber)) {
return null;
}
if (entry == null) {
entry = new Entry(key);
lruEntries.put(key, entry); //存新數據到LinkedHashMap
} else if (entry.currentEditor != null) {
return null;
}
Editor editor = new Editor(entry);
entry.currentEditor = editor;
// 往日志文件添加一行狀態為DIRTY的記錄
journalWriter.append(DIRTY);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
journalWriter.flush();
return editor;
}
將臨時文件改為正式的緩存文件
public File getFile(int index) throws IOException {
synchronized (DiskLruCache.this) {
if (entry.currentEditor != this) {
throw new IllegalStateException();
}
//readable默認為false
if (!entry.readable) {
written[index] = true; //這里會賦值為true
}
File dirtyFile = entry.getDirtyFile(index);
if (!directory.exists()) {
directory.mkdirs();
}
return dirtyFile;
}
}
public void commit() throws IOException {
completeEdit(this, true);
committed = true;
}
private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
Entry entry = editor.entry;
if (entry.currentEditor != editor) {
throw new IllegalStateException();
}
if (success && !entry.readable) {
for (int i = 0; i < valueCount; i++) {
//上面在getFile()時賦值為true了,所以不會進入這里。
if (!editor.written[i]) {
editor.abort();
throw new IllegalStateException("Newly created entry didn't create value for index " + i);
}
//在getFile()之后就調用了writer.write(file),所以DirtyFile也是存在的,不會進入里面。
if (!entry.getDirtyFile(i).exists()) {
editor.abort();
return;
}
}
}
//valueCount為1
for (int i = 0; i < valueCount; i++) {
File dirty = entry.getDirtyFile(i);
if (success) {
if (dirty.exists()) {
//將DirtyFile重命名為CleanFile,文件名為(key.0),并計算當前緩存大小
File clean = entry.getCleanFile(i);
dirty.renameTo(clean);
long oldLength = entry.lengths[i];
long newLength = clean.length();
entry.lengths[i] = newLength;
size = size - oldLength + newLength;
}
} else {
deleteIfExists(dirty);
}
}
redundantOpCount++;
entry.currentEditor = null;
if (entry.readable | success) {
//往日志文件添加狀態為CLEAN的記錄
entry.readable = true; //賦值為true
journalWriter.append(CLEAN);
journalWriter.append(' ');
journalWriter.append(entry.key);
journalWriter.append(entry.getLengths());
journalWriter.append('\n');
if (success) {
entry.sequenceNumber = nextSequenceNumber++;
}
} else {
lruEntries.remove(entry.key);
journalWriter.append(REMOVE);
journalWriter.append(' ');
journalWriter.append(entry.key);
journalWriter.append('\n');
}
journalWriter.flush();
if (size > maxSize || journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
}
get()
獲取緩存文件,先根據key獲取對應的Entry,如果存在則判斷是否可讀(磁盤有對應的圖片緩存),如果緩存文件存在則往日志文件添加一條狀態為READ的記錄,最后把數據封裝進Value并返回。
public File get(Key key) {
String safeKey = safeKeyGenerator.getSafeKey(key);
File result = null;
try {
//Value只是DiskLruCache里面的一個內部類,主要起到封裝數據的作用,并不是實際緩存的值。
final DiskLruCache.Value value = getDiskCache().get(safeKey);
if (value != null) {
//返回緩存的文件,即是CleanFile
result = value.getFile(0);
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unable to get from disk cache", e);
}
}
return result;
}
//DiskLruCache
public synchronized Value get(String key) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
//在上面的completeEdit()中會將readable賦值為true
if (!entry.readable) {
return null;
}
for (File file : entry.cleanFiles) {
// 判斷緩存文件是否存在
if (!file.exists()) {
return null;
}
}
redundantOpCount++;
//往日志文件添加一條狀態為READ的記錄
journalWriter.append(READ);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
//將數據封裝進Value并返回
return new Value(key, entry.sequenceNumber, entry.cleanFiles, entry.lengths);
}
delete()
刪除緩存,首先會將緩存文件刪除,然后往日志文件添加一條狀態為REMOVE的記錄,最后把LinkedHashMap中的數據也刪除。
@Override
public void delete(Key key) {
String safeKey = safeKeyGenerator.getSafeKey(key);
try {
getDiskCache().remove(safeKey);
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unable to delete from disk cache", e);
}
}
}
//DisLruCache
public synchronized boolean remove(String key) throws IOException {
checkNotClosed();
Entry entry = lruEntries.get(key);
//在completeEdit()中會將currentEditor置為null
if (entry == null || entry.currentEditor != null) {
return false;
}
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
//刪除CleanFile
if (file.exists() && !file.delete()) {
throw new IOException("failed to delete " + file);
}
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
redundantOpCount++;
//往日志文件添加一條狀態為REMOVE的記錄
journalWriter.append(REMOVE);
journalWriter.append(' ');
journalWriter.append(key);
journalWriter.append('\n');
//在LinkedHashMap中也刪除
lruEntries.remove(key);
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return true;
}
clear()
刪除全部磁盤緩存數據。
@Override
public synchronized void clear() {
try {
getDiskCache().delete();
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.WARN)) {
Log.w(TAG, "Unable to clear disk cache or disk cache cleared externally", e);
}
} finally {
resetDiskCache(); //diskLruCache = null;
}
}
//DiskLruCache
public void delete() throws IOException {
close();
Util.deleteContents(directory);
}
public synchronized void close() throws IOException {
if (journalWriter == null) {
//如果之前已經執行過close()的則直接返回
return;
}
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
if (entry.currentEditor != null) {
//刪除DirtyFile,接著在LinkedHashMap中刪除對應的數據,最后往日志文件添加一條狀態為REMOVE的記錄
entry.currentEditor.abort();
}
}
trimToSize();
journalWriter.close();
journalWriter = null;
}
public void abort() throws IOException {
//在上面有分析,第二個參數不一樣而已
completeEdit(this, false);
}
//如果文件存在則刪除
private static void deleteIfExists(File file) throws IOException {
if (file.exists() && !file.delete()) {
throw new IOException();
}
}
//遞歸刪除文件夾及里面的文件
static void deleteContents(File dir) throws IOException {
File[] files = dir.listFiles();
if (files == null) {
throw new IOException("not a readable directory: " + dir);
}
for (File file : files) {
if (file.isDirectory()) {
deleteContents(file);
}
if (!file.delete()) {
throw new IOException("failed to delete file: " + file);
}
}
}
總結
DiskLruCacheWrapper實現了DiskCache接口來提供磁盤緩存的操作,但是具體的操作則是由DiskLruCache來實現。DiskLruCache內部同樣是使用了LinkedHashMap來實現最近最少使用原則,同時還會將所有的操作記錄寫入日志文件。當初始化時LinkedHashMap就可以根據日志文件來恢復數據和使用順序。