android DiskLruCache使用方法
下載
好了,對(duì)DiskLruCache有了最初的認(rèn)識(shí)之后,下面我們來(lái)學(xué)習(xí)一下DiskLruCache的用法吧。由于DiskLruCache并不是由Google官方編寫的,所以這個(gè)類并沒(méi)有被包含在Android?API當(dāng)中,我們需要將這個(gè)類從網(wǎng)上下載下來(lái),然后手動(dòng)添加到項(xiàng)目當(dāng)中。DiskLruCache的源碼在Google?Source上,地址在文章底部,下載好了源碼之后,只需要在項(xiàng)目中新建一個(gè)libcore.io包,然后將DiskLruCache.java文件復(fù)制到這個(gè)包中即可。
打開(kāi)緩存
這樣的話我們就把準(zhǔn)備工作做好了,下面看一下DiskLruCache到底該如何使用。首先你要知道,DiskLruCache是不能new出實(shí)例的,如果我們要?jiǎng)?chuàng)建一個(gè)DiskLruCache的實(shí)例,則需要調(diào)用它的open()方法,接口如下所示:
1
public?static?DiskLruCache?open(File?directory,?int?appVersion,?int?valueCount,?long?maxSize)
open()方法接收四個(gè)參數(shù),第一個(gè)參數(shù)指定的是數(shù)據(jù)的緩存地址,第二個(gè)參數(shù)指定當(dāng)前應(yīng)用程序的版本號(hào),第三個(gè)參數(shù)指定同一個(gè)key可以對(duì)應(yīng)多少個(gè)緩存文件,基本都是傳1,第四個(gè)參數(shù)指定最多可以緩存多少字節(jié)的數(shù)據(jù)。
其中緩存地址前面已經(jīng)說(shuō)過(guò)了,通常都會(huì)存放在?/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);??
}
可以看到,當(dāng)SD卡存在或者SD卡不可被移除的時(shí)候,就調(diào)用getExternalCacheDir()方法來(lái)獲取緩存路徑,否則就調(diào)用getCacheDir()方法來(lái)獲取緩存路徑。前者獲取到的就是?/sdcard/Android/data/
接著又將獲取到的路徑和一個(gè)uniqueName進(jìn)行拼接,作為最終的緩存路徑返回。那么這個(gè)uniqueName又是什么呢?其實(shí)這就是為了對(duì)不同類型的數(shù)據(jù)進(jìn)行區(qū)分而設(shè)定的一個(gè)唯一值,比如說(shuō)在網(wǎng)易新聞緩存路徑下看到的bitmap、object等文件夾。
接著是應(yīng)用程序版本號(hào),我們可以使用如下代碼簡(jiǎn)單地獲取到當(dāng)前應(yīng)用程序的版本號(hào):
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;??
}
需要注意的是,每當(dāng)版本號(hào)改變,緩存路徑下存儲(chǔ)的所有數(shù)據(jù)都會(huì)被清除掉,因?yàn)镈iskLruCache認(rèn)為當(dāng)應(yīng)用程序有版本更新的時(shí)候,所有的數(shù)據(jù)都應(yīng)該從網(wǎng)上重新獲取。
后面兩個(gè)參數(shù)就沒(méi)什么需要解釋的了,第三個(gè)參數(shù)傳1,第四個(gè)參數(shù)通常傳入10M的大小就夠了,這個(gè)可以根據(jù)自身的情況進(jìn)行調(diào)節(jié)。
因此,一個(gè)非常標(biāo)準(zhǔn)的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();??
}
首先調(diào)用getDiskCacheDir()方法獲取到緩存地址的路徑,然后判斷一下該路徑是否存在,如果不存在就創(chuàng)建一下。接著調(diào)用DiskLruCache的open()方法來(lái)創(chuàng)建實(shí)例,并把四個(gè)參數(shù)傳入即可。
有了DiskLruCache的實(shí)例之后,我們就可以對(duì)緩存的數(shù)據(jù)進(jìn)行操作了,操作類型主要包括寫入、訪問(wèn)、移除等,我們一個(gè)個(gè)進(jìn)行學(xué)習(xí)。
寫入緩存
先來(lái)看寫入,比如說(shuō)現(xiàn)在有一張圖片,地址是http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg,那么為了將這張圖片下載下來(lái),就可以這樣寫:
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;??
}
這段代碼相當(dāng)基礎(chǔ),相信大家都看得懂,就是訪問(wèn)urlString中傳入的網(wǎng)址,并通過(guò)outputStream寫入到本地。有了這個(gè)方法之后,下面我們就可以使用DiskLruCache來(lái)進(jìn)行寫入了,寫入的操作是借助DiskLruCache.Editor這個(gè)類完成的。類似地,這個(gè)類也是不能new的,需要調(diào)用DiskLruCache的edit()方法來(lái)獲取實(shí)例,接口如下所示:
雙擊代碼復(fù)制
1
public?Editor?edit(String?key)?throws?IOException
可以看到,edit()方法接收一個(gè)參數(shù)key,這個(gè)key將會(huì)成為緩存文件的文件名,并且必須要和圖片的URL是一一對(duì)應(yīng)的。那么怎樣才能讓key和圖片的URL能夠一一對(duì)應(yīng)呢?直接使用URL來(lái)作為key?不太合適,因?yàn)閳D片URL中可能包含一些特殊字符,這些字符有可能在命名文件時(shí)是不合法的。其實(shí)最簡(jiǎn)單的做法就是將圖片的URL進(jìn)行MD5編碼,編碼后的字符串肯定是唯一的,并且只會(huì)包含0-F這樣的字符,完全符合文件的命名規(guī)則。
那么我們就寫一個(gè)方法用來(lái)將字符串進(jìn)行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();??
}
代碼很簡(jiǎn)單,現(xiàn)在我們只需要調(diào)用一下hashKeyForDisk()方法,并把圖片的URL傳入到這個(gè)方法中,就可以得到對(duì)應(yīng)的key了。
因此,現(xiàn)在就可以這樣寫來(lái)得到一個(gè)DiskLruCache.Editor的實(shí)例:
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的實(shí)例之后,我們可以調(diào)用它的newOutputStream()方法來(lái)創(chuàng)建一個(gè)輸出流,然后把它傳入到downloadUrlToStream()中就能實(shí)現(xiàn)下載并寫入緩存的功能了。注意newOutputStream()方法接收一個(gè)index參數(shù),由于前面在設(shè)置valueCount的時(shí)候指定的是1,所以這里index傳0就可以了。在寫入操作執(zhí)行完之后,我們還需要調(diào)用一下commit()方法進(jìn)行提交才能使寫入生效,調(diào)用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();
由于這里調(diào)用了downloadUrlToStream()方法來(lái)從網(wǎng)絡(luò)上下載圖片,所以一定要確保這段代碼是在子線程當(dāng)中執(zhí)行的。注意在代碼的最后我還調(diào)用了一下flush()方法,這個(gè)方法并不是每次寫入都必須要調(diào)用的,但在這里卻不可缺少,我會(huì)在后面說(shuō)明它的作用。
現(xiàn)在的話緩存應(yīng)該是已經(jīng)成功寫入了,我們進(jìn)入到SD卡上的緩存目錄里看一下,如下圖所示:
可以看到,這里有一個(gè)文件名很長(zhǎng)的文件,和一個(gè)journal文件,那個(gè)文件名很長(zhǎng)的文件自然就是緩存的圖片了,因?yàn)槭鞘褂昧薓D5編碼來(lái)進(jìn)行命名的。
讀取緩存
緩存已經(jīng)寫入成功之后,接下來(lái)我們就該學(xué)習(xí)一下如何讀取了。讀取的方法要比寫入簡(jiǎn)單一些,主要是借助DiskLruCache的get()方法實(shí)現(xiàn)的,接口如下所示:
1
public?synchronized?Snapshot?get(String?key)?throws?IOException
很明顯,get()方法要求傳入一個(gè)key來(lái)獲取到相應(yīng)的緩存數(shù)據(jù),而這個(gè)key毫無(wú)疑問(wèn)就是將圖片URL進(jìn)行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);
很奇怪的是,這里獲取到的是一個(gè)DiskLruCache.Snapshot對(duì)象,這個(gè)對(duì)象我們?cè)撛趺蠢媚??很?jiǎn)單,只需要調(diào)用它的getInputStream()方法就可以得到緩存文件的輸入流了。同樣地,getInputStream()方法也需要傳一個(gè)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對(duì)象,然后把它設(shè)置到ImageView當(dāng)中。如果運(yùn)行一下程序,將會(huì)看到如下效果:
OK,圖片已經(jīng)成功顯示出來(lái)了。注意這是我們從本地緩存中加載的,而不是從網(wǎng)絡(luò)上加載的,因此即使在你手機(jī)沒(méi)有聯(lián)網(wǎng)的情況下,這張圖片仍然可以顯示出來(lái)。
移除緩存
學(xué)習(xí)完了寫入緩存和讀取緩存的方法之后,最難的兩個(gè)操作你就都已經(jīng)掌握了,那么接下來(lái)要學(xué)習(xí)的移除緩存對(duì)你來(lái)說(shuō)也一定非常輕松了。移除緩存主要是借助DiskLruCache的remove()方法實(shí)現(xiàn)的,接口如下所示:
1
public?synchronized?boolean?remove(String?key)?throws?IOException
相信你已經(jīng)相當(dāng)熟悉了,remove()方法中要求傳入一個(gè)key,然后會(huì)刪除這個(gè)key對(duì)應(yīng)的緩存圖片,示例代碼如下:
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();??
}
用法雖然簡(jiǎn)單,但是你要知道,這個(gè)方法我們并不應(yīng)該經(jīng)常去調(diào)用它。因?yàn)槟阃耆恍枰獡?dān)心緩存的數(shù)據(jù)過(guò)多從而占用SD卡太多空間的問(wèn)題,DiskLruCache會(huì)根據(jù)我們?cè)谡{(diào)用open()方法時(shí)設(shè)定的緩存最大值來(lái)自動(dòng)刪除多余的緩存。只有你確定某個(gè)key對(duì)應(yīng)的緩存內(nèi)容已經(jīng)過(guò)期,需要從網(wǎng)絡(luò)獲取最新數(shù)據(jù)的時(shí)候才應(yīng)該調(diào)用remove()方法來(lái)移除緩存。
其它API
除了寫入緩存、讀取緩存、移除緩存之外,DiskLruCache還提供了另外一些比較常用的API,我們簡(jiǎn)單學(xué)習(xí)一下。
1.?size()
這個(gè)方法會(huì)返回當(dāng)前緩存路徑下所有緩存數(shù)據(jù)的總字節(jié)數(shù),以byte為單位,如果應(yīng)用程序中需要在界面上顯示當(dāng)前緩存數(shù)據(jù)的總大小,就可以通過(guò)調(diào)用這個(gè)方法計(jì)算出來(lái)。比如網(wǎng)易新聞中就有這樣一個(gè)功能,如下圖所示:
2.flush()
這個(gè)方法用于將內(nèi)存中的操作記錄同步到日志文件(也就是journal文件)當(dāng)中。這個(gè)方法非常重要,因?yàn)镈iskLruCache能夠正常工作的前提就是要依賴于journal文件中的內(nèi)容。前面在講解寫入緩存操作的時(shí)候我有調(diào)用過(guò)一次這個(gè)方法,但其實(shí)并不是每次寫入緩存都要調(diào)用一次flush()方法的,頻繁地調(diào)用并不會(huì)帶來(lái)任何好處,只會(huì)額外增加同步j(luò)ournal文件的時(shí)間。比較標(biāo)準(zhǔn)的做法就是在Activity的onPause()方法中去調(diào)用一次flush()方法就可以了。
3.close()
這個(gè)方法用于將DiskLruCache關(guān)閉掉,是和open()方法對(duì)應(yīng)的一個(gè)方法。關(guān)閉掉了之后就不能再調(diào)用DiskLruCache中任何操作緩存數(shù)據(jù)的方法,通常只應(yīng)該在Activity的onDestroy()方法中去調(diào)用close()方法。
4.delete()
這個(gè)方法用于將所有的緩存數(shù)據(jù)全部刪除,比如說(shuō)網(wǎng)易新聞中的那個(gè)手動(dòng)清理緩存功能,其實(shí)只需要調(diào)用一下DiskLruCache的delete()方法就可以實(shí)現(xiàn)了。