5

私はこれを自分で解決しようと何千回も試みましたが、まったく成功しませんでした。アプリケーションをログに記録してデバッグしました。収集した情報から、アニメーションに使用される大きなスプライトシートが 1 つあります。サイズが 3000x1614 ピクセルの .png です。スプライトシートには、アニメーションに不可欠な 10x6 のスプライトがあります。可能な限りメモリ効率を高めるために、スプライト間にギャップはまったくありません。

これは、ビットマップを読み込んでデコードし、ユーザーの電話とゲームを構築している私の電話の縦横比にサイズ変更する方法です。

public Pixmap newResizedPixmap(String fileName, PixmapFormat format, double Width, double Height) {
    // TODO Auto-generated method stub
    Config config = null;
    if (format == PixmapFormat.RGB565)
        config = Config.RGB_565;
    else if (format == PixmapFormat.ARGB4444)
        config = Config.ARGB_4444;
    else
        config = Config.ARGB_8888;

    Options options = new Options();
    options.inPreferredConfig = config;
    options.inPurgeable=true;
    options.inInputShareable=true;
    options.inDither=false;

    InputStream in = null;
    Bitmap bitmap = null;
    try {
        //OPEN FILE INTO INPUTSTREAM
        in = assets.open(fileName);
        //DECODE INPUTSTREAM
        bitmap = BitmapFactory.decodeStream(in, null, options);

        if (bitmap == null)
            throw new RuntimeException("Couldn't load bitmap from asset '"+fileName+"'");
    } catch (IOException e) {
        throw new RuntimeException("Couldn't load bitmap from asset '"+fileName+"'");
    } finally {
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
            }
        }
    }

    if (bitmap.getConfig() == Config.RGB_565)
        format = PixmapFormat.RGB565;
    else if (bitmap.getConfig() == Config.ARGB_4444)
        format = PixmapFormat.ARGB4444;
    else
        format = PixmapFormat.ARGB8888;

    //THIS IS WHERE THE OOM HAPPENS
    return new AndroidPixmap(Bitmap.createScaledBitmap(bitmap, (int)(Width+0.5f), (int)(Height+0.5f), true), format);
}

これは、エラーの LogCat 出力です。

05-07 12:57:18.570: E/dalvikvm-heap(636): Out of memory on a 29736016-byte allocation.
05-07 12:57:18.570: I/dalvikvm(636): "Thread-89" prio=5 tid=18 RUNNABLE
05-07 12:57:18.580: I/dalvikvm(636):   | group="main" sCount=0 dsCount=0 obj=0x414a3c78 self=0x2a1b1818
05-07 12:57:18.580: I/dalvikvm(636):   | sysTid=669 nice=0 sched=0/0 cgrp=apps handle=705796960
05-07 12:57:18.590: I/dalvikvm(636):   | schedstat=( 14462294890 67436634083 2735 ) utm=1335 stm=111 core=0
05-07 12:57:18.590: I/dalvikvm(636):   at android.graphics.Bitmap.nativeCreate(Native Method)
05-07 12:57:18.590: I/dalvikvm(636):   at android.graphics.Bitmap.createBitmap(Bitmap.java:640)
05-07 12:57:18.590: I/dalvikvm(636):   at android.graphics.Bitmap.createBitmap(Bitmap.java:586)
05-07 12:57:18.590: I/dalvikvm(636):   at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:466)
05-07 12:57:18.590: I/dalvikvm(636):   at com.NAME.framework.impl.AndroidGraphics.newResizedPixmap(AndroidGraphics.java:136)
05-07 12:57:18.590: I/dalvikvm(636):   at com.NAME.GAME.GameScreen.<init>(GameScreen.java:121)
05-07 12:57:18.600: I/dalvikvm(636):   at com.NAME.GAME.CategoryScreen.update(CategoryScreen.java:169)
05-07 12:57:18.600: I/dalvikvm(636):   at com.NAME.framework.impl.AndroidFastRenderView.run(AndroidFastRenderView.java:34)
05-07 12:57:18.600: I/dalvikvm(636):   at java.lang.Thread.run(Thread.java:856)
05-07 12:57:18.620: I/Choreographer(636): Skipped 100 frames!  The application may be doing too much work on its main thread.
05-07 12:57:18.670: W/dalvikvm(636): threadid=18: thread exiting with uncaught exception (group=0x40a13300)
05-07 12:57:18.720: E/AndroidRuntime(636): FATAL EXCEPTION: Thread-89
05-07 12:57:18.720: E/AndroidRuntime(636): java.lang.OutOfMemoryError
05-07 12:57:18.720: E/AndroidRuntime(636):  at android.graphics.Bitmap.nativeCreate(Native Method)
05-07 12:57:18.720: E/AndroidRuntime(636):  at android.graphics.Bitmap.createBitmap(Bitmap.java:640)
05-07 12:57:18.720: E/AndroidRuntime(636):  at android.graphics.Bitmap.createBitmap(Bitmap.java:586)
05-07 12:57:18.720: E/AndroidRuntime(636):  at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:466)
05-07 12:57:18.720: E/AndroidRuntime(636):  at com.NAME.framework.impl.AndroidGraphics.newResizedPixmap(AndroidGraphics.java:136)
05-07 12:57:18.720: E/AndroidRuntime(636):  at com.NAME.GAME.GameScreen.<init>(GameScreen.java:121)
05-07 12:57:18.720: E/AndroidRuntime(636):  at com.NAME.GAME.CategoryScreen.update(CategoryScreen.java:169)
05-07 12:57:18.720: E/AndroidRuntime(636):  at com.NAME.framework.impl.AndroidFastRenderView.run(AndroidFastRenderView.java:34)
05-07 12:57:18.720: E/AndroidRuntime(636):  at java.lang.Thread.run(Thread.java:856)
05-07 12:57:18.847: D/Activity:(636): Pausing...

