1. 程式人生 > >Android避免載入圖片出現OOM

Android避免載入圖片出現OOM

很多時候在使用BitmapFactory.decode*解碼圖片的時候會出現記憶體不足。主要有以下幾個原因導致這個問題:

  • 移動裝置通常都限制了系統資源,一般每一個應用最小可以限制到16M。應用程式應該優化到應用能在這個最小記憶體正常執行,但是,很多裝置都配置更高的限制。
  • Bitmap佔用了大量的記憶體,尤其是內容豐富的影象照片。例如,在Galaxy Nexus One的相機拍攝的照片達2592x1936畫素(5百萬畫素)。如果使用Bitmap的配置是ARGB_8888(從Android2.3以後的預設設定),然後載入這個影象到記憶體大約需要的記憶體(2592*1936*4位元組)19MB,應用程式在某些裝置上使用馬上就有了限制。
  • Android應用程式使用者介面的經常需要幾個Bitmap一次載入。如ListView的元件,GridView和ViewPager通常包括多個Bitmap在螢幕上。
知道了原因我們可以針對這些原因來優化,比如現在網路上的圖片是1280×960,那麼使用ARG_8888的方式載入到記憶體就會佔用1280×960×4 =約4.9M,但是我們在裝置上顯示的時候不可能使用這麼大的解析度,因為比如一個頭像可能的大小是128×96或者其他解析度,載入一個完整的大圖就是浪費記憶體。所以我們可以通過BitmapFactory.Options類指定解碼選項來解碼縮小後的圖片到記憶體。BitmapFactory.Options
類有很多屬性,這裡會用到的就是outWidth,outHeight分別對應的是圖片的寬和高,inJustDecodeBounds這個屬性非常重要,因為它可以讓我們不載入圖片到記憶體就能知道圖片的大小和型別。設定inJustDecodeBounds是true,可以避免在解碼的時候分配記憶體,會返回Null的Bitmap物件,但是會設定outWidthoutHeightoutMimeType.可以像下面這樣獲取到圖片的大小:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

到此為止我們就獲取到了即將要載入到記憶體的圖片的尺寸,接下來我們會用到BitmapFactory.Options中一個非常有用的屬性:inSampleSize。它的作用是告訴解碼器解碼圖片的時候的模板量,什麼意思呢?簡單的理解它是一個量詞,預設它是1,如果你設定成2,那麼解碼出來的圖片的大小會減小一半,也就是說100×100的圖片如果使用inSampleSize=2的選項去載入,那麼解碼出來的圖片就是50×50.這個屬性有一個非常重要的特點就是解碼器在解碼的時候會四捨五入這個屬性最接近2的冪(意思就是我們使用的時候這個屬性最好是2的冪)。google官方推薦了一種計算inSampleSize的方式:

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    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;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}
好了,我們已經獲取到了即將載入圖片的尺寸和需要縮放的模板量,接下來我們把inJustDecodeBounds設定成false,把inSampleSize設定成我們計算出來的大小,然後再一次通過BitmapFactory.decode*解碼圖片就完成了。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

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

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}
當然解碼的這個方法不要放到UI執行緒去操作,因為我們不能確定會載入多長時間。

如果有什麼問題,或者需要指教的地方請一定告訴我。