画像をビットマップオブジェクトにロードする際の奇妙なメモリ不足の問題


1289

各行にいくつかの画像ボタンがあるリストビューがあります。リストの行をクリックすると、新しいアクティビティが起動します。カメラのレイアウトに問題があるため、独自のタブを作成する必要がありました。結果に対して起動されるアクティビティはマップです。ボタンをクリックして画像プレビューを起動する(SDカードから画像を読み込む)と、アプリケーションはアクティビティからアクティビティに戻りlistview、結果ハンドラーに戻り、画像ウィジェットにすぎない新しいアクティビティを再起動します。

リストビューの画像プレビューは、カーソルとで行われていますListAdapter。これでかなり簡単になりますが、サイズ変更した画像を配置する方法がわかりません(srcつまり、オンザフライで画像ボタンのようにピクセルではなくビットサイズを小さくします。したがって、電話のカメラから出た画像のサイズを変更しました。

問題は、戻って2番目のアクティビティを再起動しようとすると、メモリ不足エラーが発生することです。

  • ごとにリストアダプターを簡単に構築できる方法はありますか?

フォーカスの問題のためにタッチスクリーンで行を選択できないため、各行のウィジェット/要素のプロパティにもいくつか変更を加える必要があるため、これは望ましい方法です。(ローラーボールが使用できます。

  • 帯域外のサイズ変更と画像の保存を実行できることはわかっていますが、それは実際に実行したいことではありませんが、そのためのサンプルコードを使用すると便利です。

リストビューで画像を無効にするとすぐに、再び正常に機能しました。

参考までに:これは私がやっていたことです:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME,DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY,DBHelper.KEY_GPSLONG,DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME  + ""};
int[] to = new int[] {R.id.businessname,R.id.address,R.id.city,R.id.gpslong,R.id.gpslat,R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);

どこにR.id.imagefilenameありますButtonImage

ここに私のLogCatがあります:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

また、画像を表示するときに新しいエラーが発生しました。

01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed

8
Bitmap.decodeStreamまたはdecodeFileを回避し、BitmapFactory.decodeFileDescriptorメソッドを使用することで、これを解決しました。
Fraggle

1
私も数週間前に同様の問題に直面し、画像を最適なポイントまで縮小することで解決しました。私は完全なアプローチを私のブログcodingjunkiesforum.wordpress.com/2014/06/12/…に書いており、https://github.com/shailendra123/BitmapHandlingDemoでOOM傾向コード対OOM証拠コードを含む完全なサンプルプロジェクトをアップロードしました
Shailendra Singh Rajawat

5
この質問で受け入れられた回答は、メタ
rene

4
これは、Android開発者ガイドを読んでいないときに発生します
Pedro Varela

2
これは、Androidのアーキテクチャが悪いために発生します。iOSのように画像自体のサイズを変更する必要があり、UWPはこれを行います。私はこれを自分でする必要はありません。Android開発者はその地獄に慣れ、本来の方法で機能すると考えています。
アクセス拒否2018年

回答:


651

Androidのトレーニングクラス、「効率的なビットマップを表示する」、理解し、例外に対処するための申し出いくつかの素晴らしい情報をjava.lang.OutOfMemoryError: bitmap size exceeds VM budgetビットマップをロードするとき。


ビットマップの次元とタイプを読み取る

BitmapFactoryクラスは、(いくつかの復号方法を提供しdecodeByteArray()decodeFile()decodeResource()作成する、など)Bitmap、様々なソースから。画像データソースに基づいて、最も適切なデコード方法を選択してください。これらのメソッドは、構築されたビットマップにメモリを割り当てようとするため、簡単にOutOfMemory例外が発生する可能性があります。デコードメソッドの各タイプには、BitmapFactory.Optionsクラスを介してデコードオプションを指定できる追加のシグネチャがあります。デコード中にinJustDecodeBoundsプロパティをに設定すると、trueメモリ割り当てが回避さnullれ、ビットマップオブジェクトに戻りますがoutWidthoutHeightとを設定しoutMimeTypeます。この手法により、ビットマップの構築(およびメモリ割り当て)の前に、画像データのサイズとタイプを読み取ることができます。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

java.lang.OutOfMemory例外を回避するには、使用可能なメモリに快適に収まる予測可能なサイズの画像データを提供することがソースから完全に信頼されない限り、ビットマップをデコードする前にそのサイズを確認してください。


縮小したバージョンをメモリにロードする

画像の寸法がわかったので、これを使用して、画像全体をメモリにロードするか、サブサンプルバージョンをロードするかを決定できます。考慮すべき要素は次のとおりです。

  • 完全なイメージをメモリにロードする推定メモリ使用量。
  • アプリケーションの他のメモリ要件を前提として、このイメージのロードにコミットするメモリの量。
  • 画像が読み込まれるターゲットImageViewまたはUIコンポーネントの寸法。
  • 現在のデバイスの画面サイズと密度。

たとえば、1024x768ピクセルの画像が128x96ピクセルのサムネイルで最終的に表示される場合、メモリに1024x768ピクセルの画像をロードする価値はありませんImageView

デコーダーにイメージをサブサンプリングし、より小さなバージョンをメモリにロードするように指示するinSampleSizetrueは、BitmapFactory.Optionsオブジェクトに設定します。たとえば、解像度2048x1536の画像をinSampleSize4でデコードすると、約512x384のビットマップが生成されます。これをメモリにロードすると、フルイメージに12MBではなく0.75MBが使用されます(ビットマップ構成がの場合ARGB_8888)。ターゲットの幅と高さに基づいて2の累乗であるサンプルサイズ値を計算する方法を次に示します。

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) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

inSampleSizeドキュメントに従って、デコーダーは最も近い2の累乗に切り捨てて最終値を使用するため、2の累乗値が計算され ます。

このメソッドを使用するには、最初にinJustDecodeBoundsset を使用してデコードしtrue、オプションを渡してから、新しいinSampleSize値を使用して再度デコードし、にinJustDecodeBounds設定しfalseます。

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
    int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

このメソッドを使用するとImageView、次のコード例に示すように、任意のサイズのビットマップを100x100ピクセルのサムネイルを表示するに簡単にロードできます。

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

BitmapFactory.decode*必要に応じて適切な方法を代用することにより、他のソースからのビットマップをデコードするための同様のプロセスに従うことができます。


21
この回答はメタ
rene

9
この回答(リンクを介して到達した情報を除く)は、回答の解決策の多くを提供しません。リンクの重要な部分を質問にマージする必要があります。
FallenAngel、2015

