天天看點

三分鐘學會緩存工具DiskLruCache

DiskLruCache是一個十分好用的android緩存工具,我們可以從GitHub上下載下傳其源碼:https://github.com/JakeWharton/DiskLruCache

DiskLruCache所有的資料都存儲在

/storage/emulated/0/Android/data/應用包名/cache/XXX

檔案夾中(你也可以修改,但不建議這樣做,原因請繼續往下看),這個是android系統預設的應用緩存位置,如果應用被删除,這個檔案也會一起被删除,避免應用删除後有殘留資料的問題。同時,由于資料沒有存儲在硬碟裡,是以不會影響系統性能,在sd卡裡,你可以存儲任意多資料。

由于DiskLruCache是被final修飾的,是以不可以直接通過new獲得它的執行個體,我們使用它的open方法獲得它的一個執行個體:

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

open方法需要四個參數,第一個是緩存檔案檔案的位置,通過下面的方法可得到:

private File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        //如果sd卡存在并且沒有被移除
        if (Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState())
                || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }
           

第二個參數是應用程式的版本号,要傳入版本号是因為如果應用更新緩存會被清除掉。通過下面的方法可以獲得程式的版本号:

private int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(
                    context.getPackageName(), );
            return info.versionCode;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        return ;
    }
           

第三個參數表示同一個key可以對應多少個緩存檔案,一般情況下我們都是傳1,這樣key和緩存檔案一一對應,查找和移除都會比較友善。

第四個參數表示最大可以緩存多少位元組的資料。

打開了DiskLruCache之後,我們可以看看怎麼向DiskLruCache中緩存資料:

先來看看從網上down一張圖檔:

