為什么會用到緩存呢充尉?主要是流量耗不起啊,國內(nèi)的公共場所的WiFi的普及率不高衣形,因此必須考慮流量的問題驼侠,說白了,就是用戶體驗啊谆吴,每次都網(wǎng)絡(luò)請求倒源,消耗資源不說,網(wǎng)速不好的情況下還會有網(wǎng)絡(luò)延時句狼,用戶體驗不好笋熬。
Android中的緩存,從方式上來說腻菇,一般有網(wǎng)絡(luò)緩存突诬,磁盤緩存即SD卡緩存,內(nèi)存緩存芜繁。網(wǎng)絡(luò)緩存需要服務端的配合旺隙,用于加快網(wǎng)絡(luò)請求的響應速度。磁盤緩存一般用DiskLruCache骏令,當然也可以用SqlLite數(shù)據(jù)庫蔬捷,以及sharedpreference等作持久化處理。這里主要說下兩種常用的緩存方法,LruCache周拐、DiskLruCache铡俐。前者用于內(nèi)存緩存,后者用于設(shè)備緩存妥粟,一般兩者結(jié)合起來效果更好审丘。
其實緩存的實現(xiàn)并不難,每一中緩存都會有三個基本操作勾给,添加滩报、獲取、刪除播急。了解這些了脓钾,就會有思路了。
再說LruCache桩警、DiskLruCache可训,可以看到,兩者都有Lru捶枢,那么Lru是什么呢握截?這是目前常用的一種緩存算法:近期最少使用算法,核心思想很簡單烂叔,就是當緩存滿時川蒙,會優(yōu)先刪除那些近期最少使用的緩存。那么現(xiàn)在分別了解下這兩種緩存吧长已。
LruCache
LruCache內(nèi)部用到的是LinkedHashMap畜眨,LinkedHashMap與HashMap的不同住處在于LinkedHashMap 維護著一個運行于所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序术瓮,該迭代順序可以是插入順序或者是訪問順序康聂。也就說它的插入和訪問是有順序的。另外LruCache是線程安全的胞四。至于使用的話就很簡單了恬汁。
// 初始化
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
總緩存大小一般會設(shè)置為當前進程可用內(nèi)存的1/8,當然這個數(shù)是可以自己設(shè)置的辜伟,這個數(shù)是推薦的氓侧。sizeOf方法是為了計算緩存對象的大小。如果有必要也可以重寫entryRemoved來完成某些資源回收工作导狡。
再看緩存的添加與刪除约巷,
//添加緩存
mMemoryCache.put(key,bitmap);
//獲取緩存
mMemoryCache.get(key);
//刪除緩存
mMemoryCache.remove(key);
DiskLruCache
DiskLruCache用與磁盤緩存,被官方推薦使用旱捧。下面來看看它的使用独郎。
自從用了Gradle后踩麦,引入項目方便多了,誰用誰知道氓癌。
compile 'com.jakewharton:disklrucache:2.0.2'
創(chuàng)建DiskLruCache:
DiskLruCache mDiskLruCache = null;
try {
File cacheDir = getDiskCacheDir(context, "bitmap");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
} ```
解釋下DiskLruCache.open的參數(shù)谓谦,第一個表示存儲的路徑,第二個表示應用的版本號贪婉,注意這里當版本號發(fā)生改變時會清空之前所有的緩存文件反粥,而在實際開發(fā)中這個性質(zhì)用的不多,所以直接寫1疲迂。第三個表示單個節(jié)點對應的數(shù)據(jù)的個數(shù)才顿,設(shè)置為1就可以了,第四個表示緩存的總大小鬼譬,當超出這個值時娜膘,會清除一些緩存保證總大小不大于這個設(shè)定的值逊脯。
添加緩存:
第一步优质,網(wǎng)絡(luò)下載圖片(文件也是一樣的步驟的)并通過outputStream寫入到本地
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;
}
第二步,處理緩存的key军洼,直接用url作為key值時最有快捷的方式巩螃,但是url里會有特殊字符,不符合Android的命名規(guī)范匕争,最好的辦法就是把url進行MD5摘要避乏。
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();
} ```
第三步 創(chuàng)建DiskLruCache.Editor的實例,寫入數(shù)據(jù)
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(); ```
editor.commit()方法用來提交寫入操作甘桑,editor.abort()回退整個操作拍皮。
讀取緩存:
Bitmap bitmap = null;
String key = hashKeyFormUrl(url);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(0);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,
reqWidth, reqHeight);
if (bitmap != null) {
addBitmapToMemoryCache(key, bitmap);
}
} ```
public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fd, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fd, null, options);
}
需要說明下的是為了避免加載圖片時導致OOM,不建議直接加在Bitmap跑杭,通常我們會通過BitmapFactory.Options來加載一張縮放的圖片铆帽,但是這中方法對于FileInputStream有問題,因為FileInputStream是有序的文件流德谅,而兩次的從的 decodeStream調(diào)用影響了文件流的位置屬性爹橱,導致第二次decodeStream時得到的為null。為了解決這個問題窄做,可以先得到對應的文件描述符愧驱,然后通過BitmapFactory.decodeFileDescriptor()來加載圖片。
移除緩存:
mDiskLruCache.remove(key);