7

So I made a game app using bitmaps and surfaceview for a school project. But the app itself is taking so much ram!! only when you start it it can get up to 60mb of ram, and the more you play it the higher it gets (at one point it got to 90mb of ram and the game was lagging horribly).

After watching Google I/O 2011 (https://www.youtube.com/watch?v=_CruQY55HOk) I relaized it might be a memory leak, because the app starts like that: enter image description here
and after playing 2 minutes it end up like that:
enter image description here

The app itself was made to look as simple as possible, with 8bit graphics and not many colors: enter image description here

all of the images I used weight ONLY 400kb So why in the hell it takes so much ram?! I thought it might be the sounds, but all the sounds together weight only 4.45mb of ram, that's like 1/10 of the amount the app takes. I understand bitmaps take a lot of ram, but this is ridiculous!

This is my onLoad:

public GameView(Context c) {
        // TODO Auto-generated constructor stub
        super(c);
        this.c = c;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        ScoreParticleP = new PointF();
        NewScoreParticleP = new PointF();
        int srcWidth = options.outWidth;
        int srcHeight = options.outHeight;
        // it=blocks.iterator();
        // Decode with inSampleSize
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        this.setKeepScreenOn(true);
        WindowManager wm = (WindowManager) c
                .getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        this.screenw = display.getWidth();
        this.screenh = display.getHeight();
        this.differencew = (double) screenw / normalw;
        this.differenceh = (double) screenh / normalh;
        try{
            mediaPlayer = MediaPlayer.create(c, R.raw.nyan);
            while(mediaPlayer == null) {
                mediaPlayer = MediaPlayer.create(c, R.raw.nyan);
            }
        mediaPlayer.setLooping(true);
        if(mediaPlayer!=null)
        mediaPlayer.start();
        }
        catch(Exception e){

        }
        try{
        mediaPlayer2 = MediaPlayer.create(c, R.raw.remix);
        while(mediaPlayer2==null){
            mediaPlayer2 = MediaPlayer.create(c, R.raw.remix);
        }
        mediaPlayer2.setLooping(true);
        }
        catch(Exception e){

        }
        try{
        mediaPlayer3 = MediaPlayer.create(c, R.raw.weed);
        while(mediaPlayer3==null){
            mediaPlayer3 = MediaPlayer.create(c, R.raw.weed);
        }
        mediaPlayer3.setLooping(true);
        }
        catch(Exception e){

        }
        SharedPreferences prefs2 = c.getSharedPreferences(
                "Sp.game.spiceinspace", Context.MODE_PRIVATE);
        counter2 = prefs2.getInt("score", 0);
        this.sprite = BitmapFactory.decodeResource(getResources(),
                R.drawable.sprite, options);
        this.sprite = Bitmap.createScaledBitmap(sprite, sprite.getWidth() * 3,
                sprite.getHeight() * 3, false);
        this.heart = BitmapFactory.decodeResource(getResources(),
                R.drawable.heart);
        this.explosionheart=BitmapFactory.decodeResource(getResources(),
                R.drawable.explosionheart);
        this.heart = Bitmap.createScaledBitmap(heart, heart.getWidth() * 3,
                heart.getHeight() * 3, false);
        currentSpeed = new PointF(0, 0);
        currentDirection = new Point(0, 0);
        currentPosition = new Point(350, 350);
        this.background = BitmapFactory.decodeResource(getResources(),
                R.drawable.space);
        this.background2=BitmapFactory.decodeResource(getResources(),
                R.drawable.space2);
        this.electricExplosion = BitmapFactory.decodeResource(getResources(),
                R.drawable.effect_explosion);
        this.normalexplison = BitmapFactory.decodeResource(getResources(),
                R.drawable.effect_explosion2);
        this.background = Bitmap.createScaledBitmap(background,
                background.getWidth() * 5, background.getHeight() * 5, false);
        this.background2 = Bitmap.createScaledBitmap(background2,
                background2.getWidth() * 5, background2.getHeight() * 5, false);
        this.lost = BitmapFactory.decodeResource(getResources(),
                R.drawable.gameover);
        this.lostNew = BitmapFactory.decodeResource(getResources(),
                R.drawable.gameovernew);
        lostNew = FitAllDevices(lostNew);
        lost = FitAllDevices(lost);
        this.alien = BitmapFactory.decodeResource(getResources(),
                R.drawable.mob_alien);
        this.coin = BitmapFactory.decodeResource(getResources(),
                R.drawable.item_coin);
        partic = BitmapFactory.decodeResource(getResources(),
                R.drawable.particle_star);
        fire = BitmapFactory.decodeResource(getResources(),
                R.drawable.particle_fire);
        smoke = BitmapFactory.decodeResource(getResources(),
                R.drawable.particle_smoke);
        partic = Bitmap.createScaledBitmap(partic, partic.getWidth() * 2,
                partic.getHeight() * 2, false);
        fire = Bitmap.createScaledBitmap(fire, fire.getWidth() * 2,
                fire.getHeight() * 2, false);
        smoke = Bitmap.createScaledBitmap(smoke, smoke.getWidth() * 2,
                smoke.getHeight() * 2, false);
        electricExplosion = Bitmap.createScaledBitmap(electricExplosion,
                electricExplosion.getWidth() * 2,
                electricExplosion.getHeight() * 2, false);
        normalexplison = Bitmap.createScaledBitmap(normalexplison,
                normalexplison.getWidth() * 3,
                normalexplison.getHeight() * 3, false);
        this.alien = Bitmap.createScaledBitmap(alien, alien.getWidth() * 3,
                alien.getHeight() * 3, false);
        asteroid = BitmapFactory.decodeResource(getResources(),
                R.drawable.mob_astroid);
        bomb = BitmapFactory.decodeResource(getResources(),
                R.drawable.mob_spacebomb);
        asteroid = Bitmap.createScaledBitmap(asteroid, asteroid.getWidth() * 3,
                asteroid.getHeight() * 3, false);
        bomb = Bitmap.createScaledBitmap(bomb, bomb.getWidth() * 3,
                bomb.getHeight() * 3, false);
        goldasteroid = BitmapFactory.decodeResource(getResources(),
                R.drawable.mob_goldastroid);
        goldasteroid = Bitmap.createScaledBitmap(goldasteroid,
                goldasteroid.getWidth() * 3, goldasteroid.getHeight() * 3,
                false);
        mushroom = BitmapFactory.decodeResource(getResources(),
                R.drawable.item_mushroom);
        mushroom = Bitmap.createScaledBitmap(mushroom, mushroom.getWidth() * 4,
                mushroom.getHeight() * 4, false);
        coin = Bitmap.createScaledBitmap(coin, coin.getWidth() * 2,
                coin.getHeight() * 2, false);
        drug = BitmapFactory
                .decodeResource(getResources(), R.drawable.item_not);
        drug = Bitmap.createScaledBitmap(drug, drug.getWidth() * 4,
                drug.getHeight() * 4, false);
        rocket = BitmapFactory.decodeResource(getResources(),
                R.drawable.item_rocket);
        rocket = Bitmap.createScaledBitmap(rocket, rocket.getWidth() * 4,
                rocket.getHeight() * 4, false);
        electricExplosion = FitAllDevices(electricExplosion);
        alien = FitAllDevices(alien);
        normalexplison = FitAllDevices(normalexplison);
        explosionheart = FitAllDevices(explosionheart);
        mushroom = FitAllDevices(mushroom);
        drug = FitAllDevices(drug);
        rocket = FitAllDevices(rocket);
        bomb = FitAllDevices(bomb);
        asteroid = FitAllDevices(asteroid);
        goldasteroid = FitAllDevices(goldasteroid);
        sprite = FitAllDevices(sprite);
        heart = FitAllDevices(heart);
        player = new Spicy(sprite, heart);
        hit = soundPool.load(c, R.raw.hit, 1);
        pass = soundPool.load(c, R.raw.win, 1);
        //remix = soundPool.load(c, R.raw.remix, 1);
        destroy = soundPool.load(c, R.raw.destroy, 1);
        aliensound = soundPool.load(c, R.raw.alien, 1);
        alienexpload = soundPool.load(c, R.raw.explosion2, 1);
        //particlesound = soundPool.load(c, R.raw.particle, 1);
        bigexplosion=soundPool.load(c, R.raw.explosion, 1);
        gameLoopThread = new GameLoopThread(this);
        this.requestFocus();
        this.setFocusableInTouchMode(true);
        holder = getHolder();
        holder.addCallback(new SurfaceHolder.Callback() {

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                boolean retry = true;
                gameLoopThread.setRunning(false);
                while (retry) {
                    try {
                        gameLoopThread.join();
                        retry = false;
                    } catch (InterruptedException e) {
                    }
                }
            }

              public void surfaceCreated(SurfaceHolder holder) {     
                  if (gameLoopThread.getState()==Thread.State.TERMINATED) { 
                        gameLoopThread = new GameLoopThread(g);
                  }
                  gameLoopThread.setRunning(true);
                  gameLoopThread.start();
          }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format,
                    int width, int height) {
            }
        });
    }

