2010-11-05 47 views
17

在我的應用程序中,我加載了來自JPEG和PNG文件的幾個圖像。當我把所有這些文件轉化爲資產目錄,並以這種方式加載它,一切都很好:從外部存儲器加載位圖時發生OutOfMemory異常

InputStream stream = getAssets().open(path); 
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null); 
stream.close(); 
return new BitmapDrawable(bitmap); 

但是,當我嘗試從SD卡中加載完全相同的圖像,我得到一個內存不足的異常!

InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path); 
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null); 
stream.close(); 
return new BitmapDrawable(bitmap); 

這是我得到的日誌中:

11-05 00:53:31.003: ERROR/dalvikvm-heap(13183): 827200-byte external allocation too large for this process. 
11-05 00:53:31.003: ERROR/GraphicsJNI(13183): VM won't let us allocate 827200 bytes 
... 
11-05 00:53:31.053: ERROR/AndroidRuntime(13183): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget 
11-05 00:53:31.053: ERROR/AndroidRuntime(13183):  at android.graphics.BitmapFactory.nativeDecodeStream(Native Method) 
... 

爲什麼能這樣呢?

更新:嘗試這兩個在真實設備上 - 似乎我無法加載超過12MB的位圖到任何被稱爲「外部存儲器」(這不是一個SD卡)。

+0

在什麼條件下,你測試上面的代碼?在仿真器,或真正的設備連接USB?有可能您的USB模式設置爲鎖定SD卡的磁盤模式。 – xandy 2010-11-05 00:59:36

+0

我在模擬器中運行這段代碼。 – Fixpoint 2010-11-05 07:37:19

+0

jpg/png文件的大小是多少? – Fedor 2010-11-08 08:10:50

回答

4

你的API使用可能沒有問題,我猜我們所能做的只是推斷使用AssetManager涉及較少的幕後堆分配而不是從SD卡中打開一個隨機文件。

800KB是任何人的書中的嚴重分配......這無疑將是解壓縮的圖像像素。鑑於你知道圖像的大小,它有多深?如果它是32bpp,則嘗試使用inPreferredConfig覆蓋該值。

+0

所以,看起來似乎只有* emulator上的AssetManager *可以避免總加載的位圖大小上限。 :( – Fixpoint 2010-11-13 22:05:51

5
  • 使用位圖做很多事情時,請不要調試應用程序 - 只需運行它。調試器會留下內存泄漏。
  • 位圖非常昂貴。如果可能,通過創建BitmapFactory.Options並將inSampleSize設置爲> 1來縮小負載。

編輯:另外,一定要檢查你的應用程序的內存泄漏。泄漏位圖(具有static位圖是一種很好的方法)很快就會耗盡可用內存。

+0

我沒有調試這個應用程序,只是運行它。其餘的代碼是100%相同的,那麼怎麼可能從一個地方加載的位圖發生泄漏,而另一些則沒有?關於縮放 - 我想使用高質量的圖像(應該有足夠的內存來加載它們,因爲它們的加載沒有來自資產的錯誤)。 – Fixpoint 2010-11-05 07:40:21

1

爲什麼不直接使用getCacheDir()將圖像移動到手機內部存儲器的緩存中,或者使用臨時目錄來存儲圖像?

有關外部存儲器使用情況,請參閱this,this。此外,this article可能與您有關。

8

我嘗試了所有的辦法,在其他資源提到here &但我來到了推斷的ImageView基準設置爲null將解決這一問題:

public Bitmap getimage(String path ,ImageView iv) 
    { 
    //iv is passed to set it null to remove it from external memory 
    iv=null; 
    InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path); 
    Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null); 
    stream.close(); 
    stream=null; 
    return bitmap; 
    } 

&你做!

注意:雖然它可能會解決上述問題,但我建議您檢查Tom van Zummeren的優化圖像加載。

並且還檢查SoftReference:所有指向可輕鬆訪問的對象的SoftReferences在VM將拋出OutOfMemoryError之前保證被清除。

0

試試這個方法......

