缩放和加载位图会导致OOM(OutOfMemoryError)(Android)

我现在已经尝试了一千次来自己解决这个问题而没有任何成功.我已经记录并调试了应用程序,根据我收集的信息,这是一个用于动画的大型spritesheet.这是一个大小为3000×1614像素的.png. spritesheet中有10×6个精灵,对于动画来说是必不可少的.精灵之间没有任何差距,使其尽可能保持内存效率.

这是我的方法,我将我的位图加载,解码并调整大小为用户手机与手机的宽高比,我正在构建游戏:

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的内存泄漏报告:

Problem Suspect 1

One instance of “com.NAME.framework.impl.AndroidPixmap”
loaded by “dalvik.system.PathClassLoader @ 0x41a06f90” occupies 12 393
680 (54,30%) bytes. The memory is accumulated in one instance of
“byte[]” loaded by “”.

Keywords dalvik.system.PathClassLoader @ 0x41a06f90
com.NAME.framework.impl.AndroidPixmap byte[]

Details »

Problem Suspect 2

The class “android.content.res.Resources”, loaded by “”, occupies 4 133 808 (18,11%) bytes. The memory is accumulated
in one instance of “java.lang.Object[]” loaded by “”.

Keywords java.lang.Object[] android.content.res.Resources

Details »

Problem Suspect 3

8 instances of “android.graphics.Bitmap”, loaded by “” occupy 2 696 680 (11,82%) bytes.

Biggest instances: •android.graphics.Bitmap @ 0x41462ba0 – 1 048 656
(4,59%) bytes. •android.graphics.Bitmap @ 0x41a08cf0 – 768 064
(3,37%) bytes. •android.graphics.Bitmap @ 0x41432990 – 635 872
(2,79%) bytes.

Keywords android.graphics.Bitmap

Details »

附加信息:
我使用RGB565或ARGB4444作为我的图像的配置.我希望ARGL8888用于带渐变的图像,但它只占用太多内存.另外需要注意的是,当我不再需要它时,我会回收我的位图.

另一件似乎消耗大量内存的事情是我的Assets类,它包含游戏使用的所有“Pixmaps”.从资产加载的位图存储在这些“Pixmaps”中,并在不再需要时删除(位图).是否有更好的方法来存储这些对象?请告诉我:

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

另外需要注意的是,OOM​​只发生在分辨率高于我自己(800×480)的设备上,这是因为位图得到了缩放以使应用适合更大的设备.

另请注意,图像是在画布上绘制的,因为我正在开发的是一个游戏,而且我对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;
    }
};

加载BITMAP

        //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的情况下加载大量缩放的位图?

最佳答案 这是一种解决方法,但这比直接缩放位图需要更长的时间.

>首先,将原始位图解码为原始大小
>拆分成小位图,在这种情况下是10位图,宽度=
originalWidth / 10和height = originalHeight.并宽度每个位图
其中,缩放到目标/ 10的大小然后保存到文件中. (我们的确是
这一个又一个,每个步骤后释放内存.)
>释放原始位图
>使用目标大小创建新位图.
>解码之前保存在文件中的每个位图并绘制到新位图上.商店
用于将来使用的新缩放位图.

方法是这样的:

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

点赞