これは、DDMS を使用したメモリ リーク レポートです。

問題の容疑者 1

「dalvik.system.PathClassLoader @ 0x41a06f90」によってロードされた「com.NAME.framework.impl.AndroidPixmap」の 1 つのインスタンスは、12 393 680 (54,30%) バイトを占有します。メモリは、"" によってロードされた "byte[]" の 1 つのインスタンスに蓄積されます。

キーワード dalvik.system.PathClassLoader @ 0x41a06f90 com.NAME.framework.impl.AndroidPixmap バイト[]

詳細 "

問題の容疑者 2

「」によってロードされるクラス「android.content.res.Resources」は、4 133 808 (18,11%) バイトを占有します。メモリは、"" によってロードされた "java.lang.Object[]" の 1 つのインスタンスに蓄積されます。

キーワード java.lang.Object[] android.content.res.Resources

詳細 "

問題の容疑者 3

「」によってロードされた「android.graphics.Bitmap」の 8 つのインスタンスは、2 696 680 (11,82%) バイトを占有します。

最大のインスタンス: •android.graphics.Bitmap @ 0x41462ba0 - 1 048 656 (4.59%) バイト。•android.graphics.Bitmap @ 0x41a08cf0 - 768 064 (3,37%) バイト。•android.graphics.Bitmap @ 0x41432990 - 635 872 (2,79%) バイト。

キーワード android.graphics.Bitmap

詳細 "


追加情報: イメージの構成として RGB565 または ARGB4444 を使用しています。グラデーションのある画像に ARGB8888 を使用したいのですが、メモリを消費しすぎます。注意すべきもう 1 つの点は、ビットマップが不要になったときにビットマップをリサイクルすることです。

多くのメモリを消費しているように見えるもう 1 つのことは、ゲームが使用するすべての "Pixmaps" で構成される Assets クラスです。アセットからロードされたビットマップはこれらの「ピックスマップ」に保存され、不要になったときに削除されます (ビットマップ)。これらのオブジェクトを保存するためのより良い方法はありますか? 私にお知らせください:

public static Pixmap image1;
public static Pixmap image2;
public static Pixmap image3;
public static Pixmap image4;
public static Pixmap image5;
public static Pixmap image6;
public static Pixmap image7;
public static Pixmap image8;
public static Pixmap image9;
public static Pixmap image10;
public static Pixmap image11;
...

もう 1 つ注意すべき点は、OOM は私のデバイス (800x480) よりも解像度が高いデバイスでのみ発生することです。これは、アプリがより大きなデバイスに適合するようにビットマップがスケーリングされるためです。

また、私が開発しているのはゲームであり、OpenGL の経験があまりないため、画像がキャンバスに描かれていることに注意してください。


編集#1:私の問題が何であるかを知っていることを確認するためだけに

次の行で OOM を取得しています。

Bitmap.createScaledBitmap(bitmap, (int)(Width), (int)(Height), true)

私は助けを求めています!これは、この血まみれのゲームのリリースに対する私の最後の封鎖です!