7
この回答は、質問やその他の回答と同様にコミュニティWikiです。これは、コミュニティが編集によって修正できるものであり、モデレーターの介入を必要としないものです。
Martijn Pieters

コンテンツやKotlinサポートへの現在のリンクで見つけることができます:developer.android.com/topic/performance/graphics/load-bitmap
パノスGrの

891

OutOfMemoryエラーを修正するには、次のようにします。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

このinSampleSizeオプションは、メモリ消費を減らします。

これが完全なメソッドです。まず、コンテンツ自体をデコードせずに画像サイズを読み取ります。次に、最適なinSampleSize値を見つけます。2の累乗である必要があり、最後に画像がデコードされます。

// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
    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_SIZE=70;

        // Find the correct scale value. It should be the power of 2.
        int scale = 1;
        while(o.outWidth / scale / 2 >= REQUIRED_SIZE && 
              o.outHeight / scale / 2 >= REQUIRED_SIZE) {
            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;
}

31
、ドキュメントが2の累乗を使用することを提案しているものの10はinSampleSize最高の価値ではないかもしれないことを注意
ミルコ・N.

70
私はChrispixと同じ問題に直面していますが、ここでのソリューションは問題を実際に解決するとは思わず、むしろそれを回避します。サンプルサイズを変更すると、使用されるメモリの量は減少しますが(画像の品質は犠牲になりますが、画像プレビューでは問題ありません)、十分な大きさの画像ストリームがデコードされている場合や、複数の画像ストリームがデコードされている場合は、例外を防ぐことはできませんデコード。より良い解決策が見つかった場合(および解決策がない場合)、ここに回答を投稿します。
Flynn81

4
ピクセル密度で画面に一致する適切なサイズのみが必要です。ズームインする場合などは、より高い密度で画像のサンプルを取得できます。
ステルスコプター2010

4
REQUIRED_SIZEは、スケーリングする新しいサイズです。
Fedor

8
このソリューションは私を助けましたが、画像の品質はひどいです。画像を表示するためにviewfilpperを使用していますか?
user1106888 2013

373

Fedorのコードを少し改善しました。基本的には同じですが、(私の意見では)醜いwhileループがないため、常に2の累乗になります。元の解決策を作成してくれたFedorへの称賛、私は彼が見つかるまで行き詰まり、それからこれを作ることができました:)

 private Bitmap decodeFile(File f){
    Bitmap b = null;

        //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;

    FileInputStream fis = new FileInputStream(f);
    BitmapFactory.decodeStream(fis, null, o);
    fis.close();

    int scale = 1;
    if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
        scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / 
           (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
    }

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize = scale;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, o2);
    fis.close();

    return b;
}

40
はい、あなたはそれほど美しいわけではありませんが、そうです。私はそれを皆に明らかにしようとしただけです。コードをありがとう。
ヒョードル

10
@Thomas Vervest-そのコードには大きな問題があります。^は2を累乗しません。結果は2をxorsします。Math.pow(2.0、...)が必要です。そうでなければ、これはよさそうだ。
DougW

6
ああ、それはとても良いものです!私の悪い、私はすぐに修正します、返信ありがとうございます!
Thomas Vervest、

8
2つの新しいFileInputStreamを作成していBitmapFactory.decodeStream()ます。それらをfinallyブロックで閉じることができるように、それぞれへの参照を保存する必要はありませんか?
matsev

1
@Babibuドキュメンテーションには、ストリームが閉じられていることが記載されていないため、まだ閉じられているはずです。興味深い、関連する議論がここにあります。Adrian Smithのコメントに注意してください。これは私たちの議論に直接関係しています。
Thomas Vervest、2013年

233

私はiOSの経験から来ましたが、画像の読み込みや表示など、非常に基本的な問題に気づきませんでした。結局のところ、この問題を抱えているすべての人は、適度なサイズの画像を表示しようとしています。とにかく、これが私の問題を修正した(そして私のアプリを非常に応答性の高いものにした)2つの変更点です。

1)行うたびにBitmapFactory.decodeXYZ()、必ずBitmapFactory.Optionswith inPurgeableset to true(およびできれば、inInputShareableさらにsetもtrue)を渡してください。

2)は絶対に使用しないでくださいBitmap.createBitmap(width, height, Config.ARGB_8888)。私は絶対に意味しません!数回パスしてもメモリエラーが発生しないことはありません。いかなる量recycle()System.gc()、何が助けません。常に例外が発生しました。実際に機能するもう1つの方法は、ドローアブル(または上記のステップ1を使用してデコードした別のビットマップ)にダミー画像を配置し、それを必要に応じて再スケーリングしてから、結果のビットマップを操作する(キャンバスに渡すなど)もっと楽しくするために)。したがって、代わりに使用する必要があるのは次のとおりBitmap.createScaledBitmap(srcBitmap, width, height, false)です。何らかの理由でブルートフォースのcreateメソッドを使用する必要がある場合は、少なくともを渡しConfig.ARGB_4444ます。

これにより、数日ではなくても数時間節約できることがほぼ保証されています。画像のスケーリングなどについて説明していることは、すべて実際には機能しません(ソリューションのサイズを間違ったり、画像の品質を低下させることを検討している場合を除きます)。


22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;そして Bitmap.createScaledBitmap(srcBitmap, width, height, false);、私はアンドロイド4.0.0にメモリ不足の例外のうちに持っていた私の問題を解決しました。相棒、ありがとな!
Jan-TerjeSørensen、2012

5
Bitmap.createScaledBitmap()呼び出しでは、フラグパラメータとしてtrueを使用する必要があります。そうしないと、拡大時に画像の品質が滑らかになりません。このスレッドをチェックstackoverflow.com/questions/2895065/...
rOrlig

11
それは本当に素晴らしいアドバイスです。この驚くほどしわくちゃのバグのためにGoogleを任せるための追加の+1をお伝えできれば幸いです。つまり、バグではない場合、ドキュメントには「THIS IS HOW YOU PROCESS PHOTOS」と書かれた真剣に点滅するネオンサインが本当に必要です。これは私が2年間これに苦労してきて、今この投稿を見つけたためです。素晴らしい発見。
Yevgeny Simkin 2012

画像を縮小することは間違いなく役立ちますが、これは重要なステップであり、私にとってこの問題を最終的に解決したものです。画像をスケーリングするだけの問題は、画像の数が多い場合、またはソース画像が非常に大きい場合でも、同じ問題が発生する可能性があることです。あなたにエフライムを+1する。
Dave

