简体   繁体   English

Android应用中的Java.Lang.OutOfMemoryError

[英]Java.Lang.OutOfMemoryError in Android App

I had an app working fine and then all of a sudden it started force closing/crashing and the logcat says it's an out of memory error. 我有一个运行良好的应用程序,然后突然开始强制关闭/崩溃,并且日志记录程序说这是内存不足错误。

The logcat is below: 日志记录如下:

04-27 10:53:05.366 8899-8899/cct.mad.lab E/AndroidRuntime: FATAL EXCEPTION: main
                                                       Process: cct.mad.lab, PID: 8899
                                                       java.lang.OutOfMemoryError
                                                           at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
                                                           at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:587)
                                                           at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:422)
                                                           at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:445)
                                                           at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:475)
                                                           at cct.mad.lab.Sprite.<init>(Sprite.java:37)
                                                           at cct.mad.lab.GameView.createSprites(GameView.java:63)
                                                           at cct.mad.lab.GameView.surfaceCreated(GameView.java:86)
                                                           at android.view.SurfaceView.updateWindow(SurfaceView.java:572)
                                                           at android.view.SurfaceView.access$000(SurfaceView.java:86)
                                                           at android.view.SurfaceView$3.onPreDraw(SurfaceView.java:175)
                                                           at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:847)
                                                           at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1871)
                                                           at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1000)
                                                           at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5670)
                                                           at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
                                                           at android.view.Choreographer.doCallbacks(Choreographer.java:574)
                                                           at android.view.Choreographer.doFrame(Choreographer.java:544)
                                                           at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
                                                           at android.os.Handler.handleCallback(Handler.java:733)
                                                           at android.os.Handler.dispatchMessage(Handler.java:95)
                                                           at android.os.Looper.loop(Looper.java:136)
                                                           at android.app.ActivityThread.main(ActivityThread.java:5017)
                                                           at java.lang.reflect.Method.invokeNative(Native Method)
                                                           at java.lang.reflect.Method.invoke(Method.java:515)
                                                           at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
                                                           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
                                                           at dalvik.system.NativeStart.main(Native Method)

The sprite class is where it appears the problem is being caused (at line 37). Sprite类是出现问题的地方(第37行)。 It is a png image. 这是一个png图像。 The section of code, with line 37 being the third line down, is below: 下面是代码段,其中第37行是第三行:

