android DiskLruCache使用方法
下載
好了,對DiskLruCache有了最初的認識之后,下面我們來學習一下DiskLruCache的用法吧。由于DiskLruCache并不是由Google官方編寫的,所以這個類并沒有被包含在Android?API當中,我們需要將這個類從網上下載下來,然后手動添加到項目當中。DiskLruCache的源碼在Google?Source上,地址在文章底部,下載好了源碼之后,只需要在項目中新建一個libcore.io包,然后將DiskLruCache.java文件復制到這個包中即可。
打開緩存
這樣的話我們就把準備工作做好了,下面看一下DiskLruCache到底該如何使用。首先你要知道,DiskLruCache是不能new出實例的,如果我們要創(chuàng)建一個DiskLruCache的實例,則需要調用它的open()方法,接口如下所示:
1
public?static?DiskLruCache?open(File?directory,?int?appVersion,?int?valueCount,?long?maxSize)
open()方法接收四個參數(shù),第一個參數(shù)指定的是數(shù)據(jù)的緩存地址,第二個參數(shù)指定當前應用程序的版本號,第三個參數(shù)指定同一個key可以對應多少個緩存文件,基本都是傳1,第四個參數(shù)指定最多可以緩存多少字節(jié)的數(shù)據(jù)。
其中緩存地址前面已經說過了,通常都會存放在?/sdcard/Android/data/
1
2
3
4
5
6
7
8
9
10
public?File?getDiskCacheDir(Context?context,?String?uniqueName)?{??
????String?cachePath;??
????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);??
}
可以看到,當SD卡存在或者SD卡不可被移除的時候,就調用getExternalCacheDir()方法來獲取緩存路徑,否則就調用getCacheDir()方法來獲取緩存路徑。前者獲取到的就是?/sdcard/Android/data/
接著又將獲取到的路徑和一個uniqueName進行拼接,作為最終的緩存路徑返回。那么這個uniqueName又是什么呢?其實這就是為了對不同類型的數(shù)據(jù)進行區(qū)分而設定的一個唯一值,比如說在網易新聞緩存路徑下看到的bitmap、object等文件夾。
接著是應用程序版本號,我們可以使用如下代碼簡單地獲取到當前應用程序的版本號:
1
2
3
4
5
6
7
8
9
public?int?getAppVersion(Context?context)?{??
????try?{??
????????PackageInfo?info?=?context.getPackageManager().getPackageInfo(context.getPackageName(),?0);??
????????return?info.versionCode;??
????}?catch?(NameNotFoundException?e)?{??
????????e.printStackTrace();??
????}??
????return?1;??
}
需要注意的是,每當版本號改變,緩存路徑下存儲的所有數(shù)據(jù)都會被清除掉,因為DiskLruCache認為當應用程序有版本更新的時候,所有的數(shù)據(jù)都應該從網上重新獲取。
后面兩個參數(shù)就沒什么需要解釋的了,第三個參數(shù)傳1,第四個參數(shù)通常傳入10M的大小就夠了,這個可以根據(jù)自身的情況進行調節(jié)。
因此,一個非常標準的open()方法就可以這樣寫:
1
2
3
4
5
6
7
8
9
10
DiskLruCache?mDiskLruCache?=?null;??
try?{??
????File?cacheDir?=?getDiskCacheDir(context,?"bitmap");??
????if?(!cacheDir.exists())?{??
????????cacheDir.mkdirs();??
????}??
????mDiskLruCache?=?DiskLruCache.open(cacheDir,?getAppVersion(context),?1,?10?*?1024?*?1024);??
}?catch?(IOException?e)?{??
????e.printStackTrace();??
}
首先調用getDiskCacheDir()方法獲取到緩存地址的路徑,然后判斷一下該路徑是否存在,如果不存在就創(chuàng)建一下。接著調用DiskLruCache的open()方法來創(chuàng)建實例,并把四個參數(shù)傳入即可。
有了DiskLruCache的實例之后,我們就可以對緩存的數(shù)據(jù)進行操作了,操作類型主要包括寫入、訪問、移除等,我們一個個進行學習。
寫入緩存
先來看寫入,比如說現(xiàn)在有一張圖片,地址是http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg,那么為了將這張圖片下載下來,就可以這樣寫:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private?boolean?downloadUrlToStream(String?urlString,?OutputStream?outputStream)?{??
????HttpURLConnection?urlConnection?=?null;??
????BufferedOutputStream?out?=?null;??
????BufferedInputStream?in?=?null;??
????try?{??
????????final?URL?url?=?new?URL(urlString);??
????????urlConnection?=?(HttpURLConnection)?url.openConnection();??
????????in?=?new?BufferedInputStream(urlConnection.getInputStream(),?8?*?1024);??
????????out?=?new?BufferedOutputStream(outputStream,?8?*?1024);??
????????int?b;??
????????while?((b?=?in.read())?!=?-1)?{??
????????????out.write(b);??
????????}??
????????return?true;??
????}?catch?(final?IOException?e)?{??
????????e.printStackTrace();??
????}?finally?{??
????????if?(urlConnection?!=?null)?{??
????????????urlConnection.disconnect();??
????????}??
????????try?{??
????????????if?(out?!=?null)?{??
????????????????out.close();??
????????????}??
????????????if?(in?!=?null)?{??
????????????????in.close();??
????????????}??
????????}?catch?(final?IOException?e)?{??
????????????e.printStackTrace();??
????????}??
????}??
????return?false;??
}
這段代碼相當基礎,相信大家都看得懂,就是訪問urlString中傳入的網址,并通過outputStream寫入到本地。有了這個方法之后,下面我們就可以使用DiskLruCache來進行寫入了,寫入的操作是借助DiskLruCache.Editor這個類完成的。類似地,這個類也是不能new的,需要調用DiskLruCache的edit()方法來獲取實例,接口如下所示:
雙擊代碼復制
1
public?Editor?edit(String?key)?throws?IOException
可以看到,edit()方法接收一個參數(shù)key,這個key將會成為緩存文件的文件名,并且必須要和圖片的URL是一一對應的。那么怎樣才能讓key和圖片的URL能夠一一對應呢?直接使用URL來作為key?不太合適,因為圖片URL中可能包含一些特殊字符,這些字符有可能在命名文件時是不合法的。其實最簡單的做法就是將圖片的URL進行MD5編碼,編碼后的字符串肯定是唯一的,并且只會包含0-F這樣的字符,完全符合文件的命名規(guī)則。
那么我們就寫一個方法用來將字符串進行MD5編碼,代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public?String?hashKeyForDisk(String?key)?{??
????String?cacheKey;??
????try?{??
????????final?MessageDigest?mDigest?=?MessageDigest.getInstance("MD5");??
????????mDigest.update(key.getBytes());??
????????cacheKey?=?bytesToHexString(mDigest.digest());??
????}?catch?(NoSuchAlgorithmException?e)?{??
????????cacheKey?=?String.valueOf(key.hashCode());??
????}??
????return?cacheKey;??
}??
???
private?String?bytesToHexString(byte[]?bytes)?{??
????StringBuilder?sb?=?new?StringBuilder();??
????for?(int?i?=?0;?i?<?bytes.length;?i++)?{??
????????String?hex?=?Integer.toHexString(0xFF?&?bytes[i]);??
????????if?(hex.length()?==?1)?{??
????????????sb.append('0');??
????????}??
????????sb.append(hex);??
????}??
????return?sb.toString();??
}
代碼很簡單,現(xiàn)在我們只需要調用一下hashKeyForDisk()方法,并把圖片的URL傳入到這個方法中,就可以得到對應的key了。
因此,現(xiàn)在就可以這樣寫來得到一個DiskLruCache.Editor的實例:
1
2
3
String?imageUrl?=?"http://img.21ic.com/21ic_pic/CSDN/=utf-8' '1378037235_7476.jpg";??
String?key?=?hashKeyForDisk(imageUrl);??
DiskLruCache.Editor?editor?=?mDiskLruCache.edit(key);
有了DiskLruCache.Editor的實例之后,我們可以調用它的newOutputStream()方法來創(chuàng)建一個輸出流,然后把它傳入到downloadUrlToStream()中就能實現(xiàn)下載并寫入緩存的功能了。注意newOutputStream()方法接收一個index參數(shù),由于前面在設置valueCount的時候指定的是1,所以這里index傳0就可以了。在寫入操作執(zhí)行完之后,我們還需要調用一下commit()方法進行提交才能使寫入生效,調用abort()方法的話則表示放棄此次寫入。
因此,一次完整寫入操作的代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
new?Thread(new?Runnable()?{??
????@Override?
????public?void?run()?{??
????????try?{??
????????????String?imageUrl?=?"http://img.21ic.com/21ic_pic/CSDN/=utf-8' '1378037235_7476.jpg";??
????????????String?key?=?hashKeyForDisk(imageUrl);??
????????????DiskLruCache.Editor?editor?=?mDiskLruCache.edit(key);??
????????????if?(editor?!=?null)?{??
????????????????OutputStream?outputStream?=?editor.newOutputStream(0);??
????????????????if?(downloadUrlToStream(imageUrl,?outputStream))?{??
????????????????????editor.commit();??
????????????????}?else?{??
????????????????????editor.abort();??
????????????????}??
????????????}??
????????????mDiskLruCache.flush();??
????????}?catch?(IOException?e)?{??
????????????e.printStackTrace();??
????????}??
????}??
}).start();
由于這里調用了downloadUrlToStream()方法來從網絡上下載圖片,所以一定要確保這段代碼是在子線程當中執(zhí)行的。注意在代碼的最后我還調用了一下flush()方法,這個方法并不是每次寫入都必須要調用的,但在這里卻不可缺少,我會在后面說明它的作用。
現(xiàn)在的話緩存應該是已經成功寫入了,我們進入到SD卡上的緩存目錄里看一下,如下圖所示:
可以看到,這里有一個文件名很長的文件,和一個journal文件,那個文件名很長的文件自然就是緩存的圖片了,因為是使用了MD5編碼來進行命名的。
讀取緩存
緩存已經寫入成功之后,接下來我們就該學習一下如何讀取了。讀取的方法要比寫入簡單一些,主要是借助DiskLruCache的get()方法實現(xiàn)的,接口如下所示:
1
public?synchronized?Snapshot?get(String?key)?throws?IOException
很明顯,get()方法要求傳入一個key來獲取到相應的緩存數(shù)據(jù),而這個key毫無疑問就是將圖片URL進行MD5編碼后的值了,因此讀取緩存數(shù)據(jù)的代碼就可以這樣寫:
1
2
3
String?imageUrl?=?"http://img.21ic.com/21ic_pic/CSDN/=utf-8' '1378037235_7476.jpg";??
String?key?=?hashKeyForDisk(imageUrl);??
DiskLruCache.Snapshot?snapShot?=?mDiskLruCache.get(key);
很奇怪的是,這里獲取到的是一個DiskLruCache.Snapshot對象,這個對象我們該怎么利用呢?很簡單,只需要調用它的getInputStream()方法就可以得到緩存文件的輸入流了。同樣地,getInputStream()方法也需要傳一個index參數(shù),這里傳入0就好。有了文件的輸入流之后,想要把緩存圖片顯示到界面上就輕而易舉了。所以,一段完整的讀取緩存,并將圖片加載到界面上的代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
try?{??
????String?imageUrl?=?"http://img.21ic.com/21ic_pic/CSDN/=utf-8' '1378037235_7476.jpg";??
????String?key?=?hashKeyForDisk(imageUrl);??
????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();??
}
?
我們使用了BitmapFactory的decodeStream()方法將文件流解析成Bitmap對象,然后把它設置到ImageView當中。如果運行一下程序,將會看到如下效果:
OK,圖片已經成功顯示出來了。注意這是我們從本地緩存中加載的,而不是從網絡上加載的,因此即使在你手機沒有聯(lián)網的情況下,這張圖片仍然可以顯示出來。
移除緩存
學習完了寫入緩存和讀取緩存的方法之后,最難的兩個操作你就都已經掌握了,那么接下來要學習的移除緩存對你來說也一定非常輕松了。移除緩存主要是借助DiskLruCache的remove()方法實現(xiàn)的,接口如下所示:
1
public?synchronized?boolean?remove(String?key)?throws?IOException
相信你已經相當熟悉了,remove()方法中要求傳入一個key,然后會刪除這個key對應的緩存圖片,示例代碼如下:
1
2
3
4
5
6
7
try?{??
????String?imageUrl?=?"http://img.21ic.com/21ic_pic/CSDN/=utf-8' '1378037235_7476.jpg";????
????String?key?=?hashKeyForDisk(imageUrl);????
????mDiskLruCache.remove(key);??
}?catch?(IOException?e)?{??
????e.printStackTrace();??
}
用法雖然簡單,但是你要知道,這個方法我們并不應該經常去調用它。因為你完全不需要擔心緩存的數(shù)據(jù)過多從而占用SD卡太多空間的問題,DiskLruCache會根據(jù)我們在調用open()方法時設定的緩存最大值來自動刪除多余的緩存。只有你確定某個key對應的緩存內容已經過期,需要從網絡獲取最新數(shù)據(jù)的時候才應該調用remove()方法來移除緩存。
其它API
除了寫入緩存、讀取緩存、移除緩存之外,DiskLruCache還提供了另外一些比較常用的API,我們簡單學習一下。
1.?size()
這個方法會返回當前緩存路徑下所有緩存數(shù)據(jù)的總字節(jié)數(shù),以byte為單位,如果應用程序中需要在界面上顯示當前緩存數(shù)據(jù)的總大小,就可以通過調用這個方法計算出來。比如網易新聞中就有這樣一個功能,如下圖所示:
2.flush()
這個方法用于將內存中的操作記錄同步到日志文件(也就是journal文件)當中。這個方法非常重要,因為DiskLruCache能夠正常工作的前提就是要依賴于journal文件中的內容。前面在講解寫入緩存操作的時候我有調用過一次這個方法,但其實并不是每次寫入緩存都要調用一次flush()方法的,頻繁地調用并不會帶來任何好處,只會額外增加同步journal文件的時間。比較標準的做法就是在Activity的onPause()方法中去調用一次flush()方法就可以了。
3.close()
這個方法用于將DiskLruCache關閉掉,是和open()方法對應的一個方法。關閉掉了之后就不能再調用DiskLruCache中任何操作緩存數(shù)據(jù)的方法,通常只應該在Activity的onDestroy()方法中去調用close()方法。
4.delete()
這個方法用于將所有的緩存數(shù)據(jù)全部刪除,比如說網易新聞中的那個手動清理緩存功能,其實只需要調用一下DiskLruCache的delete()方法就可以實現(xiàn)了。