編集#2:うまくいかなかった私が試した新しい方法

デコードされたビットマップを使用する前に LruCache に追加してみました。また、ビットマップのマップを一時ファイルに作成してから再度ロードする方法も試しました。このような:

アクティビティ

// Get memory class of this device, exceeding this amount will throw an
// OutOfMemory exception.
final int memClass = ((ActivityManager) getSystemService(
        Context.ACTIVITY_SERVICE)).getMemoryClass();

// Use 1/8th of the available memory for this memory cache.
final int cacheSize = 1024 * 1024 * memClass / 8;

mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        // The cache size will be measured in kilobytes rather than
        // number of items.
        return (bitmap.getRowBytes() * bitmap.getHeight()) / 1024;
    }
};

ビットマップのロード

        //Copy the byte to the file
//Assume source bitmap loaded using options.inPreferredConfig = Config.ARGB_8888;
FileChannel channel = randomAccessFile.getChannel();
MappedByteBuffer map = null;
try {
    map = channel.map(MapMode.READ_WRITE, 0, width*height*4);
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
bitmap.copyPixelsToBuffer(map);
//recycle the source bitmap, this will be no longer used.
bitmap.recycle();
//Create a new bitmap to load the bitmap again.
bitmap = Bitmap.createBitmap(width, height, config);
map.position(0);
//load it back from temporary 
bitmap.copyPixelsFromBuffer(map);
//close the temporary file and channel , then delete that also
try {
    channel.close();
    randomAccessFile.close();
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

((AndroidGame) activity).addBitmapToMemoryCache(""+fileName, Bitmap.createScaledBitmap(bitmap, (int)(Width), (int)(Height), true));

return new AndroidPixmap(((AndroidGame) activity).getBitmapFromMemCache(""+fileName), format);

編集#3:あなたが知らなかった場合に備えて...

ビットマップを縮小するのではなく拡大しているので、この状況では inSampleSize は役に立ちません。私はすでに inSampleSize を試したので、すべてがぼやけてしまい、何が何であるかさえわかりません。それが、inSampleSize を 2 に設定したときです。


では、この問題を解決するにはどうすればよいでしょうか。品質を維持しながら、OOM を取得せずにスケーリングされたビットマップを大量にロードするにはどうすればよいですか?

4

6 に答える 6

5

これは回避策ですが、ビットマップを直接スケーリングするよりも少し時間がかかります。

  1. まず、元のビットマップを元のサイズとしてデコードします
  2. 小さなビットマップに分割します。この場合、幅 = originalWidth/10 および高さ = originalHeight の 10 個のビットマップです。そして、それらの各ビットマップの幅を、target/10 のサイズにスケーリングしてから、ファイルに保存します。(これを 1 つずつ実行し、各ステップの後にメモリを解放します。)
  3. 元のビットマップを解放する
  4. ターゲット サイズで新しいビットマップを作成します。
  5. 以前にファイルに保存された各ビットマップをデコードし、新しいビットマップに描画します。将来の使用のために、新しいスケーリングされたビットマップを保存します。

メソッドは次のようなものです。

public Bitmap newResizedPixmapWorkAround(Bitmap.Config format,
            double width, double height) {
        int[] pids = { android.os.Process.myPid() };
        MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (beginning) = " + myMemInfo.dalvikPss);

        Config config = null;
        if (format == Bitmap.Config.RGB_565) {
            config = Config.RGB_565;
        } else if (format == Bitmap.Config.ARGB_4444) {
            config = Config.ARGB_4444;
        } else {
            config = Config.ARGB_8888;
        }

        Options options = new Options();
        options.inPreferredConfig = config;
        options.inPurgeable = true;
        options.inInputShareable = true;
        options.inDither = false;

        InputStream in = null;
        Bitmap bitmap = null;
        try {
            // OPEN FILE INTO INPUTSTREAM
            String path = Environment.getExternalStorageDirectory()
                    .getAbsolutePath();
            path += "/sprites.png";
            File file = new File(path);
            in = new FileInputStream(file);
            // DECODE INPUTSTREAM
            bitmap = BitmapFactory.decodeStream(in, null, options);

            if (bitmap == null) {
                Log.e(TAG, "bitmap == null");
            }
        } catch (IOException e) {
            Log.e(TAG, "IOException " + e.getMessage());
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                }
            }
        }

        if (bitmap.getConfig() == Config.RGB_565) {
            format = Bitmap.Config.RGB_565;
        } else if (bitmap.getConfig() == Config.ARGB_4444) {
            format = Bitmap.Config.ARGB_4444;
        } else {
            format = Bitmap.Config.ARGB_8888;
        }

        myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (before separating) = " + myMemInfo.dalvikPss);

        // Create 10 small bitmaps and store into files.
        // After that load each of them and append to a large bitmap

        int desSpriteWidth = (int) (width + 0.5) / 10;
        int desSpriteAppend = (int) (width + 0.5) % 10;
        int desSpriteHeight = (int) (height + 0.5);

        int srcSpriteWidth = bitmap.getWidth() / 10;
        int srcSpriteHeight = bitmap.getHeight();

        Rect srcRect = new Rect(0, 0, srcSpriteWidth, srcSpriteHeight);
        Rect desRect = new Rect(0, 0, desSpriteWidth, desSpriteHeight);

        String pathPrefix = Environment.getExternalStorageDirectory()
                .getAbsolutePath() + "/sprites";

        for (int i = 0; i < 10; i++) {
            Bitmap sprite;
            if (i < 9) {
                sprite = Bitmap.createBitmap(desSpriteWidth, desSpriteHeight,
                        format);
                srcRect.left = i * srcSpriteWidth;
                srcRect.right = (i + 1) * srcSpriteWidth;
            } else { // last part
                sprite = Bitmap.createBitmap(desSpriteWidth + desSpriteAppend,
                        desSpriteHeight, format);
                desRect.right = desSpriteWidth + desSpriteAppend;
                srcRect.left = i * srcSpriteWidth;
                srcRect.right = bitmap.getWidth();
            }
            Canvas canvas = new Canvas(sprite);
            // NOTE: if using this drawBitmap method of canvas do not result
            // good quality scaled image, you can try create tile image with the
            // same size original then use Bitmap.createScaledBitmap()
            canvas.drawBitmap(bitmap, srcRect, desRect, null);

            String path = pathPrefix + i + ".png";
            File file = new File(path);
            try {
                if (file.exists()) {
                    file.delete();
                }
                file.createNewFile();
                FileOutputStream fos = new FileOutputStream(file);
                sprite.compress(Bitmap.CompressFormat.PNG, 100, fos);
                fos.flush();
                fos.close();
                sprite.recycle();
            } catch (FileNotFoundException e) {
                Log.e(TAG,
                        "FileNotFoundException  at tile " + i + " "
                                + e.getMessage());
            } catch (IOException e) {
                Log.e(TAG, "IOException  at tile " + i + " " + e.getMessage());
            }
        }

        bitmap.recycle();

        bitmap = Bitmap.createBitmap((int) (width + 0.5f),
                (int) (height + 0.5f), format);
        Canvas canvas = new Canvas(bitmap);

        desRect = new Rect(0, 0, 0, bitmap.getHeight());

        for (int i = 0; i < 10; i++) {
            String path = pathPrefix + i + ".png";
            File file = new File(path);
            try {
                FileInputStream fis = new FileInputStream(file);
                // DECODE INPUTSTREAM
                Bitmap sprite = BitmapFactory.decodeStream(fis, null, options);
                desRect.left = desRect.right;
                desRect.right = desRect.left + sprite.getWidth();
                canvas.drawBitmap(sprite,  null, desRect, null);
                sprite.recycle();
            } catch (IOException e) {
                Log.e(TAG, "IOException  at tile " + i + " " + e.getMessage());
            }
        }

        myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (before scaling) = " + myMemInfo.dalvikPss);

        // THIS IS WHERE THE OOM HAPPENS
        return bitmap;
    }

私は自分の電話でテストしましたが、1.5 にスケーリングすると OOM なしで実行されます (元の方法では OOM が実行されます)。

于 2013-05-13T11:03:20.433 に答える
0

この機能を試してみてください。

void scaleBitmap()
{


        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        o.inPurgeable = true;
        o.inScaled = true;
        BitmapFactory.decodeStream(new FileInputStream(f), null, o);
        int width_temp = o.outWidth, height_temp = o.outHeight;
        int scale = 1;
        while (true) {
                if (width_temp / 2 < 80 || height_temp / 2 < 80)
                    break;
                width_temp /= 2;
                height_temp /= 2;
                scale *= 2;
            }

            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            return BitmapFactory.decodeStream(new FileInputStream(f), null,
                    o2);
}
于 2013-05-12T10:50:39.313 に答える