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

时间:2013-05-07 13:23:59

标签: android memory-management bitmap out-of-memory

我现在已经尝试过一千次来自己解决这个问题而没有任何成功。我已经记录并调试了应用程序,根据我收集的信息,这是一个用于动画的大型spritesheet。这是一个大小为3000x1614像素的.png。 spritesheet中有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

     

“com.NAME.framework.impl.AndroidPixmap”的一个实例   由“dalvik.system.PathClassLoader @ 0x41a06f90”加载占用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作为我的图像的配置。我希望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只发生在分辨率高于我自己(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;
    }
};

加载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?

6 个答案:

答案 0 :(得分:5)

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

  1. 首先,将原始位图解码为原始大小
  2. 拆分为小位图,在这种情况下是10位图,宽度为= originalWidth / 10和height = originalHeight。并宽度每个位图 其中,缩放到目标/ 10的大小然后保存到文件中。 (我们的确是 这一个又一个,并在每一步之后释放记忆。)
  3. 发布原始位图
  4. 使用目标大小创建新的位图。
  5. 解码之前保存在文件中的每个位图并绘制到新位图上。商店 新的缩放位图以供将来使用。
  6. 方法是这样的:

    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运行

答案 1 :(得分:1)

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

3000x1614非常大,如果你可以放弃&lt; = 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我认为这需要&gt; = 3.0

答案 2 :(得分: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

答案 3 :(得分: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);
}

答案 4 :(得分:-1)

将图像转换为Bitmap对象。并使用Bitmap类的API来缩放图像,以避免Out of Memory错误。

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

尝试放置宽度和高度的值,以便它不会泄漏内存。愿这可以帮到你。

答案 5 :(得分:-3)

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

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

java -Xmx1024m

OR

使用任何图片上传器并在演示

中传递图片网址

1)Nostra的Universal Image Loader

2)Fedor的LazyList。和;

3)Novoda的ImageLoader