Android快取機制&一個快取框架推薦
阿新 • • 發佈:2019-02-09
前五行稱為journal日誌檔案的頭,下面部分的每一行會以四種字首之一開始:DIRTY、CLEAN、REMOVE、READ。
以一個DIRTY字首開始的,後面緊跟著快取圖片的key。以DIRTY這個這個字首開頭,意味著這是一條髒資料。每當我們呼叫一次DiskLruCache的edit()方法時,都會向journal檔案中寫入一條DIRTY記錄,表示我們正準備寫入一條快取資料,但不知結果如何。然後呼叫commit()方法表示寫入快取成功,這時會向journal中寫入一條CLEAN記錄,意味著這條“髒”資料被“洗乾淨了”,呼叫abort()方法表示寫入快取失敗,這時會向journal中寫入一條REMOVE記錄。也就是說,每一行DIRTY的key,後面都應該有一行對應的CLEAN或者REMOVE的記錄,否則這條資料就是“髒”的,會被自動刪除掉。
在CLEAN字首和key後面還有一個數值,代表的是該條快取資料的大小。
因此,我們可以總結DiskLruCache中的工作流程:
1)初始化:通過open()方法,獲取DiskLruCache的例項,在open方法中通過readJournal(); 方法讀取journal日誌檔案,根據journal日誌檔案資訊建立map中的初始資料;然後再呼叫processJournal();方法對剛剛建立起的map資料進行分析,分析的工作,一個是計算當前有效快取檔案(即被CLEAN的)的大小,一個是清理無用快取檔案;
2)資料快取與獲取快取:上面的初始化工作完成後,我們就可以在程式中進行資料的快取功能和獲取快取的功能了;
快取資料的操作是藉助DiskLruCache.Editor這個類完成的,這個類也是不能new的,需要呼叫DiskLruCache的edit()方法來獲取例項,如下所示:
publicEditoredit(Stringkey)throwsIOException 在寫入完成後,需要進行commit()。如下一個簡單示例:注意每次呼叫edit()時,會向journal日誌檔案寫入DIRTY為字首的一條記錄;檔案儲存成功後,呼叫commit()時,也會向journal日誌中寫入一條CLEAN為字首的一條記錄,如果失敗,需要呼叫abort(),abort()裡面會向journal檔案寫入一條REMOVE為字首的記錄。 獲取快取資料是通過get()方法實現的,如下一個簡單示例:new Thread(new Runnable() { @Override public void run() { try { String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg"; String key = hashKeyForDisk(imageUrl); //MD5對url進行加密,這個主要是為了獲得統一的16位字元 DiskLruCache.Editor editor = mDiskLruCache.edit(key); //拿到Editor,往journal日誌中寫入DIRTY記錄 if (editor != null) { OutputStream outputStream = editor.newOutputStream(0); if (downloadUrlToStream(imageUrl, outputStream)) { //downloadUrlToStream方法為下載圖片的方法,並且將輸出流放到outputStream editor.commit(); //完成後記得commit(),成功後,再往journal日誌中寫入CLEAN記錄 } else { editor.abort(); //失敗後,要remove快取檔案,往journal檔案中寫入REMOVE記錄 } } mDiskLruCache.flush(); //將快取操作同步到journal日誌檔案,不一定要在這裡就呼叫 } catch (IOException e) { e.printStackTrace(); } } }).start();
try {
String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";
String key = hashKeyForDisk(imageUrl); //MD5對url進行加密,這個主要是為了獲得統一的16位字元
//通過get拿到value的Snapshot,裡面封裝了輸入流、key等資訊,呼叫get會向journal檔案寫入READ為字首的記錄
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
Bitmap bitmap = BitmapFactory.decodeStream(is);
mImage.setImageBitmap(bitmap);
}
} catch (IOException e) {
e.printStackTrace();
}
3)合適的地方進行flush()
在上面進行資料快取或獲取快取的時候,呼叫不同的方法會往journal中寫入不同字首的一行記錄,記錄寫入是通過IO下的Writer寫入的,要真正生效,還需要呼叫writer的flush()方法,而DiskLruCache中的flush()方法中封裝了writer.flush()的操作,因此,我們只需要在合適地方呼叫DiskLruCache中的flush()方法即可。其作用也就是將操作記錄同步到journal檔案中,這是一個消耗效率的IO操作,我們不用每次一往journal中寫資料後就呼叫flush,這樣對效率影響較大,可以在Activity的onPause()中呼叫一下即可。
小結&注意:
(1)我們可以在在UI執行緒中檢測記憶體快取,即主執行緒中可以直接使用LruCache;
(2)使用DiskLruCache時,由於快取或獲取都需要對本地檔案進行操作,因此需要另開一個執行緒,在子執行緒中檢測磁碟快取、儲存快取資料,磁碟操作從來不應該在UI執行緒中實現;
(3)LruCache記憶體快取的核心是LinkedHashMap,而DiskLruCache的核心是LinkedHashMap和journal日誌檔案,相當於把journal看作是一塊“記憶體”,LinkedHashMap的value只儲存檔案的簡要資訊,對快取檔案的所有操作都會記錄在journal日誌檔案中。
DiskLruCache可能的優化方案:
DiskLruCache是基於日誌檔案journal的,這就決定了每次對快取檔案的操作都需要進行日誌檔案的記錄,我們可以不用journal檔案,在第一次構造DiskLruCache的時候,直接從程式訪問快取目錄下的快取檔案,並將每個快取檔案的訪問時間作為初始值記錄在map的value中,每次訪問或儲存快取都更新相應key對應的快取檔案的訪問時間,這樣就避免了頻繁的IO操作,這種情況下就需要使用單例模式對DiskLruCache進行構造了,上面的Acache輕量級的資料快取類就是這種實現方式。