Android開發藝術探索 - 第12章 Bitmap的加載和cache

1.Bitmap高效加載

加載Bitmap的方法:使用BitmapFactory的decodeFile/decodeResource/decodeStream/decodeByteArray可以分別從,文件/資源/輸入流/字節數組中加載一個Bitmap。decodeFile/decodeResource會間接調用decodeStream。

通過採樣率控制加載出的Bitmap的大小,以提高加載效率:

  • 將BitmapFactory.Options的inJustDecodeBounds參數設置爲true,並加載圖片
  • 從BitmapFactory.Options中取出圖片的原始寬高信息,即outWidth和outHeight
  • 根據採樣率的規則(2的倍數)以及目標View的大小,計算出inSampleSize的大小
  • 將BitmapFactory.Options的inJustDecodeBounds參數設置爲false,重新加載圖片
public static Bitmap decodeSampleBitmapFromResource(Resources resource, int resId,
                                                    int reqWidth, int reqHeight) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(resource, resId, options);

    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(resource, resId, options);
}
public static int calculateInSampleSize (BitmapFactory.Options options, int reqWidth, int reqHeight) {
    int width = options.outWidth;
    int height = options.outHeight;
    int inSampleSize = 1;
    
    if (height > reqHeight || width > reqWidth) {
        int halfWidth = width / 2;
        int halfHeight = height / 2;
        
        while (halfHeight / inSampleSize >= reqHeight
                && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2;
        }
    }
    
    return inSampleSize;
}

2.Android中的緩存策略

LRU(Least Recently Used):內存緩存LruCache和存儲設備緩存DiskLruCache

  1. LruCache
    內部採用一個LinkedHashMap,以強引用的方式存儲外界的緩存對象。使用方法,設置緩存大小,然後複寫sizeOf計算緩存對象的大小,之後通過put和get方法,添加和獲取緩存對象:
int cacheSize = 4 * 1024 * 1024; // 4MiB
LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
    protected int sizeOf(String key, Bitmap value) {
        return value.getByteCount();
    }
}}

如果緩存對象引用的資源需要顯式的進行釋放,可以複寫entryRemoved方法釋放資源。
當通過get方法成功訪問到一個緩存對象,在LruCache內部會將其移動到隊列頭部;通過put方法添加緩存時,如果緩存已滿,隊列最後一個對象就會被刪除。可以通過remove方法直接刪除一個緩存對象。
LruCache是線程安全的。
2. DiskLruCache
將緩存對象寫入文件系統進行緩存。不在sdk中,需另行下載。源碼

  • 創建過程
    通過DiskLruCache#open方法進行
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
        throws IOException {

參數:

  • directory:緩存目錄
  • appVersion:應用版本號,版本號改變時會清空之前的緩存
  • valueCount:單個key對應的數據個數
  • maxSize:緩存空間大小
private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
File diskCacheDir = getDiskCacheDir(mContext, "bitmap");
if (!diskCacheDir.exists()) {
    diskCacheDir.mkdirs();
} 
mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
  • 添加緩存
    DiskLruCache的緩存添加操作,通過Editor來完成。通過Editor得到一個輸出流:
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
    OutputStream out = editor.newOutputStream(0);   // valueCount爲1,index從0開始
}

將數據寫入到該輸出流之後,調用editor的commit方法,緩存創建完成:

if (downloadUrlToStream(url, outputStream)) {
    editor.commit();
}
  • 獲取緩存
    DiskLruCache的get方法獲取一個Snapshot對象,通過它便可得到輸入流,通過該輸入流便可得到緩存的文件。對於緩存Bitmap時的處理:因爲在加載大圖的時候,需要decode兩次,而輸入流讀取過一次,第二次就會返回null,對於這種情況,可以通過輸入流獲得文件的文件描述,然後調用BitmapFactory.decodeFileDescriptor來加載Bitmap:
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
    FileInputStream in = (FileInputStream) snapshot.getInputStream(0);
    FileDescriptor fd = in.getFD();

    bitmap = decodeSampleBitmapFromFD(fd, reqWidth, reqHeight);
    if (bitmap != null) {
        addBitmapToMemoryCache(key, bitmap);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章