1. ホーム
  2. android

Androidでjava.lang.OutOfMemoryErrorが発生した場合の対処方法

2023-11-26 23:56:20

質問

drawable フォルダに非常に小さなサイズの画像を入れているにもかかわらず、ユーザーからこのエラーが出ます。また、私はコードでビットマップ関数を使用していません。少なくとも意図的に :)

java.lang.OutOfMemoryError
    at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
    at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:683)
    at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:513)
    at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:889)
    at android.content.res.Resources.loadDrawable(Resources.java:3436)
    at android.content.res.Resources.getDrawable(Resources.java:1909)
    at android.view.View.setBackgroundResource(View.java:16251)
    at com.autkusoytas.bilbakalim.SoruEkrani.cevapSecimi(SoruEkrani.java:666)
    at com.autkusoytas.bilbakalim.SoruEkrani$9$1.run(SoruEkrani.java:862)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:146)
    at android.app.ActivityThread.main(ActivityThread.java:5602)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
    at dalvik.system.NativeStart.main(Native Method)

このstackTraceによると、私はこの行でこのエラーを取得している('tv'はtextViewです)。

tv.setBackgroundResource(R.drawable.yanlis);

何が問題なのでしょうか?あなたがコードに関するいくつかの他の情報が必要な場合、私はそれを追加することができます。 ありがとうございます!

どのように解決するには?

ヒープサイズを動的に増やすことはできませんが、ヒープを使用するように要求することは可能です。

android:largeHeap="true"です。

の中に manifest.xml を追加することができ、それはいくつかの状況で動作しています。

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:largeHeap="true"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

アプリケーションのプロセスを大きな Dalvik ヒープで作成するかどうかを指定します。これは、アプリケーション用に作成されたすべてのプロセスに適用されます。これは、プロセスにロードされた最初のアプリケーションにのみ適用されます。共有ユーザー ID を使用して複数のアプリケーションがプロセスを使用できるようにしている場合、すべてのアプリケーションがこのオプションを一貫して使用しなければならず、さもなければ予測不可能な結果が生じます。 ほとんどのアプリケーションはこのオプションを必要とせず、代わりにパフォーマンス向上のために全体のメモリ使用量を減らすことに集中すべきです。デバイスによっては、利用可能なメモリの合計に制約があるため、これを有効にしても、利用可能なメモリの一定の増加を保証するものではありません。


実行時に利用可能なメモリサイズを問い合わせるには、以下のメソッドを使用します。 getMemoryClass() または getLargeMemoryClass() .

それでもまだ問題に直面している場合、これも動作するはずです

 BitmapFactory.Options options = new BitmapFactory.Options();
 options.inSampleSize = 8;
 mBitmapInsurance = BitmapFactory.decodeFile(mCurrentPhotoPath,options);

1 に設定された場合、デコーダにオリジナル画像をサブサンプリングするよう要求し、メモリを節約するために小さい画像を返します。

これは、画像の表示速度に関して、BitmapFactory.Options.inSampleSizeの最適な使用方法です。 ドキュメントでは、2のべき乗の値を使用することに言及しているので、私は2、4、8、16などで作業しています。

画像サンプリングについてもっと深く掘り下げてみましょう。

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

デコーダに画像をサブサンプリングし、より小さいバージョンをメモリに読み込むように指示するために inSampleSizetrue の中に BitmapFactory.Options オブジェクトを作成します。例えば、解像度 2100 x 1500 ピクセルの画像で、デコード時に inSampleSize 4 でデコードされた解像度が 2100 x 1500 ピクセルの画像は、約 512 x 384 ピクセルのビットマップを生成します。これをメモリにロードすると、フル画像で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;
}

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

このメソッドを使うには、まずデコードを inJustDecodeBounds に設定します。 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 に読み込むことができます。

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

他のソースからのビットマップをデコードする場合も同様の手順で、適切な BitmapFactory.decode* メソッドに置き換えることで、他のソースからのビットマップをデコードすることもできます。


私はこのコードも面白いと思いました。

