Bitmap的加載和Cache

目前比較常用的緩存策略是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常被用作內存緩存,而DiskLruCache常被用作磁盤緩存。Lru是Least Recently Used的縮寫,

LurCache內部其實是用了一個LinkedHashMap來存儲數據的,它的構造如下:

public LruCache(int maxSize) {

if (maxSize <= 0) {

throw new IllegalArgumentException("maxSize <= 0");

}

this.maxSize = maxSize;

this.map = new LinkedHashMap(0, 0.75f, true);

}

構造了一個初始容量為0,負載因子為0.75,accessOrder為true的LinkedHashMap。accessOrder為true意味著鏈表中元素的順序為訪問順序,即調用get方法后,會將這次訪問的元素移至鏈表尾部,這樣最前面的一個元素就是最近最少使用的了。當元素數量達到指定的最大數量之后是怎么刪除的呢?主要是LinkedHashMap中的removeEldestEntry方法。例如可以這樣重寫這個方法:

final int MAX_ENTRIES = 50;

protected boolean removeEldestEntry(Map.Entry eldest) {

return size() > MAX_ENTRIES;

}

這樣當put新元素的時候,如果removeEldestEntry返回了true,就會刪除最老的那個元素。而返回true 的條件就是LinkedHashMap的size大于我們指定的值。這就是LruCache的實現原理。DiskLruCache類似。

下面開始將如何高效的加載Bitmap。

Bitmap 的加載主要是通過BItmapFactory,BitmapFactory有4中方法加載Bitmap:decodeFile,decodeResource,decodeStream和decodeByteArray。其中decodeFile,decodeResource間接調用了decodeStream方法。

很多時候我們圖片的大小是大于ImageView的大小的,這個時候我們就需要對Bitmap進行縮放,如何縮放呢?

主要是通過采樣率來進行縮放。設置采樣率的方式為 BitmapFactory.Options的inSampleSize參數。當inSampleSize為1時,表示是圖片的原始大小不縮放,當inSampleSize大于1,比如為2時,那么采樣后的圖片的寬帶都為原來的1/2,而像素數為原圖的1/4,其占有的內存大小也為1/4。而且采樣率必須為大于1的整數才有效果,當小于1時,其作用相當于1,無縮放效果。另外最新的官方文檔中指出,inSampleSize的取值應該總是2的指數,比如,1,2,4,8,16等等。如果外界傳遞給系統的inSampleSize不為2的指數,那么系統會向下取整并選擇一個最接近2的指數來代替。比如傳3,系統會取2來代替。但是通過驗證,這個結論并非在所有的Android版本都適用,建議開發的時候按2的指數取。

通過采樣率加載可以按照以下步驟:

1.將BitmapFactory.Options的inJustDecodeBounds參數設置為true并加載圖片。(設置為true之后并不會真正的去加載圖片,只會解析圖片的原始寬高。這個操作是輕量級的,但是得注意這個操作獲取的圖片的寬高信息跟圖片的位置以及程序運行的設備有關,比如放在不同的Drawable下面或運行在不同分辨率的機器上)。

2.從BitmapFactory.Options中取出圖片的原始寬高信息,它們對應于outWidth和outHeight參數。

3.根據采樣率的規則并結合目標View的所需大小計算出采樣率inSampleSize。

4.將BitmapFactory.Options的inJustDecodeBounds參數設為false,然后重新加載圖片。

下面給出一個比較通用的計算采樣率的算法:

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,其中這個算法的關鍵部分可以仔細體會一下。

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方法是計算緩存對象大小的。這里大小需要和總容量的單位一致,上面的為KB。

DiskLruCache提供了open方法來創建自身,如下所示:

public static DiskLruCache open(File dir, int appVersion, int valueCount, long maxSize);

dir表示緩存的文件的目錄。

appVersion表示應用的版本號,一般設為1,版本號發生改變時,DiskLruCache會清空之前所有的緩存文件。

valueCount 表示耽擱節點所對應的數據的個數,一般設為1即可。

maxSize表示緩存的最大值,比如50MB,但是要轉換成byte。

DiskLruCache緩存的添加:

DiskLruCache的緩存的添加時通過Editor完成的,Editor表示一個緩存對象的編輯對象。

例如:

DiskLruCache.Editor editor = mDiskLruCache.edit(key);

if(editor != null){

OutputStream outputStream = editor.newOutputSream(index);

}

因為前面設置了valueCount為1,這里index可以直接傳0;

拿到這個outputSream之后,就可以把從網絡上下載下路的文件流寫入里面,注意最后寫完了,要調一下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是因為,通過采樣率來縮放圖片之后,會對FileInputStream的縮放存在問題,因為FileInputStream是一種有序的文件流,而兩次decodeStream調用影響了文件流的位置屬性,導致了第二次decodeStream時得到的是null;所以這里用文件描述符來解決。

mDiskLruCache.remove(key):刪除某個文件。

mDiskLruCache.delete():刪除所有緩存文件。

其他方法可自行查閱。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容