web-dev-qa-db-ja.com

Android:BitmapFactory.decodeStream()のメモリ不足、2MBの空きヒープを持つ400KBファイル

私のアプリは、ソースの次の行でOOMエラーをヒットしています。

image = BitmapFactory.decodeStream(assetManager.open(imgFilename));

OOMエラーでアプリが強制終了される割り当ての直前:

(...)
08-05 21:22:12.443: I/dalvikvm-heap(2319): Clamp target GC heap from 25.056MB to 24.000MB
08-05 21:22:12.443: D/dalvikvm(2319): GC_FOR_MALLOC freed <1K, 50% free 2709K/5379K, external 18296K/19336K, paused 58ms
08-05 21:22:14.513: D/dalvikvm(2319): GC_EXTERNAL_ALLOC freed <1K, 50% free 2709K/5379K, external 18296K/19336K, paused 101ms
08-05 21:22:14.903: I/dalvikvm-heap(2319): Clamp target GC heap from 25.073MB to 24.000MB
08-05 21:22:14.903: D/dalvikvm(2319): GC_FOR_MALLOC freed 0K, 50% free 2709K/5379K, external 18312K/19336K, paused 53ms
08-05 21:22:22.843: D/ddm-heap(2319): Heap GC request
08-05 21:22:22.963: I/dalvikvm-heap(2319): Clamp target GC heap from 25.073MB to 24.000MB
08-05 21:22:22.963: D/dalvikvm(2319): threadid=1: still suspended after undo (sc=1 dc=1)
08-05 21:22:22.963: D/dalvikvm(2319): GC_EXPLICIT freed 1K, 50% free 2710K/5379K, external 18312K/19336K, paused 116ms

DDMSは、ヒープの状態に関する同様の図を報告します。

Heap Size:  5.254 MB
Allocated:  2.647 MB
Free:   2.607 MB
%Used:  50.38%
#Objects    49,028  

この行をシングルステップ実行すると、OOMエラーが発生します。

08-05 21:26:04.783: D/dalvikvm(2319): GC_EXTERNAL_ALLOC freed <1K, 50% free 2710K/5379K, external 18312K/19336K, paused 57ms
08-05 21:26:05.023: E/dalvikvm-heap(2319): 2097152-byte external allocation too large for this process.
08-05 21:26:05.163: I/dalvikvm-heap(2319): Clamp target GC heap from 25.073MB to 24.000MB
08-05 21:26:05.163: E/GraphicsJNI(2319): VM won't let us allocate 2097152 bytes
08-05 21:26:05.163: D/dalvikvm(2319): GC_FOR_MALLOC freed 0K, 50% free 2710K/5379K, external 18312K/19336K, paused 30ms
08-05 21:26:05.283: D/skia(2319): --- decoder->decode returned false
  1. 「imgFileName」で参照されるファイルのサイズは、Windowsで<400Kと報告されています。では、なぜBitmapFactory.decodeStreamは2MBを割り当てようとしますか?
  2. 十分な空き領域があるように見えるのにOOMエラーが発生するのはなぜですか?

このアプリは、Android 2.2以降を対象としています。

前もって感謝します!

65
Mayank

Androidライブラリは画像を読み込むのにそれほどスマートではないため、このための回避策を作成する必要があります。

私のテストでは、Drawable.createFromStreamBitmapFactory.decodeStreamより多くのメモリを使用します。

色スキームを変更してメモリを削減できます(RGB_565)が、画像の品質も低下します。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

リファレンス: http://developer.Android.com/reference/Android/graphics/Bitmap.Config.html

また、スケーリングされた画像を読み込むこともできます。これにより、メモリ使用量が大幅に減少しますが、画像の品質が低下しすぎないようにする必要があります。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

リファレンス: http://developer.Android.com/reference/Android/graphics/BitmapFactory.Options.html

InSampleSizeを動的に定義するには、画像サイズを確認して決定することができます。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
bitmap = BitmapFactory.decodeStream(stream, null, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;

options.inJustDecodeBounds = false;
// recreate the stream
// make some calculation to define inSampleSize
options.inSampleSize = ?;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

デバイスの画面サイズに応じてinSampleSizeをカスタマイズできます。画面サイズを取得するには、次を実行できます。

DisplayMetrics metrics = new DisplayMetrics();
((Activity) activity).getWindowManager().getDefaultDisplay().getMetrics(metrics);
int screenWidth = metrics.widthPixels;
int screenHeight =metrics.heightPixels;

その他のチュートリアル:- http://developer.Android.com/training/displaying-bitmaps/load-bitmap.html - http://developer.Android.com/training/displaying -bitmaps/index.html

85
Paulo Cheque

大きなビットマップをより効率的にロードするためのガイドについては、これを参照してください。

http://developer.Android.com/training/displaying-bitmaps/load-bitmap.html

400 KBの画像ファイルは、5〜10 MBのRAMを簡単に占有できます。

42
Michell Bak

ディスク上のファイルのサイズは、メモリ内のファイルのサイズと必ずしも一致しません。ファイルが圧縮されている可能性がありますが、デコード時には圧縮されません。計算でそれを考慮する必要があります。

画像のサイズ(幅x高さ)に画像の色深度を掛けて、画像のメモリ内サイズを取得します。

8
nEx.Software

基本的に、Bitmapをスケーリングすることで問題を解決でき、メモリ消費量が削減されます。それを行うには、示されているメソッド here をコピーします。

また、Android Developeresには専用のページがあり、大きなビットマップを読み込む方法をよりよく理解するのに役立ちます。公式の ドキュメント をご覧ください。

3
yugidroid

上記の答えは明らかに正しいですが、より良い方法は、explicitly ImageViewのビットマップ/ srcプロパティをnullに設定することです。アクティビティが破壊されています。その他のヘビーデューティーリソース(大きなテキスト、オーディオ、ビデオ)なども無効になる場合があります。これにより、GCが収集されるのを待たずに、リソースが即座に解放されます。

1
rishabh

OutOfMemoryError以下のメソッドを使用して問題を解決しました

private Bitmap downloadImageBitmap(String sUrl) {
        this.url = sUrl;
        bitmap = null;
                    try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            for (options.inSampleSize = 1; options.inSampleSize <= 32; options.inSampleSize++) {
                InputStream inputStream = new URL(sUrl).openStream();  
                try {
                    bitmap = BitmapFactory.decodeStream(inputStream, null, options);      
                    inputStream.close();
                    break;
                } catch (OutOfMemoryError outOfMemoryError) {
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return bitmap;
    }
1