Bitmap加載與緩存相關
概述
Bitmap在Android中指的是一張圖片,可以是各種常見的圖片格式。可以使用BitmapFactory類來加載一個圖片。BitmapFactory提供了四類方法:decodeFile、decodeResource、decodeStream和decodeByteArray,分別用於支持從文件系統、資源、輸入流以及字節數組中加載出一個Bitmap對象。
高效加載Bitmap
高效加載Bitmap的思想是,採用Bitmap.Options來加載所需尺寸的圖片。通過options按一定採樣率來加載縮小後的圖片,可以降低內存佔用,從而來一定程度上避免OOM。
通過BitmapFactory.Options來縮放圖片,用到它的inSampleSize參數(採樣率)。inSampleSize必須是大於1的整數(值應總是2的指數,如果不是,會被向下取整並選擇一個最接近2的指數來代替。小於1時會被當做1。),設置後可以將圖片的寬高都縮小(1/inSampleSize)倍,相應地,縮放比例爲(1/inSampleSize的2次方)。
獲取採樣率的流程:
①、將BitmapFactory.Options的inJustDecodeBounds參數設爲true並加載圖片(設爲true後,BitmapFactory只會解析圖片的寬高信息,而不會真正加載圖片,故這個操作是輕量級的)。
②、從BitmapFactory.Options中取出圖片的原始寬高信息,對應於outWidth和outHeight。
③、根據採樣率的規則並結合目標View的所需大小計算出採樣率inSampleSize。
④、將BitmapFactory.Options的inJustDecodeBounds參數設爲false,然後重新加載圖片。
代碼如下:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
private static 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;
}
在上面的calculateInSampleSize是計算圖片的採樣率。使採樣率爲“寬或高的最大縮放值”中的較小的一個。
內存緩存(LruCache)
目前常用的一種緩存算法是LRU(Least Recently Used,最近最少使用)。思想是,當緩存滿時,優先淘汰那些近期最少使用的緩存對象。採用LRU算法的緩存有兩種:LruCache(實現內存緩存)和DiskLruCache(實現存儲設備緩存)。
LruCache是Android 3.1提供的一個緩存類。內部採用LinkedHashMap以強引用的方式存儲外界的緩存對象,其提供了get和put方法來完成緩存的獲取和添加操作,當緩存滿時,LruCache會移除較早使用的緩存對象。使用LinkedHashMap的原因是它保留了插入順序,使得輸出順序和輸入順序一致,在put方法和get方法執行create之後,會調用到trimToSize方法來根據需要移除之前的緩存,使用LinkedHashMap有助於優先移除最近最少使用的緩存。
使用LruCache只需要提供緩存的總容量大小並重寫sizeOf方法即可。在需要的情況下,也可以重寫entryRemoved方法,在其中完成一些資源回收工作。一個使用的例子如下:
private static int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
private static int cacheSize = maxMemory / 8;
private static LruCache<String, Bitmap> mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
下面看下它的put方法:
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
比較簡單。先判斷key和value是否爲null,是的話拋異常。否則計算要緩存的內容的大小,然後使用map.put方法將其加進去。map.put方法會替換並返回當前key之前已經保存的value(如果沒有就返回null)。若返回的不是null,則說明map.put方法執行的是替換,故把size相應減小。同時回調entryRemoved方法。最後調用trimToSize方法,判斷是否需要移除一些緩存以使緩存容量符合maxSize。可以看到put方法中添加緩存的代碼塊是由synchronized修飾的,故是線程安全的。
再看下get方法:
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
/*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
get方法也比較簡單。首先從map中提取key對應的緩存,如果存在就直接返回。如果不存在,就嘗試去創建一個,我們可以通過重寫create方法來進行創建(猜測用途是從存儲設備中獲取並緩存進內存)。創建成功後就把它添加進map中,思路和put方法中差不多。
存儲設備緩存(DiskLruCache)
基本用法:
①、首先創建DiskLruCache實例:
DiskLruCache mDiskLruCache = DiskLruCache.open(File directory, int appVersion, int valueCount, long maxSize);
②、獲取SnapShot:
SnapShot snapShot = mDiskLruCache.get(key);
③、對snapShot第一次判空:
I、若爲空,則從網上下載:
DiskLruCache.Editor editor = mDiskLruCache.edit(key); //打開Editor
OutputStream os = editor.newOutputStream(0);
...... //從網上下載,寫入os輸出流。
if(下載成功){
editor.commit();
} else{
editor.abort();
}
mDiskLruCache.flush();
II、若不爲空,則取緩存:
FileInputStream fis = snapShot.getInputStream(0);
FileDescriptor fileDescriptor = fis.get(0);
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
以上是大致總結,更具體看郭霖博文:http://blog.csdn.net/guolin_blog/article/details/28863651
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.