2013-01-09 11 views
35

私はアンドロイドアプリケーションで作業しています。アプリケーションには、多数のイメージを含むビューがあります。私は間違いがあったので、できるだけ多くの情報を提供しようとします。ビットマップを避けるための提案メモリ不足エラー

アプリケーションは、すべてのローカルテストでうまく機能していました。しかし、私は、ユーザーからのクラッシュの多くを受け取っ:java.lang.OutOfMemoryError: bitmap size exceeds VM budget

これが私の最大の問題は、私も古いデバイス上でローカルに問題を再現することができなかったということであるスタックトレース

0  java.lang.OutOfMemoryError: bitmap size exceeds VM budget 
1 at android.graphics.Bitmap.nativeCreate(Native Method) 
2 at android.graphics.Bitmap.createBitmap(Bitmap.java:507) 
3 at android.graphics.Bitmap.createBitmap(Bitmap.java:474) 
4 at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:379) 
5 at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:498) 
6 at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:473) 
7 at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336) 
8 at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:359) 
9 at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:385) 

です。

私はこの問題を解決しようとするものの多くを実装している:いいえメモリはをリークしない

  1. :私はまったくメモリリークがないことを確認しました。私はそれらを必要としないときに私はビューを削除しました。また、すべてのビットマップをリサイクルし、ガベージコレクタが正常に動作していることを確認しました。そして、私はonDestroy()メソッドのすべての必要な手順を実装しました
  2. 画像サイズは正しく調整されました:画像を取得する前に、私はinSampleSizeを計算します。
  3. ヒープサイズ:イメージを取得する前に最大ヒープサイズを検出し、十分なスペースがあることを確認します。十分でない場合は、それに応じて画像のサイズを変更します。

コード古いデバイス

のヒープサイズに基づいて画像のサイズを計算するために、ビットマップ

// decodes image and scales it to reduce memory consumption 
    private static Bitmap decodeFile(File file, int newWidth, int newHeight) 
    {// target size 
     try 
     { 

     Bitmap bmp = MediaStore.Images.Media.getBitmap(getContext().getContentResolver(), Uri.fromFile(file)); 
     if(bmp == null) 
     { 
      // avoid concurrence 
      // Decode image size 
      BitmapFactory.Options option = new BitmapFactory.Options(); 

      // option = getBitmapOutput(file); 

      option.inDensity = res.getDisplayMetrics().densityDpi < DisplayMetrics.DENSITY_HIGH ? 120 : 240; 
      option.inTargetDensity = res.getDisplayMetrics().densityDpi; 

      if(newHeight > 0 && newWidth > 0) 
       option.inSampleSize = calculateInSampleSize(option, newWidth, newWidth); 

      option.inJustDecodeBounds = false; 
      byte[] decodeBuffer = new byte[12 * 1024]; 
      option.inTempStorage = decodeBuffer; 
      option.inPurgeable = true; 
      option.inInputShareable = true; 
      option.inScaled = true; 

      bmp = BitmapFactory.decodeStream(new FileInputStream(file), null, option); 
      if(bmp == null) 
      { 
       return null; 
      } 

     } 
     else 
     { 
      int inDensity = res.getDisplayMetrics().densityDpi < DisplayMetrics.DENSITY_HIGH ? 120 : 240; 
      int inTargetDensity = res.getDisplayMetrics().densityDpi; 
      if(inDensity != inTargetDensity) 
      { 
       int newBmpWidth = (bmp.getWidth() * inTargetDensity)/inDensity; 
       int newBmpHeight = (bmp.getHeight() * inTargetDensity)/inDensity; 
       bmp = Bitmap.createScaledBitmap(bmp, newBmpWidth, newBmpHeight, true); 
      } 
     } 

     return bmp; 
     } 
     catch(Exception e) 
     { 
     Log.e("Error calling Application.decodeFile Method params: " + Arrays.toString(new Object[]{file }), e); 
     } 
     return null; 
    } 

コードを取得するには、正しい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) 
     { 
     if(width > height) 
     { 
      inSampleSize = Math.round((float) height/(float) reqHeight); 
     } 
     else 
     { 
      inSampleSize = Math.round((float) width/(float) reqWidth); 
     } 
     } 
     return inSampleSize; 
    } 

コードを計算します