private boolean downloadImg(final String urlStr,
            final OutputStream outputStream) {
        HttpURLConnection conn = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            URL url = new URL(urlStr);
            conn = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(conn.getInputStream(),  * );
            out = new BufferedOutputStream(outputStream,  * );
            int len = ;
            while ((len = in.read()) != -) {
                out.write(len);
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (conn != null)
                conn.disconnect();
            try {
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (in != null)
                    in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }
           

這是一個簡單的聯網down圖檔代碼,拿到圖檔後就可以緩存到本地了,但是對于每一個存儲資源都需要有一個key,這個key要是唯一的,而且這個key最長120個字元,且隻能包括a-z,0-9,下劃線以及減号,一次我們可以采用Java中的UUID來得到key,也可以使用MD5加密網址得到一個key,我這裡采用md5,方法如下:

public class MD5Util {

    public final static String md5(String pwd) {
        //用于加密的字元
        char md5String[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'A', 'B', 'C', 'D', 'E', 'F' };
        try {
            //使用平台的預設字元集将此 String 編碼為 byte序列,并将結果存儲到一個新的 byte數組中
            byte[] btInput = pwd.getBytes();

            // 獲得指定摘要算法的 MessageDigest對象,此處為MD5
            //MessageDigest類為應用程式提供資訊摘要算法的功能,如 MD5 或 SHA 算法。
            //資訊摘要是安全的單向哈希函數,它接收任意大小的資料,并輸出固定長度的哈希值。 
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            //System.out.println(mdInst);  
            //MD5 Message Digest from SUN, <initialized>

            //MessageDigest對象通過使用 update方法處理資料, 使用指定的byte數組更新摘要
            mdInst.update(btInput);
            //System.out.println(mdInst);  
            //MD5 Message Digest from SUN, <in progress>

            // 摘要更新之後,通過調用digest()執行哈希計算,獲得密文
            byte[] md = mdInst.digest();
            //System.out.println(md);

            // 把密文轉換成十六進制的字元串形式
            int j = md.length;
            //System.out.println(j);
            char str[] = new char[j * ];
            int k = ;
            for (int i = ; i < j; i++) {   //  i = 0
                byte byte0 = md[i];  //95
                str[k++] = md5String[byte0 >>>  & ];    //    5  
                str[k++] = md5String[byte0 & ];   //   F
            }

            //傳回經過加密後的字元串
            return new String(str);

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
           

各位看官在使用的時候記得把md5String[]中大寫的字母改為小寫,因為key中如果有大寫字母驗證會不通過。當然,你也可以修改DiskLruCache的源碼進而讓它支援大寫字母,修改的地方:

三分鐘學會緩存工具DiskLruCache

現在萬事俱備,我們來把圖檔緩存起來,由于聯網是好事操作,是以要在新線程中完成,完整的方法如下:

private void cacheImg() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                String key = MD5Util.md5(IMGIP);
                try {
                    DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                    if (editor != null) {
                        OutputStream out = editor.newOutputStream();
                        if (downloadImg(IMGIP, out)) {
                            //送出
                            editor.commit();
                        } else {
                            //撤銷操作
                            editor.abort();
                        }
                    }
                    /**
                     * 這個方法用于将記憶體中的操作記錄同步到日志檔案(也就是journal檔案)當中。
                     * 這個方法非常重要,因為DiskLruCache能夠正常工作的前提就是要依賴于journal檔案中的内容。
                     * 并不是每次寫入緩存都要調用一次flush()方法的,頻繁地調用并不會帶來任何好處,
                     * 隻會額外增加同步journal檔案的時間。
                     * 比較标準的做法就是在Activity的onPause()方法中去調用一次flush()方法就可以了
                     */
                    mDiskLruCache.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
           

editor.newOutputStream(0);方法有一個參數,檢視源碼我們知道這個參數必須大于0并且小于valueCount,前文中valueCount我們已經設定為1了,是以這裡隻能取值0。這個時候打開你的緩存檔案夾,

/storage/emulated/0/Android/data/應用包名/cache/XXX

,裡邊已經有了我們緩存的資料了:

三分鐘學會緩存工具DiskLruCache

好了,資料存下來了,接下來就是讀取,每一個緩存檔案都對應一個key,讀取就是根據這個key來讀取:

private void showImg() {
        String key = MD5Util.md5(IMGIP);  
        try {
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
            if(snapShot!=null){
                InputStream is = snapShot.getInputStream();
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                im.setImageBitmap(bitmap);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
           

讀取的時候我們最先拿到的是一個Snapshot 對象,再根據我們之前傳入的參數0拿到緩存檔案的流,最後把流轉換為圖檔。

到這裡大家可能就明白了,之前的editor.newOutputStream(0);方法為什麼會有一個0的參數了,相當于一個辨別,讀取時也傳入參數0才能讀到我們想要的資料。(加入我們的key與緩存檔案不是一一對應,也就是我們一開始的open方法中傳入的不是valueCount的值不是1,那麼一個key對應多個緩存檔案我們要怎麼區分?就是通過這種方式,有興趣的同學檢視源碼就一目了然了)。

下來就是清除緩存了,看方法:

private void clearCache() {
        String key = MD5Util.md5(IMGIP);
        try {
            mDiskLruCache.remove(key);
        } catch (IOException e) {
            e.printStackTrace();
        }  
    }
           

根據緩存檔案的key,調用remove方法,将該緩存檔案移除。

下來是檢視緩存大小:

三分鐘學會緩存工具DiskLruCache

像鳳凰新聞用戶端中顯示緩存大小,這個數值我們可以通過size()方法直接拿到:

private void getCacheSize() {
        tv.setText(mDiskLruCache.size()+"");
    }
           

大家應該看到了鳳凰新聞還有一個功能就是清除緩存,這個功能直接調用delete方法就能實作:

private void deleteAll() {
        /**
         * 這個方法用于将所有的緩存資料全部删除
         * 其實隻需要調用一下DiskLruCache的delete()方法就可以實作了。
         * 會删除包括日志檔案在内的所有檔案
         */
        try {
            mDiskLruCache.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
           

所有功能都完成之後,我們要記得在onDestory方法中關閉DiskLruCache。

@Override
    protected void onDestroy() {
        super.onDestroy();
        /**
         * 這個方法用于将DiskLruCache關閉掉,是和open()方法對應的一個方法。
         * 關閉掉了之後就不能再調用DiskLruCache中任何操作緩存資料的方法,
         * 通常隻應該在Activity的onDestroy()方法中去調用close()方法。
         */
        try {
            mDiskLruCache.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
           

到此,我想大家已經基本會用這個東東了吧。

最後奉上本文源碼下載下傳位址http://pan.baidu.com/s/1kTzSHtd