10
ロリポップのよう、BitmapFactory.Options.inPurgeableBitmapFactory.Options.inInputShareable廃止されましたdeveloper.android.com/reference/android/graphics/...
デニスKniazhev

93

これは既知のバグです。ファイルが大きいことが原因ではありません。AndroidはDrawableをキャッシュするので、いくつかの画像を使用すると、メモリ不足になります。しかし、Androidのデフォルトのキャッシュシステムをスキップすることで、別の方法を見つけました。

解決策:画像を「assets」フォルダーに移動し、次の関数を使用してBitmapDrawableを取得します。

public static Drawable getAssetImage(Context context, String filename) throws IOException {
    AssetManager assets = context.getResources().getAssets();
    InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
    Bitmap bitmap = BitmapFactory.decodeStream(buffer);
    return new BitmapDrawable(context.getResources(), bitmap);
}

79

私はこれと同じ問題を抱えており、BitmapFactory.decodeStreamまたはdecodeFile関数を回避することで解決し、代わりに BitmapFactory.decodeFileDescriptor

decodeFileDescriptor decodeStream / decodeFileとは異なるネイティブメソッドを呼び出すように見えます。

とにかく、うまくいったのはこれでした(上記のようにいくつかのオプションを追加したことに注意してください。ただし、違いはありません。重要なのは、decodeStreamまたはdecodeFileではなくBitmapFactory.decodeFileDescriptorを呼び出すことです)。

private void showImage(String path)   {

    Log.i("showImage","loading:"+path);
    BitmapFactory.Options bfOptions=new BitmapFactory.Options();
    bfOptions.inDither=false;                     //Disable Dithering mode
    bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
    bfOptions.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
    bfOptions.inTempStorage=new byte[32 * 1024]; 

    File file=new File(path);
    FileInputStream fs=null;
    try {
        fs = new FileInputStream(file);
    } catch (FileNotFoundException e) {
        //TODO do something intelligent
        e.printStackTrace();
    }

    try {
        if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
    } catch (IOException e) {
        //TODO do something intelligent
        e.printStackTrace();
    } finally{ 
        if(fs!=null) {
            try {
                fs.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    im.setImageBitmap(bm);
    //bm.recycle();
    bm=null;                        
}

decodeStream / decodeFileで使用されるネイティブ関数に問題があると思います。decodeFileDescriptorを使用すると、別のネイティブメソッドが呼び出されることを確認しました。また、私が読んだのは、「イメージ(ビットマップ)は標準のJavaの方法ではなくネイティブ呼び出しを介して割り当てられることです。割り当ては仮想ヒープの外で行われますが、それに対して カウントされます!


1
メモリから同じ結果が得られますが、実際には、どの方法を使用するかは、メモリから提供されるデータを読み取るために保持しているバイト数に依存します。
PiyushMishra

72

OutOfMemoryErrorはそれを避ける最善の方法はそれに直面してそれを理解することだと思います。

意図的にを引き起こし、メモリ使用量を監視するアプリを作成しましたOutOfMemoryError

このアプリで多くの実験を行った後、以下の結論を得ました。

最初にHoney Combの前にSDKのバージョンについて話します。

  1. ビットマップはネイティブヒープに格納されますが、ガベージコレクションが自動的に行われるため、recycle()を呼び出す必要はありません。

  2. {VMヒープサイズ} + {割り当てられたネイティブヒープメモリ}> = {デバイスのVMヒープサイズ制限}で、ビットマップを作成しようとすると、OOMがスローされます。

    注意:VM割り当て済みメモリではなく、VMヒープサイズがカウントされます。

  3. VMヒープサイズは、割り当てられたVMメモリが縮小された場合でも、拡大後は縮小されません。

  4. したがって、ビットマップに使用可能なメモリを節約するためにVMヒープサイズが大きくなりすぎないようにするには、ピークVMメモリをできるだけ低く保つ必要があります。

  5. 手動でSystem.gc()を呼び出すのは意味がありません。システムは、ヒープサイズを大きくする前に、最初にそれを呼び出します。

  6. ネイティブヒープサイズも縮小されることはありませんが、OOMにはカウントされないため、心配する必要はありません。

次に、Honey CombからのSDK Startsについて話しましょう。

  1. ビットマップはVMヒープに格納され、ネイティブメモリはOOMにカウントされません。

  2. OOMの条件ははるかに単純です:{VMヒープサイズ}> = {デバイスのVMヒープサイズ制限}。

  3. したがって、同じヒープサイズ制限でビットマップを作成するために利用可能なメモリが増えると、OOMがスローされる可能性が低くなります。

これが、ガベージコレクションとメモリリークに関する私の観察の一部です。

あなたはそれを自分でアプリで見ることができます。アクティビティが破棄された後も実行中であったAsyncTaskをアクティビティが実行した場合、そのアクティビティは、AsyncTaskが完了するまでガベージコレクションされません。

これは、AsyncTaskが匿名の内部クラスのインスタンスであるため、アクティビティの参照を保持しているためです。

タスクがバックグラウンドスレッドのIO操作でブロックされている場合、AsyncTask.cancel(true)を呼び出しても実行は停止しません。

コールバックも匿名の内部クラスであるため、プロジェクトの静的インスタンスがそれらを保持していて解放しないと、メモリがリークします。

タイマーなどの繰り返しタスクまたは遅延タスクをスケジュールし、onPause()でcancel()およびpurge()を呼び出さない場合、メモリがリークします。


AsyncTaskは必ずしも「匿名の内部クラスのインスタンス」である必要はありません。同じことがCallbackksにも当てはまります。AsyncTaskを拡張する独自のファイルで、またはprivate static class同じクラスでさえ、新しいパブリッククラスを作成できます。彼らはアクティビティへの参照を保持しません(もちろん、あなたが彼らに1つを与えない限り)
Simon Forsberg 2013年

65

最近、OOMの例外とキャッシングに関する多くの質問を見てきました。開発者ガイドにはこれに関する非常に優れた記事がありますが、適切な方法で実装することに失敗する傾向がある人もいます。

このため、Android環境でのキャッシングを示すサンプルアプリケーションを作成しました。この実装はまだOOMを取得していません。

ソースコードへのリンクについては、この回答の最後をご覧ください。

要件:

  • Android API 2.1以降(API 1.6のアプリケーションで使用可能なメモリを取得できませんでした。これは、API 1.6で機能しない唯一のコードです)
  • Androidサポートパッケージ

スクリーンショット

特徴:

  • シングルトンを使用して、向きが変更された場合にキャッシュを保持します
  • 利用八分の一のキャッシュに割り当てられたアプリケーションメモリの(必要に応じ変更)
  • 大きなビットマップはスケーリングされます(許可する最大ピクセルを定義できます)
  • ビットマップをダウンロードする前にインターネット接続が利用可能であることを制御ます
  • 行ごとに1 つのタスクのみをインスタンス化していることを確認します
  • 場合あなたが投げつけているListViewすぐに、それは単に間のビットマップをダウンロードしません

これには含まれません:

  • ディスクキャッシング。これはとにかく簡単に実装できるはずです-ディスクからビットマップを取得する別のタスクをポイントするだけです

サンプルコード:

ダウンロードされる画像は、Flickrからの画像(75x75)です。ただし、処理する画像のURLは何でも入れてください。最大値を超えると、アプリケーションによって縮小されます。このアプリケーションでは、URLは単純にString配列になっています。

LruCacheビットマップに対処するための良い方法を持っています。ただし、このアプリケーションではLruCache、アプリケーションをより実現可能にするために、作成した別のキャッシュクラスの内部にインスタンスを配置しました。

Cache.javaの重要なもの(loadBitmap()メソッドが最も重要です):

public Cache(int size, int maxWidth, int maxHeight) {
    // Into the constructor you add the maximum pixels
    // that you want to allow in order to not scale images.
    mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;

    mBitmapCache = new LruCache<String, Bitmap>(size) {
        protected int sizeOf(String key, Bitmap b) {
            // Assuming that one pixel contains four bytes.
            return b.getHeight() * b.getWidth() * 4;
        }
    };

    mCurrentTasks = new ArrayList<String>();    
}

/**
 * Gets a bitmap from cache. 
 * If it is not in cache, this method will:
 * 
 * 1: check if the bitmap url is currently being processed in the
 * BitmapLoaderTask and cancel if it is already in a task (a control to see
 * if it's inside the currentTasks list).
 * 
 * 2: check if an internet connection is available and continue if so.
 * 
 * 3: download the bitmap, scale the bitmap if necessary and put it into
 * the memory cache.
 * 
 * 4: Remove the bitmap url from the currentTasks list.
 * 
 * 5: Notify the ListAdapter.
 * 
 * @param mainActivity - Reference to activity object, in order to
 * call notifyDataSetChanged() on the ListAdapter.
 * @param imageKey - The bitmap url (will be the key).
 * @param imageView - The ImageView that should get an
 * available bitmap or a placeholder image.
 * @param isScrolling - If set to true, we skip executing more tasks since
 * the user probably has flinged away the view.
 */
public void loadBitmap(MainActivity mainActivity, 
        String imageKey, ImageView imageView,
        boolean isScrolling) {
    final Bitmap bitmap = getBitmapFromCache(imageKey); 

    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.ic_launcher);
        if (!isScrolling && !mCurrentTasks.contains(imageKey) && 
                mainActivity.internetIsAvailable()) {
            BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
                    mainActivity.getAdapter());
            task.execute();
        }
    } 
}