Am I doing something wrong? my app is just a small game of bitmaps coming out of one side and going to the other side, at first it starts slow but the more you play the more bitmaps are coming, I would understand this amount of ram if there were many bitmaps on the screen, but it takes 60mb when there is nothing but the player and the background on the screen!

I will be happy to email anyone the app to try and see for himself how simple it is yet how much ram it takes for no reason..


Edit:

I mentioned that the app is taking more ram over time, I understand that it has to do with the fact I create new bitmaps and move them around and then remove them, and the more you play the more bitmaps are created, but I try my best to remove them right after and recycle them to make sure they are collected by the garbage collector. I wonder how can I minimize the amount of usage and still make my game playable:

this is how I "spawn" mobs (mob gets the bitmap I loaded on onLoad):

private void spawnMob() {
    if (timer2 == 0) {
        int mobtype = randInt(1, 40);
        switch (mobtype) {
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
        case 7:
        case 8:
        case 9:
        case 10:
        case 11:
        case 12:
        case 13:
        case 14:
        case 15:
            Mob m = new Mob(alien, MobEffect.comeback, 1);
            Point p = new Point(0, 0);
            p.y = randInt(0, screenh - alien.getHeight());
            p.x = screenw + alien.getWidth();
            spawned.put(p, m);
            break;

        case 16:
        case 17:
        case 18:
        case 19:
        case 20:
        case 21:
        case 22:
        case 23:
        case 24:
        case 25:
        case 26:
        case 27:
        case 28:
        case 29:
        case 30:
        case 31:
        case 32:
            Mob m2 = new Mob(asteroid, MobEffect.dissapire, 0);
            Point p2 = new Point(0, 0);
            p2.y = randInt(0, screenh - asteroid.getHeight());
            p2.x = screenw + asteroid.getWidth();
            spawned.put(p2, m2);
            break;
        case 33:
        case 34:
        case 35:
        case 36:
        case 37:
        case 38:
        case 39:
            Mob m3 = new Mob(goldasteroid, MobEffect.dissapire, 1);
            Point p3 = new Point(0, 0);
            p3.y = randInt(0, screenh - goldasteroid.getHeight());
            p3.x = screenw + goldasteroid.getWidth();
            spawned.put(p3, m3);
        case 40:
            if (counter > 3) {
                Mob m4 = new Mob(bomb, MobEffect.expload, 1, 5, false,
                        false);
                Point p4 = new Point(0, 0);
                p4.y = randInt(0, screenh - bomb.getHeight());
                p4.x = screenw + bomb.getWidth();
                spawned.put(p4, m4);
            } else {
                Mob m5 = new Mob(asteroid, MobEffect.dissapire, 0);
                Point p5 = new Point(0, 0);
                p5.y = randInt(0, screenh - asteroid.getHeight());
                p5.x = screenw + asteroid.getWidth();
                spawned.put(p5, m5);
            }
            break;
        }
        if (rocketspeed >= 10) {
            timer2 = randInt(1, 8);
        } else {
            if (bleedoff != 35) {
                if (bleedoff > 1)
                    timer2 = randInt(35 / (int) bleedoff,
                            150 / (int) bleedoff);
                else
                    timer2 = randInt(35, 150);
            } else
                timer2 = randInt(1, 10);
        }
    } else {

        timer2--;

    }
}

