android - outofmemoryerror - setimagebitmap out of memory




BitmapFactory OOMが私をナット駆動 (4)

1.6 MBのメモリはたくさんあるようですが、メモリが非常に断片化しているため、大きなメモリブロックを1つのメモリに割り当てることはできません(これは非常に奇妙です)。

画像リソースを使用しているときのOOMの一般的な原因の1つは、本当に高解像度のJPG、PNG、GIF画像を解凍するときです。 これらのフォーマットはすべて圧縮されていて容量はほとんどありませんが、イメージを電話にロードすると、メモリはwidth * height * 4 bytesようなものになりwidth * height * 4 bytes 。 また、解凍が開始されるとき、復号化ステップのためにいくつかの他の補助データ構造をロードする必要がある。

私は多くの検索を行ってきました。多くの人々がBitmapFactory同じOOMメモリの問題を経験していることをBitmapFactoryます。 私のアプリは、 Runtime.getRuntime ().totalMemory() . Runtime.getRuntime ().totalMemory()を使用して4MBの合計メモリしか表示しません。 制限が16MBの場合、ビットマップのためのスペースを確保するために合計メモリが増えないのはなぜですか? 代わりに、エラーが発生します。

Runtime.getRuntime().freeMemory()よると、1.6MBの空きメモリがあると、「VMは私たちに614400バイトを割り当てさせません」というエラーが表示されるのはなぜですか? 私には十分なメモリがあります。

この問題を除いて、私のアプリは完全で、私のアプリだけが走っているように電話を再起動すると遠ざかります。 私はデバイステスト(Android 1.5)のためにHTC Heroを使用しています。

この時点で、私はBitmapFactory使用を避けるしかないと考えています。

1.6MBの空きメモリがあるときにVMが614KBを割り当てないのは誰ですか?


[このコモンズウェアが指摘しているように、この回答の全アプローチは2.3.x(ジンジャーブレッド)までしか適用されないことに注意してください。 Honeycombの時点では、ビットマップデータはVMヒープに割り当てられています。]

ビットマップデータはVMヒープには割り当てられません。 VMヒープ(これは小さい)への参照がありますが、実際のデータは、基本的なSkiaグラフィックスライブラリによってネイティブヒープに割り当てられています。

残念ながら、BitmapFactory.decode ...()の定義では、イメージデータをデコードできない場合はnullを返しますが、Skiaの実装(JavaコードとSkiaの間のJNI接着剤)は、 (「VMはxxxxバイトを割り当てません」)、「ビットマップサイズがVMバジェットを超えています」という誤解を招くメッセージでOutOfMemory例外がスローされます。

この問題はVMヒープにはなく、ネイティブヒープにあります。 Natïveヒープは実行中のアプリケーション間で共有されるため、空き領域の量は、他のアプリケーションの実行状況とビットマップの使用状況によって異なります。 しかし、BitmapFactoryが返されないことを前提にすると、呼び出しを行う前に呼び出しが成功するかどうかを判断する方法が必要です。

ネイティブヒープのサイズを監視するルーチンがあります(DebugクラスのgetNativeメソッドを参照)。 しかし、私はgetNativeHeapFreeSize()とgetNativeHeapSize()が信頼できないことを発見しました。 したがって、私のアプリケーションの1つでは、多数のビットマップを動的に作成するため、次のことを行います。

ネイティブヒープサイズはプラットフォームによって異なります。 起動時に、許可される最大VMヒープサイズを確認して、許容最大ネイティブヒープサイズを決定します。 [マジックナンバーは2.1と2.2のテストで決定され、他のAPIレベルでは異なるかもしれません。]

long mMaxVmHeap     = Runtime.getRuntime().maxMemory()/1024;
long mMaxNativeHeap = 16*1024;
if (mMaxVmHeap == 16*1024)
     mMaxNativeHeap = 16*1024;
else if (mMaxVmHeap == 24*1024)
     mMaxNativeHeap = 24*1024;
else
    Log.w(TAG, "Unrecognized VM heap size = " + mMaxVmHeap);

その後、BitmapFactoryを呼び出す必要があるたびに、フォームのチェックによってコールに先行します。

long sizeReqd        = bitmapWidth * bitmapHeight * targetBpp  / 8;
long allocNativeHeap = Debug.getNativeHeapAllocatedSize();
if ((sizeReqd + allocNativeHeap + heapPad) >= mMaxNativeHeap)
{
    // Do not call BitmapFactory…
}

heapPadは、a)ネイティブヒープサイズのレポートが「ソフト」であり、b)他のアプリケーションのためにネイティブヒープにスペースを残す必要があるという事実を考慮に入れるためのマジックナンバーです。 私たちは現在、3 * 1024 * 1024(つまり3Mバイト)のパッドで動作しています。


最近のAndroidのバージョンでは、 Toridの回答で問題が解決されたようです。

しかし、イメージキャッシュ(特殊なもの、あるいは通常のHashMap)を使用している場合、メモリリークを作成することでこのエラーを取得するのはかなり簡単です。

私の経験上、誤ってBitmap参照を保持してメモリリークを作成すると、OPのエラー( Bitmap BitmapFactoryとネイティブメソッドを参照する)がアプリケーションをクラッシュさせるエラーになります(ICS-14と+まで)

これを避けるには、あなたのビットマップを "放す"ようにしてください。 つまり、キャッシュの最後の層にSoftReferencesを使用することで、Bitmapがガベージコレクションを取得できるようになります。 これはうまくいくはずですが、まだクラッシュしている場合は、 bitmap.recycle()を使用して明示的に特定のビットマップにマークを付けることができます。ビットマップを使用しない場合は、 bitmap.isRecycled()を使用してください。

LinkedHashMapsLinkedHashMapsは、 この例のようなハードリファレンスとソフトリファレンスを組み合わせる場合(行308を開始する場合)には、かなり良いキャッシュ構造を簡単に実装するための優れたツールです...しかし、ハードリファレンスを使用することで、あなたが混乱するならば、漏れの状況。


通常、エラーはVMによってのみスローされますが、この特定のケースではエラーがjniグルーコードによってスローされるため、エラーをキャッチするのは難しいですが、イメージをロードできないケースを処理するのは非常に簡単です。 OutOfMemoryErrorをキャッチします。





out-of-memory