public Sprite(GameView gameView) {
    this.gameView = gameView;
    spritebmp = BitmapFactory.decodeResource(gameView.getResources(),
            R.drawable.bad4);
    this.bmp_width = spritebmp.getWidth() / BMP_COLUMNS;
    this.bmp_height = spritebmp.getHeight() / BMP_ROWS;
    xSpeed = random.nextInt(15) + 1;
    ySpeed = random.nextInt(15) + 1;
    x = random.nextInt(gameView.getWidth() - bmp_width);
    y = random.nextInt(gameView.getHeight() - bmp_height);

I've tried adding the below to the manifest but with no success: 我尝试将以下内容添加到清单中,但没有成功:

android:largeHeap="true"

I'm not quite sure what to try next and this is for an assignment. 我不太确定接下来要尝试什么,这是一项任务。 It was going so well but if I can't figure this out I won't have anything to submit. 一切都很好,但是如果我不明白这一点,我将没有什么可提交的。

All help and advice greatly appreciated. 所有帮助和建议,不胜感激。

Thanks 谢谢

EDIT: Below I have added the entire sprite class as from what people are saying, some things need to change in here: 编辑:在下面,我从人们所说的添加了整个sprite类,在这里需要进行一些更改:

package cct.mad.lab;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.media.SoundPool;
import android.support.v7.app.AppCompatActivity;

import java.util.Random;


public class Sprite extends AppCompatActivity {

private static final int BMP_ROWS = 4;
private static final int BMP_COLUMNS = 3;
//x,y position of sprite - initial position (0,50)
private int x = 0;
private int y = 0;
private int xSpeed = 5;//Horizontal increment of position (speed)
private int ySpeed = 5;// Vertical increment of position (speed)
private GameView gameView;
private Bitmap spritebmp;
private int currentFrame = 0;
//Width and Height of the Sprite image
private int bmp_width;
private int bmp_height;
// Needed for new random coordinates.
private Random random = new Random();

private SoundPool mySound;
int zapSoundId;


public Sprite(GameView gameView) {
    this.gameView = gameView;
    spritebmp = BitmapFactory.decodeResource(gameView.getResources(),
            R.drawable.bad3);
    this.bmp_width = spritebmp.getWidth() / BMP_COLUMNS;
    this.bmp_height = spritebmp.getHeight() / BMP_ROWS;
    xSpeed = random.nextInt(15) + 1;
    ySpeed = random.nextInt(15) + 1;
    x = random.nextInt(gameView.getWidth() - bmp_width);
    y = random.nextInt(gameView.getHeight() - bmp_height);

    //mySound = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
    //zapSoundId = mySound.load(this, R.raw.zap, 1);


}

public Sprite(GameView gameView, Bitmap bmp) {

}

//update the position of the sprite
public void update() {
    x = x + xSpeed;
    y = y + ySpeed;
    bounce();
    //y = random.nextInt(gameView.getWidth());
    //wrapAround(); //Adjust motion of sprite.
}

private void bounce() {
    if (x <= 0 || x >= gameView.getWidth() ) {
        xSpeed = xSpeed * -1;
    }
    if (y <= 0 || y >= gameView.getHeight() ) {
        ySpeed = ySpeed * -1;
    }
    currentFrame = ++currentFrame % BMP_COLUMNS;
    /*if (x > gameView.getWidth() - spritebmp.getWidth() - xSpeed) {
        xSpeed = -5;
        if (x + xSpeed < 0) {
            xSpeed = 5;
        }
        x = x + xSpeed;
    }
    if (y > gameView.getHeight() - spritebmp.getHeight() - ySpeed) {
        ySpeed = -5;
        if (y + ySpeed < 0) {
            ySpeed = 5;
        }
        y = y + ySpeed;
    }*/
}

public void draw(Canvas canvas) {

    update();
    int srcX = currentFrame * bmp_width;
    int srcY;
    if (xSpeed > 0) {
        srcY = 0 * bmp_height;
    }
    else {
        srcY = 1 * bmp_height;
    }
    // Create Rect around the source image to be drawn
    Rect src = new Rect(srcX, srcY, srcX + bmp_width, srcY + bmp_height);
    // Rect for destination image
    Rect dst = new Rect(x, y, x + bmp_width, y + bmp_height);
    //
    // Draw the image frame
    canvas.drawBitmap(spritebmp, src, dst, null);

}


// Checks if the sprite was touched
public boolean wasItTouched(float ex, float ey) {
    boolean touched = false;
    if ((x <= ex) && (ex < x + bmp_width) &&
            (y <= ey) && (ey < y + bmp_height)) {
        touched = true;
        //mySound.play(zapSoundId, 1, 1, 1, 0, 1);


    }
    //

    return touched;
}

}

EDIT: I would also like to point out that the sprites I use are all less than 5kb each and I have an array that replicates it about 8 times, so it's hardly a massive image that would take up so much memory 编辑:我还想指出,我使用的精灵每个都小于5kb,并且我有一个数组将其复制大约8次,因此它几乎不是一个占用大量内存的大图像。

Please refer to this tutorial: https://developer.android.com/topic/performance/graphics/load-bitmap.html 请参考本教程: https : //developer.android.com/topic/performance/graphics/load-bitmap.html

In summary they first read the size of the bitmap. 总之,他们首先读取位图的大小。 Then they subsample the file. 然后,他们对该文件进行子采样。

Edit: Now I see that your file is very small. 编辑:现在我看到您的文件很小。 When it comes to games - please make sure that you load the resource into memory just once and then reuse it. 关于游戏-请确保仅将资源加载到内存一次,然后重新使用。

Also, when you're done using the bitmap, call .recycle() . 同样,当您使用完位图后,请调用.recycle()

Ideally you should have some asset manager object that loads resources and keeps them for as long as needed. 理想情况下,您应该有一些资产管理器对象来加载资源并将其保留所需的时间。 And any Sprite must ask for the already loaded resource instead of reading a new one everytime. 而且任何Sprite必须请求已经加载的资源,而不是每次都读取一个新资源。

Edit 2: Ok, let's explain it a bit further. 编辑2:好的,让我们进一步解释一下。 If it's a simple game, and I assume it is, create a class that will handle loading of your assets. 如果这是一个简单的游戏,并且我认为是这样,请创建一个类来处理您的资产加载。 Let's call it AssetManager . 我们称它为AssetManager

Then let's make AssetManager be instantiated only once (a singleton) by your game "engine" (whatever handles the game loop). 然后,让您的游戏“引擎”(无论处理游戏循环如何)实例化AssetManager一次(单例)。 Prepare some kind of mapping, you can read it from file or you can hardcode it for now (just for the sake of simplicity of this example). 准备某种映射,您可以从文件中读取它,或者现在可以对其进行硬编码(仅出于简化本示例的目的)。

let the AssetManager hold this map and another one for bitmaps. AssetManager保留此映射,为位图保留另一个。

Map<String, Integer> spriteRes;
Map<String, Bitmap> spriteBmps;

and then in Sprite take a String argument describing what bitmap to use, eg 然后在Sprite使用String参数描述要使用的位图,例如

Sprite ghost = new Sprite("ghost");

Every game runs in a loop and has a draw function. 每个游戏都循环运行并具有平局功能。 Then for every Sprite that's in the scene all you need to do is to take the name of the resource and retrieve the Bitmap from the AssetManager . 然后,对于场景中的每个Sprite ,您所需要做的就是获取资源名称并从AssetManager检索位图。 If it doesn't have it stored in spriteBmps , then call Bitmap.decodeResource() and store it. 如果没有将其存储在spriteBmps ,则调用Bitmap.decodeResource()并将其存储。 You can also preload all assets at the start of your program. 您还可以在程序开始时预加载所有资产。

class AssetManager {
    Map<String, Integer> spriteRes;
    Map<String, Bitmap> spriteBmps;

    Bitmap getBitmap(GameView gameView, String key) {
        if (spriteBmps.containsKey(key) && spriteBmps.get(key) != null)
            return spriteBmps.get(key);
        Bitmap bitmap = BitmapFactory.decodeResource(gameView.getResources(), R.drawable.bad3)
        spriteBmps.put(key, bitmap);
        return bitmap;
    }
}

That way you hold just one Bitmap for every object type and only tell where to render it on the canvas. 这样,您就只为每种对象类型保留一个Bitmap ,并且只告诉在画布上渲染它的位置。

When to call .recycle() ? 什么时候调用.recycle() When you no longer need the bitmap. 当您不再需要位图时。 It depends on your case and architecture. 这取决于您的情况和体系结构。

The problem was with my initial for loop. 问题出在我最初的for循环上。 I mistakenly made it so that it was never ending and always creating a new sprite in my GameView class. 我错误地做到了,以至于它永无止境,并总是在我的GameView类中创建一个新的精灵。 I fixed that and it now works fine 我修复了它,现在可以正常工作

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM