Problem with loading bitmaps into Android Application

Started by
16 comments, last by alexgibson 12 years, 2 months ago
This is a free for all forum, not your personal help line.


I never doubted that / thought that, respectively. cool.png
Advertisement
Now is midnight and I am still looking into this. Just found this interesting article from Google I/O 2011:

ERRATUM: I’d like to correct one mistake I made in the Q&A, answering a question at 49:33 in the video. I said that “garbage collection is fundamentally kind of asynchronous; if you’re really close to the limit there can be times that you’re trying to allocate so fast that the garbage collector can’t keep up.” That isn’t really true. Dalvik will always perform a full, synchronous GC before thowing an OutOfMemoryError. What I was referring to here was finalization. [color=#ff0000]Finalization is asynchronous, so if you are close to your heap limit and allocating a lot of objects, you may run out of memory before finalizable objects can be freed.

Could this be related to my problem ?
It certainly could be related to your problem as you are doing quite a few allocations very quickly and are running dangerously close to the limit :) (I don't think we come anywhere near that in terms of memory usage).

Certainly the Garbage collector is doing the correct thing before your OOM and having one last shot at freeing stuff up. If you are leaking memory (holding onto it for longer than you intended) then this might be an issue (particularly as you are so close to the 48MB limit). You will see times where things work fine and then others where the Garbage Collector is unable to collect something you thought should be freed up and just can't collect enough to carry on (We saw something similar flicking back and forwards on our menu screens as they had large backgrounds and we leaked some memory each time). Does anything happen in your game before this loading of the images? (A menu system, other activities as leaking the context is quite common).

Was there anything particularly different in the scenes for the log where you have the successful result and the crash? or were they exactly the same test? (Was it running on the phone or the emulator?). It looks like a pre load of all the resources before starting a level (which is definately the right thing to do).

Given you are so close to the 48MB limit you might want to look into how your resources (pngs, oggs so on and so forth) are setup (I can't comment on this as I don't know much about your project but I know I had to drag our artists back into the real world of having to be as conservative as they could).

I will post an answer on your other post just now which might clear things up a bit in terms of how to structure your code (not that I am an expert by any stretch of the imagination).

Was there anything particularly different in the scenes for the log where you have the successful result and the crash? or were they exactly the same test?



For clarity, I am posting 2 logs in following two posts.

1st log is where the game starts fine, about 50 or so textures ranging in size from 80x80 to 1280x752 load just fine and everything works great. This works whether I put to allocation ridiculously small numbers, or their thousedfold values (width * height * 4 does not work, that crashes immediately, but I put there anything from 12 * 4 to width * 4 and it did not seem to make an iota of difference - all textures loaded and game ran). As you can see though there is something strange going on there - it seems like Android is running out of puff there, having 1-2-3% of memory free, then suddenly reclaims up to 71%!

2nd log is the same situation, I did not change memory allocation, only added one more object loading one more 1280x752 texture. That is where it crashes.


As you can see, there is no way how 50 bitmaps take 29% of the memory and then only 1 additional bitmap takes remaining 71% - there must be something else going on, most likely too many allocations too quickly, as we both concluded above. This is why I would prefer to load only bitmaps for one level at a time which would completely solve all my problems. But how to do it, if I need for that the damned Context and I cannot get it within onDrawFrame function ??? wacko.png

Was it running on the phone or the emulator?


Both apps related to the above logs were running on the emulator. Then I tried to run it on the actual device, and it loads some 10 bitmaps more before it goes OOM. This probably relates to speed of GC which would be much quicker on the device than on the emulator. This still does not solve my problem though, as that is still only about 1/3 of all the bitmaps which I need to load for the whole game.

It looks like a pre load of all the resources before starting a level (which is definately the right thing to do).


In fact, I am trying to load all the bitmaps which I need for the whole game. I don't think this is the right thing to do, I would much more prefer to load only the bitmaps which I need for the first level, and load bitmaps for another level as and when I start that particular level, but I don't know how to do it either - to load the bitmaps later, I need the Context and I don't know how to pass it into onDrawFrame function, where is my main game loop (as explained in my other post http://www.gamedev.net/topic/619474-how-to-pass-context-into-ondrawframe/page__fromsearch__1

In fact, I am trying to load all the bitmaps which I need for the whole game. I don't think this is the right thing to do, I would much more prefer to load only the bitmaps which I need for the first level, and load bitmaps for another level as and when I start that particular level, but I don't know how to do it either - to load the bitmaps later, I need the Context and I don't know how to pass it into onDrawFrame function, where is my main game loop (as explained in my other post http://www.gamedev.n...__fromsearch__1)


Ahhh smile.png yes. Don't do that, I assumed you would be loading only the stuff you needed. You want to load your resources as you need them (As you have correctly pointed out). So if your level needs 10 textures load 10 textures. I have posted some information on your other thread.

You don't want to pass the context to onDrawFrame the interface won't let you. What you can do is pass the context into the Renderer so that it is available for use elsewhere.

This might get a bit long and so I apologise if i am being too verbose and at too simple a level. Also I am not making any claims that this is the only way to do things.

You have your Activity (Generally you will have an activity for the game, and 1 or more activities for menu screens and so forth). Moving between your activities is a place where you can decide to do some loading of the things you need for the next activity and free up any memory (If this is going to be a time consuming process you display a lightweight loading screen while the loading takes place).

In your activity for your game I am assuming you will have your GlSurfaceView and your GlRenderer of some kind. The surface view as far as I recall has a setRenderer method that allows you to inject the renderer you want to use. The GlRenderer exposes the onDrawFrame(GL10 gl) method which will get called constantly by the surfaceView. It sounds like your onDrawFrame(GL10 gl) looks a little like the below.

public void onDrawFrame(GL10 gl) {
endTime = System.currentTimeMillis();

long dt = endTime - startTime;
int fps = Math.round(1000f / dt);

if (fps < TargetFps) {
try {
Thread.sleep(TargetFps - fps);
} catch (InterruptedException e) {}
}

startTime = System.currentTimeMillis();

// update the game state
// render the game using the gl context
}


So your question is how do I get my Bitmaps into memory such that my Level has access to them?

I would very much recommend using a class purely for loading the bitmaps (This can be a Singleton but you have to be careful about how you work with a Singleton in Android as your application can be killed at anytime and thus your Singleton has to rebuild its state when the app resumes. For managers of things like bitmaps and sounds this generally is not a big deal and easily handled.).

You could possibly do something like the below if you use a singleton style object for handling your bitmap management.

// This is just off the cuff code, i don't expect it to compile it's just to give you an idea.
public class GameActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyGameLevel level = createTheLevel();

GlSurfaceView view = // Get a hold of your view or create it
Renderer r = new LevelRenderer(level);
view.setRenderer(r);

// If this causes noticeable issues on starting the activity don't do it
Runtime.getRuntime().gc();
}