ディスクキャッシングを実装する場合を除き、Cache.javaファイルの内容を編集する必要はありません。

MainActivity.javaの重要なもの:

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (view.getId() == android.R.id.list) {
        // Set scrolling to true only if the user has flinged the       
        // ListView away, hence we skip downloading a series
        // of unnecessary bitmaps that the user probably
        // just want to skip anyways. If we scroll slowly it
        // will still download bitmaps - that means
        // that the application won't wait for the user
        // to lift its finger off the screen in order to
        // download.
        if (scrollState == SCROLL_STATE_FLING) {
            mIsScrolling = true;
        } else {
            mIsScrolling = false;
            mListAdapter.notifyDataSetChanged();
        }
    } 
}

// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {           
    View row = convertView;
    final ViewHolder holder;

    if (row == null) {
        LayoutInflater inflater = getLayoutInflater();
        row = inflater.inflate(R.layout.main_listview_row, parent, false);  
        holder = new ViewHolder(row);
        row.setTag(holder);
    } else {
        holder = (ViewHolder) row.getTag();
    }   

    final Row rowObject = getItem(position);

    // Look at the loadBitmap() method description...
    holder.mTextView.setText(rowObject.mText);      
    mCache.loadBitmap(MainActivity.this,
            rowObject.mBitmapUrl, holder.mImageView,
            mIsScrolling);  

    return row;
}

getView()頻繁に呼び出されます。通常、行ごとに無限のスレッドを開始しないことを確認するチェックを実装していない場合、そこに画像をダウンロードすることはお勧めできません。Cache.javaは、rowObject.mBitmapUrlすでにタスクが存在するかどうかを確認し、存在する場合は別のタスクを開始しません。したがって、AsyncTaskプールからのワークキュー制限を超えることはほとんどありません。

ダウンロード:

ソースコードはhttps://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zipからダウンロードできます。


最後の言葉:

私はこれを数週間テストしましたが、まだ単一のOOM例外を受け取っていません。エミュレータ、Nexus One、Nexus Sでこれをテストしました。HD品質の画像を含む画像URLをテストしました。唯一のボトルネックは、ダウンロードに時間がかかることです。

OOMが表示されることを想像できるシナリオは1つだけあります。それは、非常に大きな画像を多数ダウンロードし、それらがスケーリングされてキャッシュに入れられる前に、同時により多くのメモリを消費してOOMを引き起こす場合です。しかし、それはいずれにしても理想的な状況ではなく、より現実的な方法で解決することはおそらく不可能です。

コメントでエラーを報告してください!:-)


43

次のようにして画像を取得し、その場でサイズを変更しました。お役に立てれば

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    

26
このアプローチはビットマップをスケーリングします。しかし、完全なビットマップがとにかくデコードされているので、それはOutOfMemoryの問題を解決しません。
Fedor

5
古いコードを確認できるかどうかはわかりますが、メモリ不足の問題は解決されたと思います。古いコードを再確認します。
Chrispix

2
少なくともこの例では、完全なビットマップへの参照を保持していないように見えるため、メモリを節約できます。
NoBugs 2013年

私にとっては、メモリの問題は解決しましたが、画像の品質が低下しました。
Pamela Sillah、2018

35

これは非常に長い間実行されている問題であり、多くの異なる説明があります。ここでは最も一般的な2つの回答をアドバイスしましたが、どちらも、プロセスのデコード部分を実行するためのバイトを確保できないというVMの問題を解決しませんでした。少し掘り下げた後、ここでの本当の問題は、ネイティブヒープから奪うデコードプロセスにあることがわかりました