private Bitmap getBitmap(String path) {

Uri uri = getImageUri(path);
InputStream in = null;
try {
    final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
    in = mContentResolver.openInputStream(uri);

    // Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, o);
    in.close();

    int scale = 1;
    while ((o.outWidth * o.outHeight) * (1 / Math.pow(scale, 2)) > 
          IMAGE_MAX_SIZE) {
       scale++;
    }
    Log.d(TAG, "scale = " + scale + ", orig-width: " + o.outWidth + ", 
       orig-height: " + o.outHeight);

    Bitmap bitmap = null;
    in = mContentResolver.openInputStream(uri);
    if (scale > 1) {
        scale--;
        // scale to max possible inSampleSize that still yields an image
        // larger than target
        o = new BitmapFactory.Options();
        o.inSampleSize = scale;
        bitmap = BitmapFactory.decodeStream(in, null, o);

        // resize to desired dimensions
        int height = bitmap.getHeight();
        int width = bitmap.getWidth();
        Log.d(TAG, "1th scale operation dimenions - width: " + width + ",
           height: " + height);

        double y = Math.sqrt(IMAGE_MAX_SIZE
                / (((double) width) / height));
        double x = (y / height) * width;

        Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, (int) x, 
           (int) y, true);
        bitmap.recycle();
        bitmap = scaledBitmap;

        System.gc();
    } else {
        bitmap = BitmapFactory.decodeStream(in);
    }
    in.close();

    Log.d(TAG, "bitmap size - width: " +bitmap.getWidth() + ", height: " + 
       bitmap.getHeight());
    return bitmap;
} catch (IOException e) {
    Log.e(TAG, e.getMessage(),e);
    return null;
}


アプリのメモリを管理する方法。 リンク


を使用するのは android:largeHeap="true" を説明するgoogleの抜粋はこちらです。

ただし、大きなヒープを要求できるのは より多くの RAM を消費する必要性を正当化できる小さなアプリのセット (大規模な写真編集アプリなど) を対象としています。 大規模な写真編集アプリなど)。単にメモリが足りなくなったからと言って、大きなヒープを要求してはいけません。 ラージヒープを要求するのは、メモリが足りなくなり、すぐに修正する必要があるからです。 メモリがどこに割り当てられ、なぜそれを保持する必要があるのかが明確な場合にのみ使用する必要があります。 メモリがどこに割り当てられ、なぜそれが保持されなければならないかを正確に知っている場合にのみ使用すべきです。しかし、たとえ確信があっても しかし、アプリが大規模ヒープを正当化できると確信している場合でも、可能な限りヒープを要求しないようにする必要があります。 可能な限り避けるべきです。余分なメモリを使用することは、ますます ガベージコレクションに時間がかかり、システムのパフォーマンスが低下するためです。 ガベージコレクションに時間がかかり、タスク切り替えやその他の一般的な操作の際にシステムパフォーマンスが低下する可能性があります。 タスクの切り替えやその他の一般的な操作を行う際に、システムのパフォーマンスが低下する可能性があります。

を苛烈に働いた後 out of memory errors を使用しているため、OOM の問題を回避するためにマニフェストにこれを追加することは罪ではないと言えます。


Android ランタイム (ART) でアプリの動作を確認する

Android ランタイム (ART) は、Android 5.0 (API レベル 21) 以降を実行するデバイスのデフォルトのランタイムです。このランタイムは、Android プラットフォームとアプリのパフォーマンスとスムーズさを向上させる多くの機能を提供します。ARTの新機能の詳細については ARTの紹介 .

しかし、Dalvik で動作するいくつかのテクニックは、ART では動作しません。この文書では、既存のアプリを ART と互換性を持つように移行する際に注意すべきことについて説明します。ほとんどのアプリは、ART で実行するときだけ動作するはずです。


ガーベッジコレクション (GC) の問題への対処

Dalvik では、アプリは、ガベージ コレクション (GC) を促すために System.gc() を明示的に呼び出すと便利であることがよくわかります。特に、GC_FOR_ALLOC タイプの発生を防ぐため、または断片化を減らすためにガベージ コレクションを呼び出している場合、これは ART でははるかに必要性が低くなるはずです。System.getProperty("java.vm.version") を呼び出すことで、どのランタイムが使用されているかを確認することができます。ART が使用されている場合、プロパティの値は "2.0.0" またはそれ以上となります。

さらに、Android Open-Source Project (AOSP) では、メモリ管理を改善するためのコンパクト化ガベージコレクタが開発中です。このため、コンパクト化 GC と互換性のない手法 (オブジェクト インスタンス データへのポインターの保存など) の使用は避ける必要があります。これは、Java Native Interface (JNI) を利用するアプリでは特に重要です。詳細については、「JNI の問題の防止」を参照してください。


JNI の問題の防止

ART の JNI は Dalvik のものよりいくらか厳しいです。一般的な問題を検出するために CheckJNI モードを使用することは、特に良いアイデアです。アプリケーションが C/C++ コードを使用する場合は、次の記事を確認する必要があります。


また、ネイティブメモリ( NDK & を使用することもできます。 JNI ) を使用することで、実際にヒープサイズの制限をバイパスすることができます。

それについてなされたいくつかの投稿を紹介します。

と、そのために作られたライブラリがこちらです。