簡體   English   中英

如何避免java.lang.OutOfMemoryError:在使用大位圖時,位圖大小超過了android中的VM預算?

[英]How can I avoid java.lang.OutOfMemoryError: bitmap size exceeds VM budget in android while using a large bitmap?

我意識到有許多類似的問題,但大多數涉及縮小位圖,或顯式調用bitmap.recycle()和System.gc(),它們不保證任何東西(在我的情況下未能防止錯誤)。

編輯:我在創建位圖時也嘗試使用isPurgable = true。

編輯:另外,我只在摩托羅拉Droid上用Froyo(2.2.2)進行了測試。

這是場景:我正在加載一個位圖(寬度:1500,高度:2400)。 這大約需要14 MB。 應用程序的其余部分在內存消耗方面微不足道(容易小於2 MB)。

我正在使用帶有變換矩陣的位圖來平移和縮放表面視圖。 在第一次加載時,這非常有效。 但是,當我退出應用程序並重新啟動它時,我得到了可怕的OutOfMemoryError。 在第三次發射它是有效的,第四次它崩潰......等等。

我不需要保存狀態,所以我嘗試在onPause()(以及上面提到的recycle()和gc()方法中調用finish())。 Finish()似乎會停止所有線程,但不會清除內存。

我應該提一下,我也在使用一種技術,我在這個問題的評論中找到了這種技術 還請檢查一下

因此,我的圖像是從Web加載的,作為一個不可變的位圖。 然后將其字節保存到sdcard(非常慢),以便重新加載回可變位圖。 如果跳過所有這些籃球是可笑的,請教育我...

對於我的情況,清除為應用程序分配的所有內存是可以接受的(如果它不生成崩潰消息)。 無論如何只是完全清除分配給我的應用程序的內存,以便每次重啟都像第一次啟動一樣干凈嗎?

有沒有涉及平鋪的解決方案? 當然我錯過了一些東西..因為圖像文件本身(一個png)只有幾千字節,我在庫存圖庫應用程序中查看了較大的圖像,沒有這個問題。

編輯:我根據從@Jason Lebrun的回答中收集的見解確定了問題的原因。 事實證明,我曾經在這個位圖上繪制的畫布也有一個引用,所以畫布需要設置為null才能正確地進行垃圾回收。 希望這可以幫助有類似問題的人。

您是否在Gingberbread或其他版本上遇到此問題? Gingerbread在使用大量Bitmap內存的應用程序中存在很多問題,因此了解操作系統版本可以幫助確定問題的本質。

在你的情況下,很難確切地說出可能是什么原因。 但是,使用14MB位圖時,即使是第二個實例也可能會耗盡可用堆並導致崩潰。 在Gingerbread上,由於並發GC + Bitmap內存在本機數組中分配的事實,因此很容易最終導致Bitmap內存停留的時間超過應有的時間。

當您退出應用程序時,它可能沒有從內存中卸載。 這可以解釋您的崩潰模式:第一次運行應用程序時,會加載大位圖。 第二次啟動它時,它實際上只是為已經在內存中的進程重新啟動一個Activity,並且由於某種原因,Bitmap的內存仍然占用空間。 因此,應用程序崩潰,並啟動一個新進程,並使用新堆。 如果回收沒有幫助,您可能仍然可以參考之前的Bitmap。 如果你總是使用相同的Bitmap,重復使用靜態引用可能有幫助,雖然我不確定。

您確定Bitmap不會通過泄露的上下文,長時間運行的后台代碼或類似的東西泄露嗎?

這里的其他答案有很好的建議,即在獲取后嘗試平鋪Bitmap,並且只在必要時加載tile。 如果您不需要支持舊版本,可以使用BitmapRegionDecoder (http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html)執行此操作,但僅限支持API級別10的設備或更高。