private void calculateImagesSize() { // only for android older than HoneyComb that does not support large heap if(Build.VERSION.SDK_INT < Constants.HONEYCOMB) { long maxHeapSize = Runtime.getRuntime().maxMemory(); long maxImageHeap = maxHeapSize - 10485760; if(Application.getResource().getDisplayMetrics().densityDpi >= DisplayMetrics.DENSITY_XHIGH) { maxImageHeap -= 12 * 1048576; } if(maxImageHeap < (30 * 1048576)) { int screenHeight = Math.min(Application.getResource().getDisplayMetrics().heightPixels, Application.getResource() .getDisplayMetrics().widthPixels); long maxImageSize = maxImageHeap/100; long maxPixels = maxImageSize/4; long maxHeight = (long) Math.sqrt(maxPixels/1.5); if(maxHeight < screenHeight) { drawableHeight = (int) maxHeight; drawableWidth = (int) (drawableHeight * 1.5); } } } } 

私はこの問題がヒープだと思っています。たぶんosはアプリケーションがmaxheapsizeを使用できないことがあります。私の最大の問題は、問題を再現することができなかったことです。そのため、修正プログラムを試すときに、ユーザーが引き続きエラーを受けているかどうかを確認する必要があります。

メモリの問題が発生しました。どんな提案も大歓迎です。おかげでたくさん

+0

非常に大きな画像を1つ取り、コードで使用してみてください。私はそれがクラッシュすると思う) 私はこの行が好きではない:ビットマップbmp = MediaStore.Images.Media.getBitmap(getContext()。getContentResolver()、Uri.fromFile(file)); – Leonidos

+0

私は別の質問で提案の要約を書いた:http://stackoverflow.com/questions/11820266/android-bitmapfactory-decodestream-out-of-memory-with-a-400kb-file-with-2mb-f/16528487 #16528487 –

+0

@Youssef、私はあなたがこれを見てみるべきだと思います - http://stackoverflow.com/a/15380872/1433187 私はメモリエラーから抜け出していました。 – Khobaib

答えて

8

はちょうど/スケールの大きさを減らすことで...これは私も同じエラーを取得し、あなたのerror..becauseのための完璧なソリューションであり、私はこのソリューションを持って...

public static Bitmap decodeFile(File f,int WIDTH,int HIGHT){ 
    try { 
     //Decode image size 
     BitmapFactory.Options o = new BitmapFactory.Options(); 
     o.inJustDecodeBounds = true; 
     BitmapFactory.decodeStream(new FileInputStream(f),null,o); 

     //The new size we want to scale to 
     final int REQUIRED_WIDTH=WIDTH; 
     final int REQUIRED_HIGHT=HIGHT; 
     //Find the correct scale value. It should be the power of 2. 
     int scale=1; 
     while(o.outWidth/scale/2>=REQUIRED_WIDTH && o.outHeight/scale/2>=REQUIRED_HIGHT) 
      scale*=2; 

     //Decode with inSampleSize 
     BitmapFactory.Options o2 = new BitmapFactory.Options(); 
     o2.inSampleSize=scale; 
     return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); 
    } catch (FileNotFoundException e) {} 
    return null; 
} 
+3

こんにちは、あなたの提案をありがとうが、私は実際に同じことをやっているが、より高度なチェックをしています。私はあなたが持っているが、クラッシュしていた正確なコードを持っていた。そこで、より高度なものを考慮して修正しました。しかし、あなたのコードのロジックが私の中に存在します。お返事ありがとうございます – Youssef

+0

どのようなエラーがありますか? –

+0

同じエラー:OutOfMemoryError:ビットマップサイズがVM予算を超えています – Youssef

4

を復号化するために、この機能を使用しますあなたがメモリ不足の例外の外に取り除くことができ、イメージの こんにちは、あなたがファイルをデコードする必要があり、この

BitmapFactory.Options options = new BitmapFactory.Options(); 
    options.inSampleSize = 6; 
    Bitmap receipt = BitmapFactory.decodeFile(photo.toString(),options); //From File You can customise on your needs. 
+11

ビットマップがぼやけています。 – Jarvis

+4

提供された画像が非常に大きい場合でも、例外が発生することがあります。 –

+0

ここでは品質が失われます –

4

を試してみてください。これは以下の方法で試してみてください。