ここを参照してください:BitmapFactory OOMが私を運転しています

そのため、別のディスカッションスレッドに進み、この問題の解決策をいくつか見つけました。1つはSystem.gc();、画像が表示された後に手動で呼び出すことです。しかし実際には、ネイティブヒープを減らすために、アプリはより多くのメモリを使用します。2.0(Donut)のリリースでのより良い解決策は、BitmapFactoryオプション "inPurgeable"を使用することです。だから私は単にo2.inPurgeable=true;直後に追加しましたo2.inSampleSize=scale;

ここでそのトピックの詳細:メモリヒープの制限は6Mだけですか?

さて、これらすべてを言ったので、私はJavaとAndroidにも完全に満足しています。したがって、これがこの問題を解決するための恐ろしい方法であると思うなら、あなたはおそらく正しいです。;-)しかし、これは私にとっては不思議に働いており、ヒープキャッシュからVMを実行することは現在不可能であることがわかりました。私が見つけることができる唯一の欠点は、キャッシュされた描画画像をゴミ箱に入れていることです。つまり、その画像に右に戻ると、毎回それを再描画していることになります。私のアプリケーションがどのように機能するかという場合、それは本当に問題ではありません。あなたのマイレージは異なる場合があります。


inPurgeableの固定OOMを使用してください。
Artem Russakovskii、2011年

35

残念ながら上記のどれも機能しない場合は、マニフェストファイルに追加してください。アプリケーションタグの内側

 <application
         android:largeHeap="true"

1
これが実際に何をするか説明できますか?これを追加するように人々に伝えるだけでは効果がありません。
Stealth Rabbi

1
これは非常に悪い解決策です。基本的には、問題を修正しようとはしていません。代わりに、アプリケーションにヒープ領域を割り当てるようにandroidシステムに要求します。これは、GCがメモリをクリーンアップするために大きなヒープスペースを実行する必要があり、アプリのパフォーマンスが低下するため、アプリがバッテリー電力を大量に消費するなど、アプリに非常に悪い影響を及ぼします。
Prakash 2017

2
では、なぜandroidがこのandroid:largeHeap = "true"をマニフェストに追加できるのですか?今、あなたはAndroidに挑戦しています。
ヒマンシュモリ2017


28

私はあらゆる種類のスケーリングを必要としないはるかに効果的なソリューションを持っています。ビットマップを一度だけデコードし、その名前に対してマップにキャッシュします。次に、名前に対してビットマップを取得し、ImageViewに設定します。実行する必要があることはこれ以上ありません。

これは、デコードされたビットマップの実際のバイナリデータがdalvik VMヒープ内に格納されていないため機能します。外部に保存されます。したがって、ビットマップをデコードするたびに、VMヒープの外部にメモリが割り当てられ、GCによって再利用されることはありません。

あなたがこれをよりよく理解するのを助けるために、あなたがあなたのイメージをドローアブルフォルダに保持していると想像してください。getResources()。getDrwable(R.drawable。)を実行して画像を取得するだけです。これは毎回イメージをデコードするわけではありませんが、呼び出すたびに既にデコードされたインスタンスを再利用します。つまり、本質的にはキャッシュされます。

これで、画像がどこかのファイル(または外部サーバーからのファイル)にあるため、デコードされたビットマップインスタンスをキャッシュして、必要な場所で再利用する必要があります。

お役に立てれば。


4
「そして、その名前に対してマップにキャッシュします。」どのように正確に画像をキャッシュしますか?
Vincent

3
これを実際に試しましたか?ピクセルデータは実際にはDalvikヒープ内に格納されていませんが、ネイティブメモリ内のそのサイズはVMに報告され、使用可能なメモリに対してカウントされます。
ErikR

3
@ビンセント私はそれらを地図に保存するのは難しくないと思います。私はHashMap <KEY、Bitmap>マップのようなものを提案します。ここで、キーはソースの文字列またはあなたにとって意味のある何かであることができます。パスをKEYとして受け取り、それをmap.put(Path、Bitmap)として保存し、map.get(Path)を介して受け取ると仮定しましょう
Rafael T

3
画像キャッシュを実装している場合は、HashMap <String、SoftReference <Bitmap >>を使用する可能性があります。そうしないと、とにかくメモリ不足になる可能性があります。また、「GCによって回収されないVMヒープの外部にメモリを割り当てることもありません」 "は本当です。メモリが解放されるのは、遅延がビットマップである可能性があることを理解しているためです。bitmap.recycle()の目的は、メモリを早期に解放するためのヒントとして...
Dori

28

同じ問題を次の方法で解決しました。

Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
    b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
    b.eraseColor(0xFFFFFFFF);
    Rect r = new Rect(0, 0,320 , 424);
    Canvas c = new Canvas(b);
    Paint p = new Paint();
    p.setColor(0xFFC0C0C0);
    c.drawRect(r, p);
    d = mContext.getResources().getDrawable(mImageIds[position]);
    d.setBounds(r);
    d.draw(c);

    /*   
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inTempStorage = new byte[128*1024];
        b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
        o2.inSampleSize=16;
        o2.inPurgeable = true;
    */
} catch (Exception e) {

}
i.setImageBitmap(b);

それはすべての権利が、2番目の時間のために再度ビットマップを削除し、作成する方法を明確にビットマップへとどのように私は、OnCreateイベントでドロー円と活動呼び出しの4-5倍を複数のビットマップを使用していたときに活動0nCreate ...
ckpatel

27

ここには2つの問題があります。

  • ビットマップメモリ​​はVMヒープ内ではなく、ネイティブヒープ内にあります-BitmapFactory OOMが私を運転しているのを見てください
  • ネイティブヒープのガベージコレクションはVMヒープよりも遅延しているため、アクティビティのonPauseまたはonDestroyを実行するたびに、bitmap.recycleおよびbitmap = nullを実行することを非常に積極的に行う必要があります。

Android 2.3以降、VMヒープ内にあります
FindOut_Quran

27

これは私のために働いた!

public Bitmap readAssetsBitmap(String filename) throws IOException {
    try {
        BitmapFactory.Options options = new BitmapFactory.Options(); 
        options.inPurgeable = true;
        Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
        if(bitmap == null) {
            throw new IOException("File cannot be opened: It's value is null");
        } else {
            return bitmap;
        }
    } catch (IOException e) {
        throw new IOException("File cannot be opened: " + e.getMessage());
    }
}

