1364

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

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

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
4

44 に答える 44

907

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;
}
于 2009-05-05T09:00:14.040 に答える
379

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;
}
于 2010-08-23T15:25:32.137 に答える
237

私は iOS の経験から来ており、画像の読み込みと表示などの基本的な問題を発見してイライラしました。結局のところ、この問題を抱えている人は皆、適切なサイズの画像を表示しようとしています。とにかく、これが私の問題を解決した(そして私のアプリを非常に反応の良いものにした)2つの変更です。

1) を実行するたびに、 aをset to (およびできれば with set to )BitmapFactory.decodeXYZ()に渡すようにしてください。BitmapFactory.OptionsinPurgeabletrueinInputShareabletrue

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

これにより、数日ではないにしても数時間の節約がほぼ保証されます。画像のスケーリングなどについて話していることはすべて、実際には機能しません (間違ったサイズや劣化した画像を解決策と見なさない限り)。

于 2011-12-15T22:52:23.267 に答える
96

これは既知のバグであり、ファイルが大きいためではありません。Android は Drawables をキャッシュするため、いくつかの画像を使用するとメモリ不足になります。しかし、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);
}
于 2011-05-24T20:17:04.343 に答える
82

これと同じ問題があり、 BitmapFactory.decodeStream または decodeFile 関数を回避して解決し、代わりに使用しましたBitmapFactory.decodeFileDescriptor

decodeFileDescriptordecodeStream/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 の方法ではなく、ネイティブ呼び出しを介して割り当てられることです。割り当ては仮想ヒープの外部で行われますが、それ に対してカウントされます!

于 2011-08-19T01:55:38.447 に答える
74

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

意図的に を発生させ、メモリ使用量を監視するアプリを作成しました。OutOfMemoryError

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

まず、Honey Comb より前の SDK バージョンについて説明します。

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

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

    注意: VM ALLOCATED MEMORY ではなく、VM HEAP SIZE がカウントされます。

  3. 割り当てられた VM メモリが縮小されたとしても、VM ヒープ サイズは成長後に縮小することはありません。

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

  5. 手動で System.gc() を呼び出すのは無意味です。システムは、ヒープ サイズを拡大しようとする前に最初に呼び出します。

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

それでは、SDK Starts from Honey Comb についてお話しましょう。

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

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

  3. 同じヒープ サイズ制限でビットマップを作成するためにより多くの使用可能なメモリがあるため、OOM がスローされる可能性は低くなります。

ガベージ コレクションとメモリ リークに関する私の見解をいくつか紹介します。

アプリで自分で見ることができます。アクティビティが破棄された後も実行されていた AsyncTask をアクティビティが実行した場合、アクティビティは AsyncTask が終了するまでガベージ コレクションを取得しません。

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

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

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

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

于 2012-12-01T03:50:37.753 に答える
67

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

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

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

要件:

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

スクリーンショット

特徴:

  • シングルトンを使用して、方向の変更があった場合にキャッシュを保持します
  • 割り当てられたアプリケーション メモリの8 分の 1をキャッシュに使用します (必要に応じて変更します)。
  • 大きなビットマップはスケーリングされます(許可する最大ピクセルを定義できます)
  • ビットマップをダウンロードする前に利用可能なインターネット接続があることを制御します
  • 行ごとに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 例外は 1 つも発生していません。エミュレータ、Nexus One、および Nexus S でこれをテストしました。HD 品質の画像を含む画像 URL をテストしました。唯一のボトルネックは、ダウンロードに時間がかかることです。

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

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

于 2012-08-24T10:32:15.710 に答える
45

画像を取得してその場でサイズを変更するために、次のことを行いました。お役に立てれば

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    
于 2009-02-16T06:23:31.773 に答える
36

これは非常に長期にわたる問題であり、さまざまな説明があります。ここでは、提示された2つの最も一般的な回答のアドバイスを受けましたが、どちらも、プロセスのデコード部分を実行するためのバイトを購入する余裕がないと主張するVMの問題を解決しませんでした。少し掘り下げた後、ここでの本当の問題は、 NATIVEヒープ からデコードプロセスを取り除くことであることがわかりました。

ここを参照してください:BitmapFactoryOOMが私を狂わせています

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

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

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

于 2011-05-19T16:49:15.803 に答える
34

これを使用してくださいbitmap.recycle();これは、画質の問題なしに役立ちます。

于 2010-11-07T11:08:19.227 に答える
31

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

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);
于 2011-06-04T09:44:58.510 に答える
30

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

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

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

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

お役に立てれば。

于 2011-03-02T18:17:45.603 に答える
29

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

  • ビットマップメモリ​​はVMヒープではなく、ネイティブヒープにあります-BitmapFactoryOOMを参照してください
  • ネイティブヒープのガベージコレクションはVMヒープよりも遅延します。そのため、アクティビティのonPauseまたはonDestroyを実行するたびに、bitmap.recycleおよびbitmap=nullを実行することに非常に積極的に取り組む必要があります。
于 2011-05-12T04:40:43.797 に答える
29

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

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());
    }
}
于 2012-06-09T20:33:32.800 に答える
22

ここで素晴らしい答えが得られましたが、この問題に対処するために完全に使用可能なクラスが必要だったので、1つ作成しました。

これが 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;
    }

}
于 2013-02-03T15:03:02.763 に答える
21

上記の答えはどれもうまくいきませんでしたが、問題を解決する恐ろしく醜い回避策を思いつきました。非常に小さい 1x1 ピクセルの画像をリソースとしてプロジェクトに追加し、ガベージ コレクションを呼び出す前に ImageView に読み込みました。ImageView が Bitmap を解放していなかった可能性があるため、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.
于 2011-12-07T12:32:34.477 に答える
20