public static Bitmap new_decode(File f) { 

     // decode image size 

     BitmapFactory.Options o = new BitmapFactory.Options(); 
     o.inJustDecodeBounds = true; 
     o.inDither = false; // Disable Dithering mode 

     o.inPurgeable = true; // Tell to gc that whether it needs free memory, 
           // the Bitmap can be cleared 

     o.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 
     try { 
      BitmapFactory.decodeStream(new FileInputStream(f), null, o); 
     } catch (FileNotFoundException e1) { 
      // TODO Auto-generated catch block 
      e1.printStackTrace(); 
     } 

     // Find the correct scale value. It should be the power of 2. 
     final int REQUIRED_SIZE = 300; 
     int width_tmp = o.outWidth, height_tmp = o.outHeight; 
     int scale = 1; 
     while (true) { 
      if (width_tmp/1.5 < REQUIRED_SIZE && height_tmp/1.5 < REQUIRED_SIZE) 
       break; 
      width_tmp /= 1.5; 
      height_tmp /= 1.5; 
      scale *= 1.5; 
     } 

     // decode with inSampleSize 
     BitmapFactory.Options o2 = new BitmapFactory.Options(); 
     // o2.inSampleSize=scale; 
     o.inDither = false; // Disable Dithering mode 

     o.inPurgeable = true; // Tell to gc that whether it needs free memory, 
           // the Bitmap can be cleared 

     o.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 
     // return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); 
     try { 

//   return BitmapFactory.decodeStream(new FileInputStream(f), null, 
//     null); 
      Bitmap bitmap= BitmapFactory.decodeStream(new FileInputStream(f), null, null); 
      System.out.println(" IW " + width_tmp); 
      System.out.println("IHH " + height_tmp);   
       int iW = width_tmp; 
       int iH = height_tmp; 

       return Bitmap.createScaledBitmap(bitmap, iW, iH, true); 

     } catch (OutOfMemoryError e) { 
      // TODO: handle exception 
      e.printStackTrace(); 
      // clearCache(); 

      // System.out.println("bitmap creating success"); 
      System.gc(); 
      return null; 
      // System.runFinalization(); 
      // Runtime.getRuntime().gc(); 
      // System.gc(); 
      // decodeFile(f); 
     } catch (FileNotFoundException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
      return null; 
     } 

    } 
+0

Brilliant Hint;) –

+1

ありがとう@MuhammedRefaat ... :) – itsrajesh4uguys

+1

私はSystem.gc()ソリューションが嫌いです。なぜそれが起こっているのか見つけようとすると、メモリリークなどがあります。 –

0

実際の問題は、開発のOSです。アンドロイドでは、iOSと異なり、Googleの人々はカメラの解像度に基づいてこれを開発します。ビットマップは、特に写真のような豊かな画像の場合、多くのメモリを占有します。異なるカメラは、異なるピクセル(異なるモバイルは異なるカメラピクセル容量を持つ)で画像をキャプチャします。ここでは、そのピクセルに基づいてアンドロイドでは、キャプチャされた画像だけがメモリを取るでしょう。そのため、明らかに高解像度の画像は、画素容量の少ない電話機によってアップロードされません。 androidはすべてのアプリケーションに最大16MBを割り当てます。アップロードされたイメージがこれを超えると、java.lang.OutofMemoryError:ビットマップサイズがVMバジェットを超え、アプリケーションがクラッシュします。

private Bitmap getBitmapSafely(Resources res, int id, int sampleSize) { 
// res = context.getResources(), id = R.drawable.yourimageid 
    Bitmap bitmap = null; 
    BitmapFactory.Options options = new BitmapFactory.Options(); 
    options.inPurgeable = true; 
    options.inSampleSize = sampleSize; 
    try { 
      bitmap = BitmapFactory.decodeResource(res, 
         id, options); 
    } catch (OutOfMemoryError oom) { 
     Log.w("ImageView", "OOM with sampleSize " + sampleSize, oom); 
     System.gc(); 
     bitmap = getBitmapSafely(res, id, sampleSize + 1); 
    } 

    return bitmap; 
} 

はそれが役に立てば幸い: uはOOMを避けたい場合は、uがOOMをキャッチすることができ、画像が解消されるまでsampleSizeを増やすこの http://developer.android.com/training/displaying-bitmaps/index.html

0

を参照してください。

エラーをキャッチするのは適切ではありません。回避策です。

+5

'OutOfMemoryError'は捕まえられません – Melllvar

+0

@Melllvar私はこれらのコードをテストしました。私は実際にOOMを捕まえました。あなたはOOMをキャッチするのに適していないという意味ですか? – Euporie

+0

OK、私は多くの矛盾する情報を見ています(例:[this post](http://stackoverflow.com/questions/14812508/java-lang-outofmemoryerror-even-in-a-try-catch-block ))。私は自分自身で 'try/catch(OutOfMemoryError)'の中に回復不可能なクラッシュを持っていましたが、これは多かれ少なかれ運の問題であることを示唆しています(例えば可能かもしれませんが、エラーがプログラムを不確定な状態にする可能性があるため、クラッシュは何に関係なく発生します)。いずれにしても、あなたの解決策はうまくいくかもしれません。 – Melllvar

関連する問題