上一節(jié),我們了解了LruCache的使用糙及,我們知道LruCache是從內(nèi)存中去獲取緩存信息豪嗽。但內(nèi)存大小是有限的珠十,因此無法存取大量的緩存。從而荠诬,引入了DiskLruCache琅翻,磁盤緩存,將信息緩存到手機中柑贞。
LruCache和DiskLruCache的區(qū)別:
共同點:兩者都是緩存信息方椎,并且都是采用LRU算法。
不同點:LruCache讀寫速度快钧嘶,但存儲大小有限棠众。DiskLruCache讀寫速度慢,但存儲空間大有决。
DiskLruCache的創(chuàng)建:
DiskLruCache并不能通過構(gòu)造方法來創(chuàng)建闸拿,它提供了open方法用于創(chuàng)建自身:
/**
* 用于創(chuàng)建DiskLruCache
* 參數(shù)一:表示磁盤在文件系統(tǒng)中存儲的路徑
* 參數(shù)二:表示應用的版本號,一般設為1书幕,當版本號發(fā)生改變時新荤,會將之前的緩存信息清空
* 參數(shù)三:表示單個節(jié)點所對應的數(shù)據(jù)的個數(shù),一般設為1
* 參數(shù)四:表示緩存的總大小台汇,當緩存大小超過這個設定值后苛骨,DiskLruCache會清除一些緩存,從而保證不會超過這個設定值
*/
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize);
具體實現(xiàn)如下:
//定義DiskLruCache
private DiskLruCache mDiskLruCache;
//設定DiskLruCache的大小,這里設置為50MB
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
//創(chuàng)建緩存文件夾
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
//若緩存文件夾不存在苟呐,則創(chuàng)建文件夾
if (!diskCacheDir.exists()){
diskCacheDir.mkdirs();
}
//創(chuàng)建DiskLruCache
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
public File getDiskCacheDir(Context Context, String uniqueName){
//檢查手機是否支持外部存儲
boolean externalStorageAvailable = Environment.getExternalStorageState()
.equals(Environment.MEDIA_MOUNTED);
final String cachePath;
//若存在外部存儲痒芝,則獲取到其對應的文件
if (externalStorageAvailable) {
cachePath = context.getExternalCacheDir().getPath();
}
//否則獲取內(nèi)部存儲緩存文件夾
else {
cachePath = context.getCacheDir().getPath();
}
//返回緩存文件夾
return new File(cachePath + File.separator + uniqueName);
}
DiskLruCache的緩存添加:
DiskLruCache的緩存添加的操作是通過Editor完成的,Editor表示一個緩存對象的編輯對象牵素。
我們以圖片緩存為例严衬,首先需要獲取圖片url所對應的key,然后根據(jù)key就可以通過edit()來獲取Editor對象笆呆。如果這個對象正在被編輯请琳,那么edit()會返回null,即DiskLruCache不允許同時編輯一個緩存對象腰奋。
因為url中可能含有特殊字符单起,這樣url在Android中直接使用抱怔,一般采用url的md5值作為key劣坊。
private String hashKeyFromUrl(String url){
String cacheKey;
try{
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(url.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch(NoSuchAlgorithmException e){
cacheKey = String.valueOf(url.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();
}
將圖片的url轉(zhuǎn)成key后,就可以獲取Editor對象屈留。對于這個key來說局冰,如果當前不存在其他的Editor對象测蘑,那么editor就會返回一個新的Editor對象,通常它可以得到一個文件輸出流康二。
private final int DISK_CACHE_INDEX = 0;
String key = hashKeyFromUrl(url);
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream os = editor.newOutputStream(DISK_CACHE_INDEX);
}
注意:上面設置DISK_CACHE_INDEX為0碳胳,因為前面在DiskLruCache的open方法中設置了一個節(jié)點只能有一個數(shù)據(jù)。
存在輸出流后沫勿,當網(wǎng)絡下載圖片時挨约,圖片就可以通過這個文件輸出流寫入到系統(tǒng)文件中。
/**
* 實現(xiàn)根據(jù)傳入的urlString從網(wǎng)絡中下載圖片數(shù)據(jù)产雹,然后寫入輸出流os中
*/
public boolean downloadUrlToStream(String urlString, OutputStream os){
//定義連接和輸入輸出流
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(),
IO_BUFFER_SIZE)诫惭;
out = new BufferedOutputStream(os, IO_BUFFER_SIZE);
//進行將網(wǎng)絡數(shù)據(jù)寫入os中
int b = -1;
while ((b = in.read()) != -1){
out.write(b);
}
return true;
} catch (IOException e){
Log.d(TAG, "downloadBitmap failed." + e);
} finally {
//最后關(guān)閉連接和流
if (urlConnection != null) {
urlConnection.disconnect();
}
MyUtils.close(out);
MyUtils.close(in);
}
return false;
}
經(jīng)過上面的步驟,并沒有真正地將圖片寫入文件系統(tǒng)中蔓挖,還必須通過Editor的commit()來提交寫入操作夕土,如果圖片下載過程中出現(xiàn)異常,那么還可以通過Editor的abort()來回退整個操作瘟判。
OutputStream os = editor.newOutputStream(DISK_CACHE_INDEX);
//若寫入成功怨绣,則提交操作
if (downloadUrlToStream(url, os)) {
editor.commit();
}
//否則終止操作
else {
editor.abort();
}
//最后刷新緩存區(qū),將緩存區(qū)中的文件寫入到系統(tǒng)中
mDiskLruCache.flush();
DiskLruCache的緩存查找
- 將url轉(zhuǎn)換為key
- 通過DiskLruCache的get方法得到一個Snapshot對象
- 通過Snapshot對象獲取到緩存的文件輸入流
- 通過輸入流拷获,獲取到Bitmap對象
為了避免加載圖片過程中導致的OOM問題篮撑,一般不建議直接加載原始圖片,而是通過BitmapFactory.Options對象來加載一張縮放后的圖片匆瓜,但是這種方法對FileInputStream的縮放存在問題咽扇,因為FileInputStream是一種有序的文件流,而兩次decodeStream調(diào)用影響了文件流的位置屬性陕壹,導致了第二次decodeStream時得到的是null质欲。為了解決該問題,可以通過文件流來得到它所對應的文件描述符糠馆,然后再通過BitmapFactory.decodeFileDescriptor方法來加載一張縮放后的圖片嘶伟。
//定義bitmap對象
Bitmap bitmap = null;
//將url轉(zhuǎn)換為key
String key = hashKeyFromUrl(url);
//根據(jù)key獲取到SnapShot對象
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
//若Snapshot對象不為空,說明存在該緩存
if (snapShot != null){
//獲取到輸入流
FileInputStream fis = (FileInputStream) snapShot.getInputStream(DISK_CACHE_INDEX);
//根據(jù)輸入流獲取到文件描述符
FileDescriptor fileDescriptor = fis.getFD();
//調(diào)用壓縮圖片的方法又碌,獲取到bitmap對象
bitmap = mImageResizer.decodeSampleBitmapFromFileDescriptor(fileDescriptor,
reqWidth, reqHeight);
//然后將bitmap對象加入到內(nèi)存緩存中
if (bitmap != null){
addBitmapToMemoryCache(key, bitmap);
}
}