Bitmap bmpOrignal = BitmapFactory.decodeFile("/sdcard/mydata/" + path"); 
+0

嘗試過,與InputStream:OutOfMemory相同的結果 – Fixpoint 2010-11-09 22:42:03

0

您不能依賴GC回收您的位圖內存。 不需要時,您必須清楚地回收位圖。

見位圖方法:

無效循環() 免費了這個位圖的像素相關聯的內存,並標記位圖作爲「死」的,這意味着它會拋出一個異常的getPixels()或的setPixels( )被調用,並且不會畫任何東西。

+0

我不想回收所有這些位圖,我正在使用它們 – Fixpoint 2010-11-10 14:13:58

+0

如果一個位圖對用戶隱藏,最好回收它們,如果它們再次顯示,你再次加載它們。手機的內存是有限的。 – imcaptor 2010-11-11 05:05:05

+0

當試圖加載已回收的位圖時,它會拋出一個異常,說不能加載回收的位圖... – 66CLSjY 2010-12-22 05:51:47

3

這是一個相當普遍的問題,我們所有人都在從SD卡加載圖像時遇到的問題。

我找到的解決方案是在使用decodeFileDescriptor加載圖像時首先使用inJustDecodeBounds。這實際上不會解碼圖像,但會給圖像大小。現在我可以適當縮放(使用選項),以調整顯示區域的圖像大小。它的需要,因爲手機上的低內存可以很容易地被你的5MP圖像接管。我相信這是最優雅的解決方案。

+0

我正在考慮它,但這也意味着我會爲一個圖像提出2個請求 – Bostone 2011-10-30 16:42:15

+0

是的,但由於兩次調用我可能因爲inJustDecodeBounds沒有做很多工作而面臨任何減速 – the100rabh 2011-11-01 13:48:57

2

這裏有兩個問題....

  • 位圖的內存是不是在VM堆而是在機堆 - 見BitmapFactory OOM driving me nuts
  • 垃圾收集本地堆是懶比VM堆 - 所以你需要相當積極地做bitmap.recycle和位圖=空你每次通過一個活動的onPause或onDestroy
0

我發現開發Android應用程序最常見的錯誤之一是「 java.lang.OutOfMemoryErro r:位圖大小超出VM預算「錯誤。在改變方向後,我發現這個錯誤使用了很多位圖:活動被破壞,再次創建,佈局從耗費虛擬機內存的XML中「膨脹」,可用於位圖。

上一個活動佈局的位圖由於垃圾收集器已交叉引用其活動而未正確釋放。經過多次實驗,我發現了一個很好的解決方案。

首先,設置在您的XML佈局的父視圖中的「id」屬性:

<?xml version="1.0" encoding="utf-8"?> 
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:id="@+id/RootView" 
    > 
    ... 

然後,在您的活動的的onDestroy()方法,調用unbindDrawables()方法傳遞refence到父視圖,然後做一個System.gc()的

@Override 
    protected void onDestroy() { 
    super.onDestroy(); 

    unbindDrawables(findViewById(R.id.RootView)); 
    System.gc(); 
    } 

    private void unbindDrawables(View view) { 
     if (view.getBackground() != null) { 
     view.getBackground().setCallback(null); 
     } 
     if (view instanceof ViewGroup) { 
      for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { 
      unbindDrawables(((ViewGroup) view).getChildAt(i)); 
      } 
     ((ViewGroup) view).removeAllViews(); 
     } 
    } 

這unbindDrawables()方法遞歸地探討了視圖樹和:

  1. 刪除所有後臺回調可繪製
  2. 刪除在每個孩子的ViewGroup中
0

使用下面的代碼,你絕不會出現以下錯誤:java.lang中。的OutOfMemoryError:位圖大小超過VM預算

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

       bounds.inSampleSize = 4; 

       myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bounds); 

       picturesView.setImageBitmap(myBitmap); 
4
The best solution i found and edited according to my need 