要擴展@ jtietema的答案,您是否嘗試僅加載/渲染應用轉換矩陣后可見的位圖部分? 您可以通過對整個圖像使用僅限邊界的位圖並對其進行轉換,並使用生成的矩形作為獲取位圖從sd卡的輸入來完成此操作。

您可以使用以下內容來減小樣本量。 我在我的一個應用程序中使用此方法來顯示assets目錄中的圖像。 但你可以使用樣本大小,我已經使用值1表示不大(94kb)的圖像和4表示較大的圖像(1.9mb)

protected void loadImageIntoBitmap( String imageAssetFile, Bitmap bitmap, int sampleSize, ImageView imgvw ) throws IOException {
    InputStream is = getContext().getAssets().open( imageAssetFile );
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = sampleSize;
    bitmap = BitmapFactory.decodeStream( is,null,options );
    imgvw.setImageBitmap( bitmap );

    is.close();
}

你在用位圖做什么? 分辨率高於你的Android設備的分辨率,所以如果你正在查看整個位圖而不是縮小它就可以了。

如果您正在放大,您可以只創建位圖的一個子集,只加載可見的部分。

無論何時將圖像保存到SD卡,您都可以使用較低質量的圖像,如:

myBitmap.compress(Bitmap.CompressFormat.PNG, 85, fileOutputStream);

和/或還使用bitmapFactory選項獲取較小的圖像以節省內存:

BitmapFactory.Options factoryOptions= new BitmapFactory.Options();
factoryOptions.inSampleSize = samplesize;

請注意,調用recicle()和gc()並不意味着資源將立即釋放。 正如文檔所說:

來自myBitmap.recycle()

釋放與此位圖關聯的本機對象,並清除對像素數據的引用。 這不會同步釋放像素數據; 如果沒有其他引用,它只是允許它被垃圾收集 位圖標記為“死”,這意味着如果調用getPixels()或setPixels(),它將拋出異常,並且不會繪制任何內容。 此操作無法撤消,因此只有在您確定位圖沒有進一步用途時才應調用此操作。 這是一個高級調用,通常不需要調用,因為正常的GC進程將在沒有更多對此位圖的引用時釋放此內存。

System.gc()

向VM表明運行垃圾收集器是一個好時機。 請注意,這只是一個提示。 無法保證垃圾收集器實際運行

因此,您的應用程序可能仍在使用資源,然后每當您重新打開應用程序時,系統仍在使用資源加上您正在生成的新資源,這可能是您遇到內存不足錯誤的原因。 因此,在簡短的處理中,內存中的大圖像並不是一個好主意。

此方法接收圖像路徑並為您提供可繪制而不會崩潰

為了獲得最佳圖像質量,請始終使用參數imageSizeDivide的value = 1調用此方法

public Drawable recurseCompressAndGetImage(String image_path,
        int imageSizeDivide) {
    try {

        Log.w("", "imageSizeDivide = " + imageSizeDivide);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = imageSizeDivide;// controls the quality of image

        // Bitmap           
        Bitmap srcBmp = BitmapFactory
                .decodeFile(image_path.trim(), options);

       //next if-else block converts the image into a squire image.Remove this block if u wish
        if (srcBmp.getWidth() >= srcBmp.getHeight()) {

            dstBmp = Bitmap.createBitmap(srcBmp, srcBmp.getWidth() / 2
                    - srcBmp.getHeight() / 2, 0, srcBmp.getHeight(),
                    srcBmp.getHeight());

        } else {

            dstBmp = Bitmap.createBitmap(srcBmp, 0, srcBmp.getHeight() / 2
                    - srcBmp.getWidth() / 2, srcBmp.getWidth(),
                    srcBmp.getWidth());
        }

        dstBmp = Bitmap.createScaledBitmap(dstBmp, 400, 400, true);

        return new BitmapDrawable(mContext.getResources(), dstBmp);

    } catch (OutOfMemoryError e) {

        //reduce quality and try again
        return recurseCompressAndGetImage(image_path, imageSizeDivide * 2);

    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM