2010年8月27日 星期五

[Android] 謹慎使用Bitmap來避免OutOfMemoryError

一般行動裝置像是手機, 記憶體(RAM)是有限的, 不像PC上可以動不動就有2G以上的記憶體可用, 既然是這樣, 在手機上就不可能任意隨我們揮霍記憶體, 在Android上, 對每個程式能使用的記憶體也有其限制, 每隻程式, 能用的Java heap, 除非手機廠商有特別改過, 要不然HVGA的裝置一支程式就只能使用16MB的Java Heap, WVGA則只能使用32MB

通常佔用記憶體一個很大的元兇就是圖, 圖在載入顯示後, 在記憶體中是未經壓縮的Bitmap, 所以佔用了相當大的heap空間,而且在開發初期不太容易注意到這問題, 除非開發初期你就是碰巧拿到一張很大的圖擋(比如說千萬像素級相機拍的照片, 一般手機尚未有千萬以上等級, 所以單用手機拍的照片, 還真不容易試到問題)

當然, 把圖片縮小就是一個最直覺的解法

翻了API doc你可能會發現Bitmap裡面有個method是用來做rescale bitmap用的:

 

public static Bitmap createScaledBitmap (Bitmap src, int dstWidth, int dstHeight, boolean filter)

 

但其實, 這method並不適用節省memory這目的, 他本身有個陷阱, 也就是你必須要先載入一張原始圖, 才能針對這張圖檔做rescale的動作, 所以除了原始圖外還會有一份縮好的圖檔, 反而更佔空間

正確的解法是, 你必須要從檔案載入時就已經先把它縮小了, 

 

        //Only decode image size. Not whole image

        BitmapFactory.Options option = new BitmapFactory.Options();

        option.inJustDecodeBounds = true;

        BitmapFactory.decodeFile(imagePath, option);

 

        //The new size to decode to 

        final int NEW_SIZE=100;

 

        //Now we have image width and height. We should find the correct scale value. (power of 2)

        int width=option.outWidth;

        int height=option.outHeight;

        int scale=1;

        while(true){

            if(width/2<NEW_SIZE || height/2<NEW_SIZE)

                break;

            width/=2;

            height/=2;

            scale++;

        }

        //Decode again with inSampleSize

        option = new BitmapFactory.Options();

        option.inSampleSize=scale;

        return BitmapFactory.decodeFile(imagePath, option);

 

BitmapFactory可以在載入圖時指定scale的比例, 這樣載入後就不會是原先的大圖了, 缺點是, 要decode兩次, 第一次要使用option.inJustDecodeBounds = true; 讓它不是decode整張圖而是只取得圖的寬高, 第二次才是真正把整張圖decode放到記憶體內, 第二個缺點是, 它的scale算法是以1/2的次方, 沒辦法剛好是你指定的大小

如果把這方式跟createScaledBitmap加起來應用, 那就可以在不造成OutOfMemoryError狀況下, 又可以取得一張符合程式需要顯示大小的圖片了

 

 

 

1 則留言: