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


     


     

























發佈了36 篇原創文章 · 獲贊 3 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章