目前比較常用的緩存策略是LruCache(Android3.1提供)和DiskLruCache(是官方文檔推薦酬蹋,但不屬于Android SDK,需要自行下載源碼編譯)嗦哆。
下載地址: https://android.googlesource.com/platform/libcore/+/android-4.1.1-r1/luni/src/main/java/libcore/io/DiskLruCache.java俊犯。
LruCache常被用作內(nèi)存緩存帽衙,而DiskLruCache常被用作磁盤緩存。Lru是Least Recently Used的縮寫纸巷,
LurCache內(nèi)部其實(shí)是用了一個LinkedHashMap來存儲數(shù)據(jù)的镇草,它的構(gòu)造如下:
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap(0, 0.75f, true);
}
構(gòu)造了一個初始容量為0,負(fù)載因子為0.75瘤旨,accessOrder為true的LinkedHashMap梯啤。accessOrder為true意味著鏈表中元素的順序?yàn)樵L問順序,即調(diào)用get方法后存哲,會將這次訪問的元素移至鏈表尾部条辟,這樣最前面的一個元素就是最近最少使用的了黔夭。當(dāng)元素?cái)?shù)量達(dá)到指定的最大數(shù)量之后是怎么刪除的呢?主要是LinkedHashMap中的removeEldestEntry方法羽嫡。例如可以這樣重寫這個方法:
final int MAX_ENTRIES = 50;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
這樣當(dāng)put新元素的時(shí)候本姥,如果removeEldestEntry返回了true,就會刪除最老的那個元素杭棵。而返回true 的條件就是LinkedHashMap的size大于我們指定的值婚惫。這就是LruCache的實(shí)現(xiàn)原理。DiskLruCache類似魂爪。
下面開始將如何高效的加載Bitmap先舷。
Bitmap 的加載主要是通過BItmapFactory,BitmapFactory有4中方法加載Bitmap:decodeFile,decodeResource,decodeStream和decodeByteArray滓侍。其中decodeFile蒋川,decodeResource間接調(diào)用了decodeStream方法。
很多時(shí)候我們圖片的大小是大于ImageView的大小的撩笆,這個時(shí)候我們就需要對Bitmap進(jìn)行縮放捺球,如何縮放呢?
主要是通過采樣率來進(jìn)行縮放夕冲。設(shè)置采樣率的方式為 BitmapFactory.Options的inSampleSize參數(shù)氮兵。當(dāng)inSampleSize為1時(shí),表示是圖片的原始大小不縮放歹鱼,當(dāng)inSampleSize大于1泣栈,比如為2時(shí),那么采樣后的圖片的寬帶都為原來的1/2弥姻,而像素?cái)?shù)為原圖的1/4南片,其占有的內(nèi)存大小也為1/4。而且采樣率必須為大于1的整數(shù)才有效果庭敦,當(dāng)小于1時(shí)铃绒,其作用相當(dāng)于1,無縮放效果螺捐。另外最新的官方文檔中指出颠悬,inSampleSize的取值應(yīng)該總是2的指數(shù),比如定血,1,2,4,8,16等等赔癌。如果外界傳遞給系統(tǒng)的inSampleSize不為2的指數(shù),那么系統(tǒng)會向下取整并選擇一個最接近2的指數(shù)來代替澜沟。比如傳3灾票,系統(tǒng)會取2來代替。但是通過驗(yàn)證茫虽,這個結(jié)論并非在所有的Android版本都適用刊苍,建議開發(fā)的時(shí)候按2的指數(shù)取既们。
通過采樣率加載可以按照以下步驟:
1.將BitmapFactory.Options的inJustDecodeBounds參數(shù)設(shè)置為true并加載圖片。(設(shè)置為true之后并不會真正的去加載圖片正什,只會解析圖片的原始寬高啥纸。這個操作是輕量級的,但是得注意這個操作獲取的圖片的寬高信息跟圖片的位置以及程序運(yùn)行的設(shè)備有關(guān)婴氮,比如放在不同的Drawable下面或運(yùn)行在不同分辨率的機(jī)器上)斯棒。
2.從BitmapFactory.Options中取出圖片的原始寬高信息,它們對應(yīng)于outWidth和outHeight參數(shù)主经。
3.根據(jù)采樣率的規(guī)則并結(jié)合目標(biāo)View的所需大小計(jì)算出采樣率inSampleSize荣暮。
4.將BitmapFactory.Options的inJustDecodeBounds參數(shù)設(shè)為false,然后重新加載圖片罩驻。
下面給出一個比較通用的計(jì)算采樣率的算法:
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight){
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize=1;
if(height > reqHeight || width > reqWidth){
final int halfHeight = height /2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) > = reqWidth){
inSampleSize *=2;
}
}
return inSampleSize;
}
傳入的options為解析后的options穗酥,其中這個算法的關(guān)鍵部分可以仔細(xì)體會一下。
LruCache的典型初始化代碼:
int maxMemory = (int) ((Runtime.getRuntime().maxMemory()) / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
sizeOf方法是計(jì)算緩存對象大小的惠遏。這里大小需要和總?cè)萘康膯挝灰恢吕荆厦娴臑镵B。
DiskLruCache提供了open方法來創(chuàng)建自身爽哎,如下所示:
public static DiskLruCache open(File dir, int appVersion, int valueCount, long maxSize);
dir表示緩存的文件的目錄蜓席。
appVersion表示應(yīng)用的版本號器一,一般設(shè)為1课锌,版本號發(fā)生改變時(shí),DiskLruCache會清空之前所有的緩存文件祈秕。
valueCount 表示耽擱節(jié)點(diǎn)所對應(yīng)的數(shù)據(jù)的個數(shù)渺贤,一般設(shè)為1即可。
maxSize表示緩存的最大值请毛,比如50MB志鞍,但是要轉(zhuǎn)換成byte。
DiskLruCache緩存的添加:
DiskLruCache的緩存的添加時(shí)通過Editor完成的方仿,Editor表示一個緩存對象的編輯對象固棚。
例如:
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor != null){
OutputStream outputStream = editor.newOutputSream(index);
}
因?yàn)榍懊嬖O(shè)置了valueCount為1,這里index可以直接傳0仙蚜;
拿到這個outputSream之后此洲,就可以把從網(wǎng)絡(luò)上下載下路的文件流寫入里面,注意最后寫完了委粉,要調(diào)一下editor的commit方法呜师,即editor.commit();
DiskLruCache緩存的查找:
Bitmap bitmap = null;
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if(snapShot != null){
FileInputStream fileInputStream = (FileInputStream)snapShot.getInputStream(index);
FileDescriptor fileDescriptor = fileInputStream.getFD();
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
之所以通過fileDescriptor是因?yàn)椋ㄟ^采樣率來縮放圖片之后贾节,會對FileInputStream的縮放存在問題汁汗,因?yàn)镕ileInputStream是一種有序的文件流衷畦,而兩次decodeStream調(diào)用影響了文件流的位置屬性,導(dǎo)致了第二次decodeStream時(shí)得到的是null知牌;所以這里用文件描述符來解決祈争。
mDiskLruCache.remove(key):刪除某個文件。
mDiskLruCache.delete():刪除所有緩存文件送爸。
其他方法可自行查閱铛嘱。