web-dev-qa-db-ja.com

BitmapFactoryOOMが私を狂わせている

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

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

この問題を除いて、私のアプリは完全です。この問題は、電話を再起動すると消えて、アプリだけが実行されます。デバイスのテストにHTCHeroを使用しています(Android 1.5)。

この時点で、これを回避する唯一の方法は、どういうわけかBitmapFactoryの使用を避けることだと思います。

これについて、または1.6MBの空きメモリがあるのにVMが614KBを割り当てない理由についての説明はありますか?

30
Greg

[(CommonsWareが以下で指摘しているように)この回答のアプローチ全体は、2.3.x(Gingerbread)までにのみ適用されることに注意してください。 Honeycombの時点で、ビットマップデータはVMヒープに割り当てられています。]

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

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

問題はVMヒープではなく、ネイティブヒープにあります。ネイティブヒープは実行中のアプリケーション間で共有されるため、空き領域の量は、実行中の他のアプリケーションとそのビットマップの使用状況によって異なります。ただし、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バイト)のパッドで実行しています。

46
Torid

Toridの回答 に記載されている問題は、Androidの最新バージョンで解決されているようです。

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

私の経験では、誤って ビットマップ 参照を保持してメモリリークを作成すると、OPのエラー( BitmapFactory およびネイティブメソッド)は、アプリをクラッシュさせるものです(最大ICS-14および+?)

これを回避するには、ビットマップを「手放す」ようにします。これは、キャッシュの最終層でSoftReferencesを使用して、ビットマップがガベージコレクションをキャッシュから取得できるようにすることを意味します。これは機能するはずですが、それでもクラッシュが発生する場合は、 bitmap.recycle() を使用して、特定のビットマップをコレクション用に明示的にマークすることができます。 、 bitmap.isRecycled() の場合、アプリで使用するビットマップを返さないことを忘れないでください。

余談ですが、LinkedHashMapsは、特に のようにハード参照とソフト参照を組み合わせる場合に、非常に優れたキャッシュ構造を簡単に実装するための優れたツールです。 この例(開始行308) ...しかし、ハード参照を使用することは、混乱した場合にメモリリークの状況に陥る方法でもあります。

2
Peter Ajtai

1.6 MBのメモリは多いように見えますが、メモリの断片化がひどく、一度にそのような大きなメモリブロックを割り当てることができない場合があります(それでもこれは非常に奇妙に聞こえます)。

画像リソースを使用しているときのOOMの一般的な原因の1つは、JPG、PNG、GIF画像を非常に高い解像度で解凍している場合です。これらの形式はすべてかなり圧縮されており、占有するスペースが非常に少ないことを覚えておく必要がありますが、画像を電話にロードすると、使用するメモリはwidth * height * 4 bytesのようになります。また、解凍が開始されると、デコードステップのために他のいくつかの補助データ構造をロードする必要があります。

2
rui

これはかなり高レベルの答えですが、私にとっての問題は、すべてのビューでハードウェアアクセラレーションを使用していることでした。私のビューのほとんどにはカスタムビットマップ操作があり、これが大きなネイティブヒープサイズの原因であると考えましたが、実際、ハードウェアアクセラレーションを無効にすると、ネイティブヒープの使用量が4分の1に削減されました。

ハードウェアアクセラレーションは、ビューに対してあらゆる種類のキャッシュを実行し、独自のビットマップを作成するようです。すべてのビットマップはネイティブヒープを共有するため、割り当てサイズはかなり劇的に大きくなる可能性があります。

0
minism

通常、エラーはvmによってのみスローされるため、エラーをキャッチすることは意味がありませんが、この特定のケースでは、エラーはjniグルーコードによってスローされるため、イメージをロードできなかった場合の処理​​は非常に簡単です。 OutOfMemoryErrorをキャッチします。

0
Zsolt Safrany