今天要講的緩存策略(緩存層分為三層:內(nèi)存層,磁盤層,網(wǎng)絡(luò)層)。
當我們第一次打開應用獲取圖片時,先到網(wǎng)絡(luò)去下載圖片,然后依次存入內(nèi)存緩存,磁盤緩存,當我們再一次需要用到剛才下載的這張圖片時,就不需要再重復的到網(wǎng)絡(luò)上去下載,直接可以從內(nèi)存緩存和磁盤緩存中找,由于內(nèi)存緩存速度較快,我們優(yōu)先到內(nèi)存緩存中尋找該圖片,如果找到則運用,如果沒有找到(內(nèi)存緩存大小有限),那么我們再到磁盤緩存中去找。只要我們合理的去協(xié)調(diào)這三層緩存運用,便可以提升應用性能和用戶體驗。
1、內(nèi)存層:(手機內(nèi)存)
內(nèi)存緩存相對于磁盤緩存而言,速度要來的快很多,但缺點容量較小且會被系統(tǒng)回收,這里的實現(xiàn)我用到了LruCache。LruCache這個類是Android3.1版本中提供的,如果你是在更早的Android版本中開發(fā),則需要導入android-support-v4的jar包。
2、磁盤層:(SD卡)
相比內(nèi)存緩存而言速度要來得慢很多,但容量很大,這里的實現(xiàn)我用到了DiskLruCache類。DiskLruCache是非Google官方編寫,但獲得官方認證的硬盤緩存類,該類沒有限定在Android內(nèi),所以理論上java應用也可以使用DiskLreCache來緩存。這是DiskLruCache類的下載地址:http://pan.baidu.com/s/1o6tPjz8
3、網(wǎng)絡(luò)層:(移動網(wǎng)絡(luò),無線網(wǎng)絡(luò))
原理
Android原生為我們提供了一個LruCache,其中維護著一個LinkedHashMap。LruCache可以用來存儲各種類型的數(shù)據(jù),但最常見的是存儲圖片(Bitmap)。LruCache創(chuàng)建LruCache時,我們需要設(shè)置它的大小,一般是系統(tǒng)最大存儲空間的八分之一。LruCache的機制是存儲最近、最后使用的圖片,如果LruCache中的圖片大小超過了其默認大小,則會將最老、最遠使用的圖片移除出去。
當圖片被LruCache移除的時候,我們需要手動將這張圖片添加到軟引用(SoftReference)中。我們需要在項目中維護一個由SoftReference組成的集合,其中存儲被LruCache移除出來的圖片。軟引用的一個好處是當系統(tǒng)空間緊張的時候,軟引用可以隨時銷毀,因此軟引用是不會影響系統(tǒng)運行的,換句話說,如果系統(tǒng)因為某個原因OOM了,那么這個原因肯定不是軟引用引起的。
下面敘述一下三級緩存的流程:
當我們的APP中想要加載某張圖片時,先去LruCache中尋找圖片,如果LruCache中有,則直接取出來使用,如果LruCache中沒有,則去SoftReference中尋找,如果SoftReference中有,則從SoftReference中取出圖片使用,同時將圖片重新放回到LruCache中,如果SoftReference中也沒有圖片,則去文件系統(tǒng)中尋找,如果有則取出來使用,同時將圖片添加到LruCache中,如果沒有,則連接網(wǎng)絡(luò)從網(wǎng)上下載圖片。圖片下載完成后,將圖片保存到文件系統(tǒng)中,然后放到LruCache中。
LruCache子類ImageCache:
import android.graphics.Bitmap;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.LruCache;
import java.lang.ref.SoftReference;
import java.util.Map;
/**
* 圖片緩存
*/
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)
public class ImageCache extends LruCache<String, Bitmap> {
private Map<String, SoftReference<Bitmap>> cacheMap;
public ImageCache(Map<String, SoftReference<Bitmap>> cacheMap) {
super((int) (Runtime.getRuntime().maxMemory() / 8));
this.cacheMap = cacheMap;
}
@Override // 獲取圖片大小
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override // 當有圖片從LruCache中移除時,將其放進軟引用集合中
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != null) {
SoftReference<Bitmap> softReference = new SoftReference<Bitmap>(oldValue);
cacheMap.put(key, softReference);
}
}
public Map<String, SoftReference<Bitmap>> getCacheMap() {
return cacheMap;
}
}
三級緩存工具類ImageCacheLoader:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.widget.ImageView;
import java.io.File;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
/**
* 緩存工具類
*/
public class CacheUtil {
private static CacheUtil instance;
private Context context;
private ImageCache imageCache;
private CacheUtil(Context context) {
this.context = context;
Map<String, SoftReference<Bitmap>> cacheMap = new HashMap<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { // SDK版本判斷
this.imageCache = new ImageCache(cacheMap);
}
}
public static CacheUtil getInstance(Context context) {
if (instance == null) {
synchronized (CacheUtil.class) {
if (instance == null) {
instance = new CacheUtil(context);
}
}
}
return instance;
}
/**
* 將圖片添加到緩存中
*/
private void putBitmapIntoCache(String fileName, byte[] data) {
// 將圖片的字節(jié)數(shù)組寫入到內(nèi)存中
FileUtil.getInstance(context).writeFileToStorage(fileName, data);
// 將圖片存入強引用(LruCache)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
imageCache.put(fileName, BitmapFactory.decodeByteArray(data, 0, data.length));
}
}
/**
* 從緩存中取出圖片
*/
private Bitmap getBitmapFromCache(String fileName) {
// 從強引用(LruCache)中取出圖片
Bitmap bm = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) { // SDK版本判斷
bm = imageCache.get(fileName);
if (bm == null) {
// 如果圖片不存在強引用中,則去軟引用(SoftReference)中查找
Map<String, SoftReference<Bitmap>> cacheMap = imageCache.getCacheMap();
SoftReference<Bitmap> softReference = cacheMap.get(fileName);
if (softReference != null) {
bm = softReference.get();
imageCache.put(fileName, bm);
} else {
// 如果圖片不存在軟引用中,則去內(nèi)存中找
byte[] data = FileUtil.getInstance(context).readBytesFromStorage(fileName);
if (data != null && data.length > 0) {
bm = BitmapFactory.decodeByteArray(data, 0, data.length);
imageCache.put(fileName, bm);
}
}
}
}
return bm;
}
/**
* 使用三級緩存為ImageView設(shè)置圖片
*/
public void setImageToView(final String path, final ImageView view) {
final String fileName = path.substring(path.lastIndexOf(File.separator) + 1);
Bitmap bm = getBitmapFromCache(fileName);
if (bm != null) {
view.setImageBitmap(bm);
} else {
// 從網(wǎng)絡(luò)獲取圖片
new Thread(new Runnable() {
@Override
public void run() {
byte[] b = HttpUtil.getInstance().getByteArrayFromWeb(path);
if (b != null && b.length > 0) {
// 將圖片字節(jié)數(shù)組寫入到緩存中
putBitmapIntoCache(fileName, b);
final Bitmap bm = BitmapFactory.decodeByteArray(b, 0, b.length);
// 將從網(wǎng)絡(luò)獲取到的圖片設(shè)置給ImageView
view.post(new Runnable() {
@Override
public void run() {
view.setImageBitmap(bm);
}
});
}
}
}).start();
}
}
}