20

上記の答えはどれもうまくいきませんでしたが、問題を解決する恐ろしく醜い回避策を思いつきました。非常に小さな1x1ピクセルの画像をリソースとしてプロジェクトに追加し、ガベージコレクションを呼び出す前にImageViewにロードしました。ImageViewがビットマップを解放していないため、GCがビットマップを取得しなかった可能性があります。それは醜いですが、今のところ機能しているようです。

if (bitmap != null)
{
  bitmap.recycle();
  bitmap = null;
}
if (imageView != null)
{
  imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();

imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.

imageView自体はビットマップを実際にはリサイクルしないように見えます。助けてくれてありがとう
Dmitry Zaytsev

@Mikeは、imageloaderの完全なコードを追加できますか、それともビットマップ画像のロードのリンクを教えてくれますか。ビットマップでリサイクルを使用すると、すべてのリストビューが表示されますが、すべてのアイテムが空白で表示されます
TNR

@マイクは、ガベージコレクションを呼び出す前にimageView = nullを実行するかどうかを確認できますか?
Youddh 2012年

@TNR私がここで見逃しているのはbitmap、上のコードでは以前に表示された画像であり、それをリサイクルし、参照をすべてクリアしimageView、小さな置換を設定して忘れさせることgc()です。そして結局のところ、上記のコードのNEW画像をロードしbitmapて表示し...ます。
TWiStErRob 2014

これは間違っています。ビットマップをリサイクルする前に(実際に表示されて使用されている間ではなく)、常にimageViewコンテンツをクリアする必要があります。
FindOut_Quran

20

ここでは素晴らしい答えですが、私はこの問題に対処するために完全に使用可能なクラスを求めていました。

これがOutOfMemoryErrorの証明である私のBitmapHelperクラスです:-)

import java.io.File;
import java.io.FileInputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

public class BitmapHelper
{

    //decodes image and scales it to reduce memory consumption
    public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
    {
        try
        {
            //Decode image size
            BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
            bitmapSizeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);

            // load image using inSampleSize adapted to required image size
            BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
            bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
            bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
            bitmapDecodeOptions.inPurgeable = true;
            bitmapDecodeOptions.inDither = !quickAndDirty;
            bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

            Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);

            // scale bitmap to mathc required size (and keep aspect ratio)

            float srcWidth = (float) bitmapDecodeOptions.outWidth;
            float srcHeight = (float) bitmapDecodeOptions.outHeight;

            float dstWidth = (float) requiredWidth;
            float dstHeight = (float) requiredHeight;

            float srcAspectRatio = srcWidth / srcHeight;
            float dstAspectRatio = dstWidth / dstHeight;

            // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
            // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
            // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
            // I do not excatly understand why, but this way it's OK

            boolean recycleDecodedBitmap = false;

            Bitmap scaledBitmap = decodedBitmap;
            if (srcAspectRatio < dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
                // will recycle recycleDecodedBitmap
                recycleDecodedBitmap = true;
            }
            else if (srcAspectRatio > dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
                recycleDecodedBitmap = true;
            }

            // crop image to match required image size

            int scaledBitmapWidth = scaledBitmap.getWidth();
            int scaledBitmapHeight = scaledBitmap.getHeight();

            Bitmap croppedBitmap = scaledBitmap;

            if (scaledBitmapWidth > requiredWidth)
            {
                int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }
            else if (scaledBitmapHeight > requiredHeight)
            {
                int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }

            if (recycleDecodedBitmap)
            {
                decodedBitmap.recycle();
            }
            decodedBitmap = null;

