Wednesday, May 8, 2013

[android help] Loading large, scaled bitmap causes OOM (OutOfMemoryError) (Android)


I have now tried a thousand times to solve this on my own without any success whatsoever. I've logged and debugged the application and from the information that I've gathered there is this one large spritesheet that is used for an animation. It's a .png with the size of 3000x1614 pixels. There are 10x6 sprites in the spritesheet that is essential for the animation. There are no gaps in between the sprites whatsoever to make it as memory-efficient as possible.


This is my method in which I load, decode and resize my bitmaps into the aspect ratio of the user's phone vs. my phone which I am building the game upon:



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), (int)(Height), true), format);
}



This is the LogCat output of the error:



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



This is the Memory leak report using the 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 »




Additional Information: I use either RGB565 or ARGB4444 as the config for my images. I wish to use ARGB8888 for the images with gradients, but it just takes up too much memory. Another thing to note is that I do recycle my bitmaps when I do not need them anymore.


Another thing that seems to consume a lot of memory is my Assets class, which consists of all the "Pixmaps" that the game uses. The bitmaps loaded from the assets are stored in these "Pixmaps" and removed when not needed anymore (the bitmaps that is). Is there maybe a better way to store these objects? Please let me know:



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


Another thing to note is that the OOM only happens on devices with higher resolutions than my own (800x480), which is because the bitmaps get scaled to make the app fit larger devices.


Also take note that the images are being drawn on a canvas, since what I am developing is a game and I'm not very experienced with OpenGL.



So, what should I do to solve this issue? How am I able to load lots of scaled bitmaps without getting the OOM while keeping their quality?



EDIT #1: JUST TO MAKE SURE YOU ARE AWARE OF WHAT MY ISSUE IS


I am getting OOM at the following line:



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


I'm desperate for help! This is my final blockade from releasing this bloody game!



EDIT #2: NEW METHODS I'VE TRIED THAT DIDN't WORK


I tried adding the decoded bitmaps to a LruCache before using them. I also tried a method where it creates a map of the bitmap in a temp file and then loads it in again. Like this:



//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);


.

stackoverflow.comm

No comments:

Post a Comment

Google Voice on T-Mobile? [General]

Google Voice on T-Mobile? So I recently switched from a GNex on Verizon to a Moto X DE on T-Mobile. I had always used Google Voice for my v...