1. ホーム
  2. android

[解決済み] Bitmapオブジェクトに画像を読み込む際にOutOfMemoryが発生する問題

2022-03-20 23:50:16

質問

私は ListView 各行にいくつかの画像ボタンがあります。ユーザーがリストの行をクリックすると、新しいアクティビティが起動します。カメラのレイアウトに問題があるため、独自のタブを構築する必要がありました。その結果、起動されるアクティビティは地図です。私のボタンをクリックして画像プレビューを起動すると(SDカードから画像をロード)、アプリケーションはアクティビティから戻って ListView アクティビティから結果ハンドラへ移動し、画像ウィジェットに過ぎない新しいアクティビティを再スタートさせます。

の画像プレビューは ListView はカーソルで行っており ListAdapter . これは非常にシンプルですが、リサイズされた画像(つまり、ピクセルではなくビットサイズを小さくしたもの)を、どのように src をオンザフライで画像ボタンに使用します。だから、私はちょうど携帯電話のカメラから来た画像をリサイズしました。

問題なのは OutOfMemoryError 2つ目のアクティビティに戻り、再度起動しようとすると

  • リスト・アダプタを一行ずつ簡単に構築して、その場でサイズを変更できる方法はありますか( ビット単位 )?

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

  • 帯域外のサイズを変更して画像を保存できることは知っていますが、私が本当にやりたいのはそれではなく、そのためのいくつかのサンプルコードがあればいいと思います。

の画像を無効化した途端 ListView は、再び正常に動作するようになりました。

参考:こんな感じでやってました。

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.imagefilenameButtonImage .

以下は私の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 

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

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

解決方法は?

その Androidトレーニング クラス、"。 ビットマップの効率的な表示 java.lang.OutOfMemoryError: bitmap size exceeds VM budget when loading Bitmaps "という例外を理解し対処するための素晴らしい情報を提供しています。


ビットマップの寸法と種類を読む

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

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ピクセルのサムネイルとして ImageView .

デコーダーに画像をサブサンプリングして、より小さいバージョンをメモリに読み込むように指示するには inSampleSize から true をあなたの BitmapFactory.Options オブジェクトを作成します。例えば、解像度2048x1536の画像を inSampleSize 4の場合、約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;
}

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

このメソッドを使用するには、まずデコードを inJustDecodeBounds に設定します。 true, pass the options through and then decode again using the new inSampleSize value and inJustDecodeBounds set to 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* メソッドを使用します。