public static Bitmap getImageBitmap(String path) throws IOException{ 
     // Allocate files and objects outside of timingoops    
     File file = new File(thumbpath);   
     RandomAccessFile in = new RandomAccessFile(file, "rws"); 
     final FileChannel channel = in.getChannel(); 
     final int fileSize = (int)channel.size(); 
     final byte[] testBytes = new byte[fileSize]; 
     final ByteBuffer buff = ByteBuffer.allocate(fileSize); 
     final byte[] buffArray = buff.array(); 
     @SuppressWarnings("unused") 
     final int buffBase = buff.arrayOffset(); 

     // Read from channel into buffer, and batch read from buffer to byte array; 
     long time1 = System.currentTimeMillis(); 
     channel.position(0); 
     channel.read(buff); 
     buff.flip(); 
     buff.get(testBytes); 
     long time1 = System.currentTimeMillis(); 
     Bitmap bmp = Bitmap_process(buffArray); 
     long time2 = System.currentTimeMillis();   
     System.out.println("Time taken to load: " + (time2 - time1) + "ms"); 

     return bmp; 
    } 

    public static Bitmap Bitmap_process(byte[] buffArray){ 
     BitmapFactory.Options options = new BitmapFactory.Options(); 

     options.inDither=false;      //Disable Dithering mode 
     options.inPurgeable=true;     //Tell to gc that whether it needs free memory, the Bitmap can be cleared 
     options.inInputShareable=true;    //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future 
     options.inTempStorage=new byte[32 * 1024]; //Allocate some temporal memory for decoding 

     options.inSampleSize=1; 

     Bitmap imageBitmap = BitmapFactory.decodeByteArray(buffArray, 0, buffArray.length, options); 
     return imageBitmap; 
    } 
1

感謝所有的線程,我發現對我的作品在真實設備上的解決方案。 的招數都是關於使用

BitmapFactory.Options opts=new BitmapFactory.Options(); 
opts.inSampleSize=(int)(target_size/bitmap_size); //if original bitmap is bigger 

但對我來說這是不夠的。我的原始圖像(取自相機應用程序)是3264x2448。對於我來說正確的比例是3,因爲我想要一個1024x768的普通VGA圖像。

但是將inSampleSize設置爲3還不夠:還是內存不足異常。 所以最後我選擇了一種迭代方法:我從計算出的正確尺寸開始,並增加它直到停止出現OOM異常。 對我來說是在4

// Decode with inSampleSize 
BitmapFactory.Options o2 = new BitmapFactory.Options(); 
// o2.inSampleSize = scale; 
float trueScale = o.outWidth/1024; 
o2.inPurgeable = true; 
o2.inDither = false; 
Bitmap b = null; 
do { 
    o2.inSampleSize = (int) trueScale; 
    Log.d(TAG, "Scale is " + trueScale); 
try { 
    b = BitmapFactory.decodeStream(new FileInputStream(f), null, o2); 
    } catch (OutOfMemoryError e) { 
     Log.e(TAG,"Error decoding image at sampling "+trueScale+", resampling.."+e); 
     System.gc(); 
    try { 
     Thread.sleep(50); 
    } catch (InterruptedException e1) { 
     e1.printStackTrace(); 
    } 
} 
    trueScale += 1; 
} while (b==null && trueScale < 10); 
return b; 
0

樣品允許inSampleSize調整最終讀取圖像。 AssetFileDescriptor的 getLength()允許獲取文件的大小。

您可以根據變化的getLength inSampleSize()來防止內存溢出這樣的:

private final int MAX_SIZE = 500000; 

public Bitmap readBitmap(Uri selectedImage) 
{ 
    Bitmap bm = null; 
    AssetFileDescriptor fileDescriptor = null; 
    try 
    { 
     fileDescriptor = this.getContentResolver().openAssetFileDescriptor(selectedImage,"r"); 
     long size = fileDescriptor.getLength(); 
     BitmapFactory.Options options = new BitmapFactory.Options(); 
     options.inSampleSize = (int) (size/MAX_SIZE); 
     bm = BitmapFactory.decodeFileDescriptor(fileDescriptor.getFileDescriptor(), null, options); 
    } 
    catch (Exception e) 
    { 
     e.printStackTrace(); 
    } 
    finally 
    { 
     try { 
      if(fileDescriptor != null) fileDescriptor.close(); 
     } catch (IOException e) {} 
    } 
    return bm; 
} 
相關問題