            scaledBitmap = null;
            return croppedBitmap;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
     * 
     * @param requiredWidth
     * @param requiredHeight
     * @param powerOf2
     *            weither we want a power of 2 sclae or not
     * @return
     */
    public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
    {
        int inSampleSize = 1;

        // Raw height and width of image
        final int srcHeight = options.outHeight;
        final int srcWidth = options.outWidth;

        if (powerOf2)
        {
            //Find the correct scale value. It should be the power of 2.

            int tmpWidth = srcWidth, tmpHeight = srcHeight;
            while (true)
            {
                if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
                    break;
                tmpWidth /= 2;
                tmpHeight /= 2;
                inSampleSize *= 2;
            }
        }
        else
        {
            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
            final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

    public static Bitmap drawableToBitmap(Drawable drawable)
    {
        if (drawable instanceof BitmapDrawable)
        {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

    public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
    {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;

        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // RECREATE THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
        return resizedBitmap;
    }

}

これを使用している人へ:私はただバグを修正しました: "int scaledBitmapHeight = scaledBitmap.getWidth();" 間違いです(当然です。「int scaledBitmapHeight = scaledBitmap.getHeight();」に置き換えました
Pascal

19

これでうまくいきます。

Bitmap myBitmap;

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;

File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

これはC#モノドロイドにあります。画像のパスは簡単に変更できます。ここで重要なのは、設定するオプションです。


16

これは、画像を読み込んで処理するためのユーティリティクラスをコミュニティと共有するのに適した場所のようです。自由に使用して変更してください。

package com.emil;

import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * A class to load and process images of various sizes from input streams and file paths.
 * 
 * @author Emil http://stackoverflow.com/users/220710/emil
 *
 */
public class ImageProcessing {

    public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    public static Dimensions getDimensions(InputStream stream) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Dimensions getDimensions(String imgPath) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    private static boolean checkDecode(BitmapFactory.Options options){
        // Did decode work?
        if( options.outWidth<0 || options.outHeight<0 ){
            return false;
        }else{
            return true;
        }
    }

    /**
     * Creates a Bitmap that is of the minimum dimensions necessary
     * @param bm
     * @param min
     * @return
     */
    public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
        int newWidth, newHeight;
        switch(min.type){
        case WIDTH:
            if(bm.getWidth()>min.minWidth){
                newWidth=min.minWidth;
                newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case HEIGHT:
            if(bm.getHeight()>min.minHeight){
                newHeight=min.minHeight;
                newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case BOTH: // minimize to the maximum dimension
        case MAX:
            if(bm.getHeight()>bm.getWidth()){
                // Height needs to minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
                if(bm.getHeight()>min.minDim){
                    newHeight=min.minDim;
                    newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }else{
                // Width needs to be minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
                if(bm.getWidth()>min.minDim){
                    newWidth=min.minDim;
                    newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }
            break;
        default:
            // No resize
            newWidth=bm.getWidth();
            newHeight=bm.getHeight();
        }
        return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
    }

    public static int getScaledWidth(int height, Bitmap bm){
        return (int)(((double)bm.getWidth()/bm.getHeight())*height);
    }

    public static int getScaledHeight(int width, Bitmap bm){
        return (int)(((double)bm.getHeight()/bm.getWidth())*width);
    }

    /**
     * Get the proper sample size to meet minimization restraints
     * @param dim
     * @param min
     * @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
     * @return
     */
    public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
        switch(min.type){
        case WIDTH:
            return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
        case HEIGHT:
            return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
        case BOTH:
            int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
            int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
            // Return the smaller of the two
            if(widthMaxSampleSize<heightMaxSampleSize){
                return widthMaxSampleSize;
            }else{
                return heightMaxSampleSize;
            }
        case MAX:
            // Find the larger dimension and go bases on that
            if(dim.width>dim.height){
                return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
            }else{
                return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
            }
        }
        return 1;
    }

    public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
        int add=multipleOf2 ? 2 : 1;
        int size=0;
        while(min<(dim/(size+add))){
            size+=add;
        }
        size = size==0 ? 1 : size;
        return size;        
    }

    public static class Dimensions {
        int width;
        int height;

        public Dimensions(int width, int height) {
            super();
            this.width = width;
            this.height = height;
        }

        @Override
        public String toString() {
            return width+" x "+height;
        }
    }

    public static class Minimize {
        public enum Type {
            WIDTH,HEIGHT,BOTH,MAX
        }
        Integer minWidth;
        Integer minHeight;
        Integer minDim;
        Type type;

        public Minimize(int min, Type type) {
            super();
            this.type = type;
            switch(type){
            case WIDTH:
                this.minWidth=min;
                break;
            case HEIGHT:
                this.minHeight=min;
                break;
            case BOTH:
                this.minWidth=min;
                this.minHeight=min;
                break;
            case MAX:
                this.minDim=min;
                break;
            }
        }

        public Minimize(int minWidth, int minHeight) {
            super();
            this.type=Type.BOTH;
            this.minWidth = minWidth;
            this.minHeight = minHeight;
        }

    }

    /**
     * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
     * @param width
     * @param height
     * @param config
     * @return
     */
    public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
        long pixels=width*height;
        switch(config){
        case ALPHA_8: // 1 byte per pixel
            return pixels;
        case ARGB_4444: // 2 bytes per pixel, but depreciated
            return pixels*2;
        case ARGB_8888: // 4 bytes per pixel
            return pixels*4;
        case RGB_565: // 2 bytes per pixel
            return pixels*2;
        default:
            return pixels;
        }
    }

    private static BitmapFactory.Options getOptionsForDimensions(){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        return options;
    }

    private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = sampleSize;
        options.inScaled = false;
        options.inPreferredConfig = bitmapConfig;
        return options;
    }
}

16

私のアプリケーションの1つで、から写真を撮る必要がありますCamera/Gallery。ユーザーがカメラから画像をクリックすると(2MP、5MP、8MPの場合があります)、画像のサイズはkBs からsに変化しMBます。画像サイズがコードよりも小さい(または最大1〜2MB)場合は正常に機能しますが、4MBまたは5MBを超えるサイズの画像がある場合OOMは、フレーム:(

それから私はこの問題を解決するために働きました、そして最後に私はFedorの(そのような素晴らしい解決策を作るためのFedorへのすべてのクレジット)コードに以下の改善を行いました:)

private Bitmap decodeFile(String fPath) {
    // Decode image size
    BitmapFactory.Options opts = new BitmapFactory.Options();
    /*
     * If set to true, the decoder will return null (no bitmap), but the
     * out... fields will still be set, allowing the caller to query the
     * bitmap without having to allocate the memory for its pixels.
     */
    opts.inJustDecodeBounds = true;
    opts.inDither = false; // Disable Dithering mode
    opts.inPurgeable = true; // Tell to gc that whether it needs free
                                // memory, the Bitmap can be cleared
    opts.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

    BitmapFactory.decodeFile(fPath, opts);

    // The new size we want to scale to
    final int REQUIRED_SIZE = 70;

    // Find the correct scale value. 
    int scale = 1;

    if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) opts.outHeight
                / (float) REQUIRED_SIZE);
        final int widthRatio = Math.round((float) opts.outWidth
                / (float) REQUIRED_SIZE);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
    }

    // Decode bitmap with inSampleSize set
    opts.inJustDecodeBounds = false;

    opts.inSampleSize = scale;

    Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
            Bitmap.Config.RGB_565, false);

    return bm;

}

これが同じ問題に直面している仲間を助けることを願っています!

以下のためのより多くの参照してください。これを


14

数分前にこの問題に遭遇しました。私は、リストビューアダプターの管理を改善することで解決しました。私が使用している何百もの50x50px画像の問題だと思ったので、行が表示されるたびにカスタムビューを拡大しようとしていることがわかりました。行が膨らんでいるかどうかを確認するためにテストするだけで、このエラーを排除し、何百ものビットマップを使用しています。これは実際にはSpinner用ですが、ベースアダプターはListViewでもまったく同じように機能します。この単純な修正により、アダプターのパフォーマンスも大幅に向上しました。

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {

    if(convertView == null){
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.spinner_row, null);
    }
...

3
本当にありがとうございました!これを見る前に私は間違った問題を追いかけていました。ただし、質問:リストの各行または一意の名前と写真があるため、行の各値を保持するためにconvertView配列を使用する必要がありました。単一の変数を使用すると、どのようにそれができるのかわかりませんでした。何か不足していますか?
PeteH 2013

13

私は一日中これらのソリューションのテストを費やしてきましたが、私のために働いた唯一のことは、イメージを取得して手動でGCを呼び出すための上記のアプローチであり、これは必要ではないことがわかっていますが、それだけが機能しました私のアプリを高負荷テストの下に置くと、アクティビティが切り替わります。私のアプリには(アクティビティAと言う)リストビューにサムネイル画像のリストがあり、それらの画像の1つをクリックすると、そのアイテムのメイン画像を表示する別のアクティビティ(アクティビティBと言う)に移動します。2つのアクティビティを切り替えると、最終的にOOMエラーが発生し、アプリが強制終了します。

私がリストビューの半分を下ると、クラッシュしました。

これで、アクティビティBに次のコードを実装すると、リストビュー全体を問題なく実行でき、何度も何度も進み続けることができます。

@Override
public void onDestroy()
{   
    Cleanup();
    super.onDestroy();
}

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}