これは私にとってはうまくいきます。

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#モノドロイドにあります。画像のパスを簡単に変更できます。ここで重要なのは、設定するオプションです。

于 2012-10-02T10:33:28.687 に答える
17

私のアプリケーションの 1 つで、いずれかから写真を撮る必要がありますCamera/Gallery。ユーザーがカメラから画像 (2MP、5MP、または 8MP の場合があります) をクリックすると、画像サイズは s からkBs まで変化し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;

}

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

詳細については、これを参照してください

于 2013-02-06T14:53:59.943 に答える
17

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

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;
    }
}
于 2012-11-02T23:41:44.470 に答える
15

数分前にこの問題に遭遇しました。リストビューアダプターの管理を改善することで解決しました。使用していた何百もの 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);
    }
...
于 2012-02-24T19:08:16.383 に答える
15

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

デバイスをチェックインしてください。デバイスで実行できます。

于 2012-08-13T11:39:42.730 に答える
14

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

私は、Samsung Galaxy 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% は画像の回転を実行するときにメモリを使い果たすことなく適切なバッファーを提供します。

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

于 2013-08-31T03:04:47.633 に答える
14

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

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()の代わりに画像パスを使用します。

于 2014-01-24T12:57:55.427 に答える
14

一般に、Android デバイスのヒープ サイズはわずか 16MB です (デバイス/OS によって異なります。投稿のヒープ サイズを参照してください)。 SDカード、リソース、またはネットワークからの画像でもgetImageUriを使用しようとするか、ビットマップをロードするとより多くのメモリが必要になります。または、そのビットマップで作業が完了した場合は、ビットマップを null に設定できます。

于 2012-11-26T05:48:17.370 に答える
14

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

リストビューの途中まで行くと、クラッシュします。

アクティビティ B で次のことを実装すると、リストビュー全体を問題なく通過し、続けて進み続けることができます...そしてその速度は非常に高速です。

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

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
于 2012-03-27T19:33:09.843 に答える
13

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

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;
    }
}
于 2012-10-31T12:03:07.970 に答える
13

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

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

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

于 2013-07-14T09:08:14.007 に答える
7

使用したイメージのサイズが非常に大きいようです。そのため、一部の古いデバイスでは、ヒープ メモリがいっぱいになるとクラッシュが発生します。古いデバイス (ハニーコーム、ICS、またはローエンド モデルのデバイス)android:largeHeap="true"では、アプリケーション タグの下のマニフェスト ファイルで使用してみてください。または、以下のコードを使用してビットマップのサイズを縮小します。

Bitmap bMap;
BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InSampleSize = 8;
bMap= BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

ビットマップのサイズを小さくするために 4 または 12 または 16 を指定することもできます

于 2014-12-18T09:40:38.610 に答える
7
BitmapFactory.Options options = new Options();
options.inSampleSize = 32;
//img = BitmapFactory.decodeFile(imageids[position], options);

Bitmap theImage = BitmapFactory.decodeStream(imageStream,null, options);
Bitmap img=theImage.copy(Bitmap.Config.RGB_565,true);
theImage.recycle();
theImage = null;
System.gc();
//ivlogdp.setImageBitmap(img);
Runtime.getRuntime().gc();
于 2013-07-24T12:00:55.817 に答える
5

Thomas Vervestのアプローチを試しましたが、IMAGE_MAX_SIZEが2048の場合、画像サイズ2592x1944に対して1のスケールが返されます。

このバージョンは、他の人から提供された他のすべてのコメントに基づいて私のために機能しました:

private 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 ();
        }

        int scale = 1;
        for (int size = Math.max (o.outHeight, o.outWidth); 
            (size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);

        // 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;
}
于 2012-12-09T21:45:45.753 に答える
4

この概念を使用すると、これが役立ちます。その後、画像ビューにimagebitmapを設定します

public static Bitmap convertBitmap(String path)   {

        Bitmap bitmap=null;
        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) {
            e.printStackTrace();
        }

        try {
            if(fs!=null)
            {
                bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
            }
            } catch (IOException e) {

            e.printStackTrace();
        } finally{ 
            if(fs!=null) {
                try {
                    fs.close();
                } catch (IOException e) {

                    e.printStackTrace();
                }
            }
        }

        return bitmap;
    }

高さと幅が 60 や 60 の大きな画像から小さな画像を作成し、リストビューを高速にスクロールする場合は、この概念を使用します。

public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth,
            int reqHeight) {

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

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

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        Bitmap bmp = BitmapFactory.decodeFile(path, options);
        return bmp;
        }

    public static int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {

        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            if (width > height) {
                inSampleSize = Math.round((float) height / (float) reqHeight);
            } else {
                inSampleSize = Math.round((float) width / (float) reqWidth);
             }
         }
         return inSampleSize;
        }

大いに役立つことを願っています。

ここの開発者サイトから助けを得ることができます

于 2013-08-29T06:34:00.200 に答える
3

すべての回答に目を通した後、画像を処理するための Glide API について誰も言及していないことに驚きました。優れたライブラリであり、ビットマップ管理のすべての複雑さを抽象化します。このライブラリと 1 行のコードで、画像をすばやく読み込んでサイズ変更できます。

     Glide.with(this).load(yourImageResource).into(imageview);

ここでリポジトリを取得できます: https://github.com/bumptech/glide

于 2017-12-15T18:38:16.660 に答える
-10

ビットマップを imageview に設定した後、次のようにリサイクルします。

bitmap.recycle();
bitmap=null;
于 2013-03-29T06:56:44.627 に答える