5

我现在已经尝试了一千次自己解决这个问题,但没有任何成功。我已经记录并调试了应用程序,从我收集到的信息中,有一个用于动画的大型 spritesheet。这是一个 .png,大小为 3000x1614 像素。spritesheet 中有 10x6 的 sprite,这对于动画来说是必不可少的。精灵之间没有任何间隙,以使其尽可能节省内存。

这是我将位图加载、解码和调整大小为用户手机与我正在构建游戏的手机的纵横比的方法:

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”实例占用 12 393 680 (54,30%) 个字节。内存在“”加载的“byte[]”的一个实例中累积。

关键词 dalvik.system.PathClassLoader @ 0x41a06f90 com.NAME.framework.impl.AndroidPixmap byte[]

细节 ”

问题嫌疑人2

由“”加载的类“android.content.res.Resources”占用4 133 808 (18,11%)字节。内存在“”加载的“java.lang.Object[]”的一个实例中累积。

关键字 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 用于具有渐变的图像,但它占用了太多内存。另一件需要注意的事情是,当我不再需要它们时,我会回收我的位图。

另一个似乎消耗大量内存的东西是我的 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;
...

另一件需要注意的是,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. 分割成小的位图,在这种情况下是 10 个位图,宽度 = originalWidth/10,高度 = originalHeight。并调整每个位图的宽度,缩放到 target/10 的大小,然后保存到文件中。(我们一个一个地做这个,每一步之后释放内存。)
  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 回答
1

为什么要放大位图?只需在绘制它们时缩放它们。

3000x1614 相当大,如果你可以放弃对 <= 2.2 的支持;http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html可以加载位图的特定部分(精灵)。

您正在将新位图传递给 AndroidPixmap 的构造函数,看起来您正在保留对它们的静态引用 - 您究竟是如何回收它们的?

作为一种逃避,您可以<application ... android:largeHeap="true" ...>在清单中要求使用更多内存;http://developer.android.com/guide/topics/manifest/application-element.html#largeHeap我相信这需要> =3.0

于 2013-05-13T08:22:37.507 回答
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 回答
0

只是为了帮助(但没​​有真正的解决方案),你读过这个吗:

http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

和这个:

http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

也许可以帮助你!如果您测试交付的示例项目并使用您的大图像更改 URL 路径!

而且,当我开始工作时,我读过这个:

http://twigstechtips.blogspot.com/2011/10/android-resize-bitmap-image-while.html

于 2013-05-07T13:41:50.120 回答
-1

将您的图像转换为位图对象。并使用 Bitmap 类的 API 来缩放您的图像,以避免出现内存不足错误。

       Bitpmap bm = BitmapFactory.decode ("image file path");
       bm.setDensity(integer);
       Bitmap bm1 = Bitmap.createScaledBitmap (bm, width, height, false);
       imageView.setImageBitmap(bm1);

尝试将宽度和高度的值设置为不应该泄漏内存。愿这能帮助你。

于 2013-05-07T13:33:12.633 回答
-3

允许 JVM 使用 -Xmx VM 参数使用更多内存。

例如,要允许JVM使用 1 GB (1024 MB) 内存:

java -Xmx1024m

或者

使用任何图像上传器并在演示中传递图像 url

1) Nostra 的通用图像加载器

2) Fedor 的LazyList。和;

3) Novoda 的ImageLoader

于 2013-05-07T13:30:48.360 回答