1. 程式人生 > >Android快取機制&一個快取框架推薦

Android快取機制&一個快取框架推薦

前五行稱為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()。如下一個簡單示例:
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(); 
注意每次呼叫edit()時,會向journal日誌檔案寫入DIRTY為字首的一條記錄;檔案儲存成功後,呼叫commit()時,也會向journal日誌中寫入一條CLEAN為字首的一條記錄,如果失敗,需要呼叫abort(),abort()裡面會向journal檔案寫入一條REMOVE為字首的記錄。   獲取快取資料是通過get()方法實現的,如下一個簡單示例:
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輕量級的資料快取類就是這種實現方式。  

2.3 二級快取

LruCache記憶體快取在解決資料量不是很大的情況下效果不錯,當資料很大時,比圖需要載入大量圖片,LruCache指定的快取容量可能很快被耗盡,此時LruCache頻繁的替換移除淘汰檔案,又頻繁要進行網路請求,很有可能出現OOM,為此,在大量資料的情況下,我們可以將磁碟快取DiskLruCache作為一個二級快取的模式,優化快取方案。 流程就是, (1)當我們需要快取資料的時候,既在記憶體中快取,也將檔案快取到磁碟; (2)當獲取快取檔案時,先嚐試從記憶體快取中獲取,如果存在,則直接返回該檔案;如果不存在,則從磁碟快取中獲取,如果磁碟快取中還沒有,那就只能從網路獲取,獲取到資料後,同時在記憶體和磁碟中進行快取。     下一篇準備根據上面的內容寫一個輕量級的資料快取框架,框架將以LruCache和DiskLruCache結合的策略進行設計,盡請期待。