private MyGameLevel createTheLevel() {
// Create a level it can load the textures
MyGameLevel level = new MyGameLevel();

// Load all the textures i need (this could be done by the level class this is just an example for arguments sake)
MyTextureManager.instance().loadBitmap(context, R.drawable.whatever);
MySoundManager.instance().loadSound(context, R.raw.sound1);

// etc...but all your resources are loaded
}
}

public class LevelRenderer extends Renderer {
MyGameLevel level;

public LevelRenderer(MyGameLevel l) {
level = l;
}

@Override
public void onDrawFrame(GL10 gl) {
// Game loop
level.update(dt);
level.render(gl);
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// You can do this or you can wait until you call level.render before binding the textures, your implementation will suit your needs
level.bindGlTextures(gl, config);
}
}

public class MyGameLevel() {
public void bindGlTextures(Gl10 gl, EGLConfig config) {
// here you can use the MyTextureManager to get the bitmaps and bind them to your gl context as you call this as soon as the surface is
// created...if you wan't to, delay this until the render call it is fine the important thing is the Textures exist already in the manager in bitmap form.
// something like ....can't quite remember the syntax
gl.bindTexture(GL_TEXTURE_2D, MyTextureManager.instance().getBitmap(R.drawable.whatever);
}

public void render(GL10 gl) { // draw the level }

public void update(long dt) { // update the level }
}


There are many ways to achieve the same thing so what I have posted above is one method for loading the Bitmaps and providing access to them. In the onfinish method of the activity you can clear the texture manager and recycle any bitmaps you no longer need. Does this help at all?

I realised I didn't post any code for the MyTextureManager class I made up in the example above...however I think you can get the idea. You are just using that as a cache to store your Bitmaps. The method to load a bitmap takes the context which is readily available in the Activity (where I used it in my example..but it can be wherever you pass the context...you could for example call getApplicationContext() in your Activity and pass that context into the Level and have the Level manage putting the Bitmaps it needs into the MyTextureManager) and a resource ID. Thus you would just call BitmapFactory.decodeResource(context.getResources(), resourceId). Obviously how you architect things will be suited to your needs I am just proposing ideas to get you thinking.
Many thanks Fiallen smile.png

This looks very promising.

I will do some tests based upon your suggestions through this weekend and then let you know how did it go.
The method to load a bitmap takes the context which is readily available in the Activity.


Of course !!! smile.png Sometime you are looking for a solution and cannot see that which is right under your nose.

It is because I program in Android / Java environment only a couple of months and still cannot get used to some of its quirks.

I am now loading the level-dependent bitmaps via functions in Activity, but I am calling these from my main game loop which is in onDrawFrame.

I did not use your approach of writing the dedicated class for handling the bitmaps (though it may be neater solution) as my game is now 99% finished and this would mean substantial rewriting of everything and further delays with finishing the game.

Application still struggles at the startup, going as low as 1% of the available memory (as in above log), but then recovers and increases to some 74% - that with 95% of all bitmaps required for the game already in. I am a bit concerned about this startup struggling, but I have run it yesterday about 50 times on the actual device and it did not go OOM a single time, so I guess this is some inherent feature of Android.

Thank you for all your help and patience Fiallen ! smile.png
No worries at all, glad you got it working.

So longs the heap increases in size you will be fine (it may report 1% memory free and then up the heap size). Your issue was coming from the memory usage being at 48Megs and the virtual machine being unable to up the heap size any further. If you have it loading the bitmaps you need on a per level basis I doubt you will get anywhere near the 48Meg limit :).

I'd give it a try on various devices though just to be absolutely certain, but it does sound like you have solved your problem, well done.

This topic is closed to new replies.

Advertisement