为什么我的应用程序占用如此多的内存?

7

我为学校项目制作了一个游戏应用,使用了位图和SurfaceView。但是这个应用程序占用了太多的内存!只有在启动时它才会占用高达60MB的内存,而且你玩得越久,它所占用的内存就越高(有一次它占用了90MB的内存,游戏变得非常卡顿)。

在观看了2011年Google I/O大会后(https://www.youtube.com/watch?v=_CruQY55HOk),我意识到这可能是内存泄漏,因为应用程序的启动方式如下:enter image description here
而玩了2分钟后,它变成了这样:
enter image description here

这个应用程序本身被设计得尽可能简单,采用了8位图形和不多的颜色:enter image description here

我使用的所有图像仅重量为400KB,为什么它需要占用这么多内存?我以为可能是声音,但所有声音加起来只占用了4.45MB的内存,这只是应用程序所占内存量的1/10。我知道位图需要很多内存,但这太离谱了!

这是我的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) {
            }
        });
    }

我做错了什么吗?我的应用程序只是一个小游戏,一些位图从一侧出现,然后到达另一侧。一开始速度很慢,但玩得越多,就会有越来越多的位图出现。如果屏幕上有许多位图,我可以理解为何需要这么多内存,但当屏幕上只有玩家和背景时,它却需要60MB!
我很乐意通过电子邮件向任何人发送该应用程序,以便他自己尝试并了解其简单性以及无缘由占用多少内存。
编辑:
我提到应用程序随着时间推移而占用更多内存,我知道这与我创建新的位图并将它们移动然后删除它们有关,而且玩得越多,就会创建越来越多的位图,但我尽力在创建后立即将它们删除并回收它们,以确保垃圾收集器可以清除它们。我想知道如何最大程度地减少使用量,并仍使我的游戏可玩:
这是我“生成”mobs(mob获取我在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--;

    }
}

在onDraw方法中,我会确保移除那些不在屏幕上的怪物,以免它们占用过多的内存:

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

也适用于Mob类:

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

编辑2:

这是eclipse内存工具显示的内容: enter image description here
确定是位图导致的问题。


enter image description here enter image description here


沿着同样的路线,记住即使您的图像在文件形式(jpg、png等)中相对较小,也不代表它们在RAM中的外观。在文件形式中,图像被压缩以节省空间。一旦您对它们进行解码,它们就会被放大成为原始像素表示,这会使它们变得更大,尤其是如果使用ARGB8888格式。再加上缩放图像(如上面的代码所示),您的RAM使用量会大幅增加。 - Larry Schiefer
@CoreyOgburn 嗯,我的问题分为两个部分。第一个问题是应用程序在启动时占用的内存量非常荒谬。我想5MB文件需要60MB的内存不正常。第二个问题是随着时间的推移,我的应用程序占用的内存越来越多,这也让我感到困惑,因为我已经确保回收和删除了所有我不显示的位图。 - SpoocyCrep
@LarrySchiefer 我明白了,那我该怎么减少它呢?我看到了许多外观更好且占用更少内存的应用程序。我认为缩放不应该影响内存,因为它只是将相同的原始像素表示进行了放大。 - SpoocyCrep
@CoreyOgburn但是如果我事先用缩放后的位图替换了原始位图,那么它不应该被垃圾回收器移除或收集吗? - SpoocyCrep
有关游戏对象池的更多信息:http://gameprogrammingpatterns.com/object-pool.html - Corey Ogburn
显示剩余8条评论
1个回答

2

为什么我的应用程序在启动时占用了这么多内存?

你的原始图像文件是经过压缩的。你正在对它们进行解压缩,并将完整位图存储在内存中(大约是按行字节数*高度的字节数计算;查找位图大小)。你还调整了很多图像的大小;例如,背景被缩放到原始大小的五倍。由于图像有两个维度,内存消耗呈平方增长。

例如:一个大小为500x500的图像;它在行之间分配4个字节(32位模式)。 这会导致一个1MB的位图大小。

如果你将此图像缩放2倍,你分配的内存不是两倍,而是:1000x4 x 1000 = 4MB

我该怎么办?

  • 如果你不需要alpha通道,可以降低位图的位深度。例如,使用Bitmap.Config.RGB_565进行解码。
  • 通过指定采样率来避免加载比需要更大的位图。(有关详细信息,请参见:加载大位图
  • 尽可能多地重复使用位图。
  • 仅在需要时加载位图。你需要同时显示两个背景吗?
  • 你的背景似乎只是一个渐变;你可以使用LinearGradient类来绘制它,从而避免使用巨大的背景图像。(对于你的正方形也是如此,如果它们不只是占位符。)

关于你代码中的内存泄漏:

  • 确保你不会不断地创建新的mob。将“死亡”的mob保存在单独的列表中,并在需要时将它们放回actor列表中。只有在实际用完“待处理”的mob时才创建新的mob。
  • 不要在频繁调用的方法中创建对象。(例如:任何draw()方法都是如此。)

如果你在预Honeycomb版本上进行测试,则需要回收任何分配的位图,否则它们可能无法从本机内存中释放。


我已经编辑了问题,这样你就能理解为什么我的应用程序随着时间的推移使用更多的内存。我部分理解了你的答案,我理解位图存储了图像的所有数据并对其进行解压缩,但我不明白它与重新调整大小有什么关系。如果我说bitmap = createScaledBitmap(bitmap); 那么旧的bitmap不应该被新的bitmap替换吗?这意味着重新缩放不应该影响内存吗?另外,您能提供解决此问题的解决方案吗?我能做些什么来修复RAM而不会太损坏游戏质量吗? - SpoocyCrep

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接