ソリューションが大好きです!このバグの解決にも何時間も費やしているので、迷惑です。編集:悲しいことに、横向きモードで画面の向きを変更しても問題は解決しません...
Xarialon

これは最後に私と一緒に助けました:-BitmapFactory.Options options = new BitmapFactory.Options(); options.InPurgeable = true; options.InSampleSize = 2;
user3833732 2016年

13

この問題はAndroidエミュレータでのみ発生します。エミュレータでもこの問題に直面しましたが、デバイスをチェックインすると問題なく動作しました。

デバイスをチェックインしてください。デバイスで実行される可能性があります。


12

私の2セント:ビットマップを使用してOOMエラーを解決しました:

a)画像を2倍にスケーリングする

b)次のように、getViewでの1回の呼び出しで、ListViewのカスタムアダプタでPicassoライブラリを使用します。Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);


画像の読み込みがはるかに簡単になるので、ピカソについて言及していただきありがとうございます。特にリモートで保存されたもの。
Chrispix 2013

12

SdCardまたはdrewableから選択したすべての画像にこれらのコードを使用して、ビットマップオブジェクトを変換します。

Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    bitmap = Bitmap.createScaledBitmap(BitmapFactory
        .decodeFile(ImageData_Path.get(img_pos).getPath()),
        width, height, true);
} catch (OutOfMemoryError e) {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Config.RGB_565;
    options.inSampleSize = 1;
    options.inPurgeable = true;
    bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
        .getPath().toString(), options), width, height,true);
}
return bitmap;

ImageData_Path.get(img_pos).getPath()のイメージパスインスタンスを使用します。


12

通常、Androidデバイスのヒープサイズはわずか16 MBです(デバイス/ OSによって異なりますポストヒープサイズを参照してください)。画像を読み込んでいて、サイズが16 MBを超える場合、ビットマップを使用する代わりに、メモリ例外がスローされます。 SDカードまたはリソースから、さらにはネットワークからの画像でもgetImageUriを使用してみてください。ビットマップのロードにはより多くのメモリが必要です。または、ビットマップで作業を行った場合はビットマップをnullに設定できます。


1
setImageURIはまだ例外を取得した場合、その後、この参照してくださいstackoverflow.com/questions/15377186/...
マヘシュ

11

ここでのすべてのソリューションでは、IMAGE_MAX_SIZEを設定する必要があります。これにより、より強力なハードウェアを備えたデバイスが制限され、画像サイズが小さすぎると、HD画面で見栄えが悪くなります。

私のサムスンギャラクシーS3と他のいくつかのデバイスで動作するソリューションを思い付きました。

その要点は、特定のデバイスでアプリに割り当てられた最大メモリを計算し、このメモリを超えない範囲でスケールをできるだけ低く設定することです。コードは次のとおりです。

public static Bitmap decodeFile(File f)
{
    Bitmap b = null;
    try
    {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        try
        {
            BitmapFactory.decodeStream(fis, null, o);
        }
        finally
        {
            fis.close();
        }

        // In Samsung Galaxy S3, typically max memory is 64mb
        // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
        // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
        // We try use 25% memory which equals to 16mb maximum for one bitmap
        long maxMemory = Runtime.getRuntime().maxMemory();
        int maxMemoryForImage = (int) (maxMemory / 100 * 25);

        // Refer to
        // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
        // A full screen GridView filled with images on a device with
        // 800x480 resolution would use around 1.5MB (800*480*4 bytes)
        // When bitmap option's inSampleSize doubled, pixel height and
        // weight both reduce in half
        int scale = 1;
        while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
        scale *= 2;

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        try
        {
            b = BitmapFactory.decodeStream(fis, null, o2);
        }
        finally
        {
            fis.close();
        }
    }
    catch (IOException e)
    {
    }
    return b;
}

このビットマップで使用される最大メモリを割り当てられた最大メモリの25%に設定します。これを必要に応じて調整し、このビットマップがクリーンアップされ、使用が終了したときにメモリ内にとどまらないようにする必要があります。通常、このコードを使用して画像の回転(ソースと宛先のビットマップ)を実行するため、アプリはメモリに2つのビットマップを同時に読み込む必要があり、画像の回転を実行すると、メモリが不足することなく25%で適切なバッファーが得られます。

これが誰かを助けることを願っています。


11

などはをOutofMemoryException呼び出すことで完全に解決することはできませんSystem.gc()

アクティビティのライフサイクルを参照して

アクティビティの状態は、各プロセスのメモリ使用量と各プロセスの優先順位に従って、OS自体によって決定されます。

使用する各ビットマップ画像のサイズと解像度を考慮することができます。サイズを減らし、解像度を下げてリサンプリングし、ギャラリーのデザインを参照することをお勧めします(1つの小さな画像PNGと1つの元の画像)。


11

このコードは、ドローアブルから大きなビットマップをロードするのに役立ちます

public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {

    Context context;

    public BitmapUtilsTask(Context context) {
        this.context = context;
    }

    /**
     * Loads a bitmap from the specified url.
     * 
     * @param url The location of the bitmap asset
     * @return The bitmap, or null if it could not be loaded
     * @throws IOException
     * @throws MalformedURLException
     */
    public Bitmap getBitmap() throws MalformedURLException, IOException {       

        // Get the source image's dimensions
        int desiredWidth = 1000;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        int srcWidth = options.outWidth;
        int srcHeight = options.outHeight;

        // Only scale if the source is big enough. This code is just trying
        // to fit a image into a certain width.
        if (desiredWidth > srcWidth)
            desiredWidth = srcWidth;

        // Calculate the correct inSampleSize/scale value. This helps reduce
        // memory use. It should be a power of 2
        int inSampleSize = 1;
        while (srcWidth / 2 > desiredWidth) {
            srcWidth /= 2;
            srcHeight /= 2;
            inSampleSize *= 2;
        }
        // Decode with inSampleSize
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = inSampleSize;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inPurgeable = true;
        Bitmap sampledSrcBitmap;

        sampledSrcBitmap =  BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        return sampledSrcBitmap;
    }

    /**
     * The system calls this to perform work in a worker thread and delivers
     * it the parameters given to AsyncTask.execute()
     */
    @Override
    protected Bitmap doInBackground(Object... item) {
        try { 
          return getBitmap();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

いいですね、非同期タスクの代わりにローダーを使用する方が良いと思いますか?
Chrispix 2012年

いかがBitmap.Config.ARGB_565ですか?高品質が重要でない場合。
Hamzeh Soboh 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.