and on onDraw I make sure to remove the mobs so they won't take extra memory when they are not on the screen:

    Iterator<Map.Entry<Point, Mob>> spawnedEntry = spawned
                .entrySet().iterator();
        while (spawnedEntry.hasNext()) {
            Map.Entry<Point, Mob> entry = spawnedEntry.next();
            if(entry.getValue().destroycomplete||entry.getValue().dissapired){
                spawnedEntry.remove();
            }
            else
            entry.getValue().draw(canvas, entry.getKey());

also on Mob class:

if(!MobEffectstarted)
        if(!destroycomplete)
    c.drawBitmap(mob,p.x,p.y, null);
        else
            mob.recycle();

Edit 2:

this is what the memory tool of eclipse tells me: enter image description here
it's definatly the bitmaps.


enter image description here enter image description here

SpoocyCrep
  • 576
  • 4
  • 22
  • If your app takes up more RAM over time I wouldn't expect the problem to be in your onload. My first guess would be that somewhere in your game logic you're recreating an object and abandoning the old instance when it would be better for you to reuse the old object. – Corey Ogburn Mar 11 '15 at 15:11
  • Along that same line, keep in mind that just because your images are relatively small in file form (jpg, png, etc.), that does not translate into what they look like in RAM. In file form, the images are compressed to save on space. As soon as you decode them they are blown up into a raw representation of pixels, which is much larger, particularly if using ARGB8888 format. Couple that with scaling the images up (which the code above does) and you're RAM usage goes up substantially. – Larry Schiefer Mar 11 '15 at 15:13
  • @CoreyOgburn well my problem is separated to two, the first one is the fact the app takes ridiculous amount of ram only when it starts. 60mb for 5mb files isn't normal I want to believe. The second problem is the fact my app takes more and more Ram over time, which I also don't understand because I made sure to recycle and remove every bitmap I don't display. – SpoocyCrep Mar 11 '15 at 15:14
  • @LarrySchiefer I see, so how can I reduce it, I've seen apps that look a lot better and take less ram, and I thought scaling shouldn't affect the ram because its the same raw representation of pixels just scaled up.. – SpoocyCrep Mar 11 '15 at 15:16
  • It probably scales using mip mapping. That will create multiple instances of the sprite. One for each size. – Corey Ogburn Mar 11 '15 at 15:18
  • @CoreyOgburn but I replace the bitmap beforehand with the scaled bitmap, shouldn't it be removed or collected by garbage collector? – SpoocyCrep Mar 11 '15 at 15:20
  • You can use tools like Eclipse MAT to see where your memory is going. Use the DDMS allocation tracker to see what you're allocating while your game runs. See http://stackoverflow.com/questions/2298208/ for information about overall memory usage. – fadden Mar 11 '15 at 15:40
  • After your edit I would recommend you keep a pool of Mob objects. The pool would be filled during the initial load. Every time you need a new Mob: pull an available Mob from the pool, reinitialize it's parameters to make sure it's in a pristine state, use it, but instead of deleting it mark it as available and put it back in the pool. Avoid using `new` if you can. – Corey Ogburn Mar 11 '15 at 15:54
  • @fadden I did, but I didn't understand it very much to be honest. I'll edit in the question the information it wrote so maybe you could make sense out of it. – SpoocyCrep Mar 11 '15 at 15:55
  • @CoreyOgburn what is a pool? i've never heared this term before, I used hashmap to collect all the mobs, and I remove them out of it if they are not displayed. – SpoocyCrep Mar 11 '15 at 15:58
  • More info on Pools for your game objects: http://gameprogrammingpatterns.com/object-pool.html – Corey Ogburn Mar 11 '15 at 16:00
  • @fadden I didn't understand what is the question, there is only one Game view. I looked at the GC roots but I don't understand any of it, it looks like chinese for me. (Ill edit in the pic) – SpoocyCrep Mar 11 '15 at 16:04
  • [Let's continue this discussion in chat](http://chat.stackoverflow.com/rooms/72775/why-my-app-is-taking-so-much-ram) – Corey Ogburn Mar 11 '15 at 16:07

1 Answers1

2

Why does my application use up so much RAM on start-up?

Your original image files are compressed. You are decompressing them and storing a full bitmap in memory. (about rowBytes * height in bytes; find out bitmap size) You are also re-sizing a lot of them; for example the backgrounds are scaled to five times the original size. Since images have two dimensions, the memory consumption scales quadratically.

Example: An image with a size of 500x500; it allocates 4 bytes between rows (32bit mode). This results in a bitmap size of 500x4 x 500 = 1MB.

If you scale this image by a factor of 2, you allocate not twice as much memory, but: 1000x4 x 1000 = 4MB

What can I do?

  • If you don't need an alpha channel, you might want to reduce the bit-depth of your bitmaps. Decode using Bitmap.Config.RGB_565 for example.
  • Don't load larger bitmaps than needed, by specifying a sample size. (More about this here:Loading Large bitmaps)
  • Make sure you re-use as many bitmaps as possible.
  • Load bitmaps only when needed. Do you need both backgrounds simultaneously?
  • Your background seems to be only a gradient; you can use the LinearGradient class to draw it, sparing you the huge background image. (The same applies to your squares, should they not just be placeholders.)

Regarding memory leaks in your code:

  • Make sure you don't constantly create new mobs. Save "dead" mobs in a separate list, and put them back in the actor-list when needed. Only create new ones when you actually run out of "pending" mobs.
  • Don't create objects in methods that get called a lot. (e.g.: any draw() method for that matter.)

If you are testing on pre-Honeycomb you need to recycle any allocated bitmaps, as they might not get released from native memory otherwise.

Community
  • 1
  • 1
Harry Silver
  • 387
  • 2
  • 10
  • I've edited the question so you will understand why my app uses more memory as time goes by. I partly understand your answer, I understand that the bitmap store all the data of the image and decompresses it, but I don't understand what it has to do with the re-sizing. If I say the bitmap = createScaledBitmap(bitmap); shouldn't the old bitmap be replaced with the new bitmap, meaning the re-scaling shouldn't affect the memory? Also can you provide solution to this problem? can I do anything that wont ruin the game quality too much yet fix the ram? – SpoocyCrep Mar 11 '15 at 15:53