通过切换许多活动并使用相机而出现oufOfMemoryError?

发布于 2024-11-28 05:43:27 字数 7013 浏览 1 评论 0原文

我现在正在开发一款安卓游戏。这是一款我必须经常改变活动的游戏。实际上这是一个活动的循环。在谷歌搜索了一下后,我发现了这篇文章:

http: //ttlnews.blogspot.com/2010/01/attacking-memory-problems-on-android.html

其中一行说: “但对于任何其他“正常”应用程序,您在使用一段时间后仍然可以运行 OOM。例如,即使您只是打开/关闭同一屏幕(活动)20 次。”

我的应用程序每轮确实需要更多内存。但有没有可能避免这个问题呢?我正在保存大量位图。但我认为这并不过分。玩家最多为 8 人。每个玩家有 2 个 Bitmap。在 X10 上,一个为 120x120,另一个为 180x180。如果我玩了几轮并返回标题屏幕开始一个完整的新游戏,堆大小可能已经相当满了。通过开始一个新游戏,我可以用相机制作一张图片,我可以将其用作玩家的位图。如果我达到 OnPictureTaken,我的应用程序将在短时间内多需要 7MB。这有时会导致 MMO,具体取决于我之前玩过多少游戏。

然而..这里有一些我用来避免 OOM 的代码片段。

在游戏开始之前,我必须进行一些设置。其中我必须设置玩家数量。如果我到达此活动,我会尝试删除迄今为止创建的玩家的所有位图(如果我想在终止另一个游戏后玩一个全新的游戏,则存在这些位图)

    for (int i = 0; i < StbApp.getPlayer().size(); i++){
        StbApp.getPlayer().get(i).getAvaterBig().recycle();
        StbApp.getPlayer().get(i).getAvatar().recycle();
        StbApp.getPlayer().get(i).setAvatarBig(null);
        StbApp.getPlayer().get(i).setAvatar(null);
    }
    System.gc();
    StbApp.getPlayer().clear();

以下是我拍照的方式: 首先是参数:

Camera.Parameters parameters = camera.getParameters();
List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes();
Camera.Size previewSize = previewSizes.get(0);
params.setPreviewSize(previewSize.width, previewSize.height);
params.setPictureFormat(PixelFormat.JPEG);
params.setJpegQuality(50);
camera.setParameters(params);
camera.startPreview();

这是我的 OnPictureTaken 方法:

camera.takePicture(null, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                try {
                    StbApp.logHeapSizeInfo();
                    //the name of the file
                    Log.d(TAG, "GC_ test2");
                    fileName = String.format("avatar_"+System.currentTimeMillis()+".jpg");
                    Log.d(TAG, "GC_ test3");
                    //will save
                    FileOutputStream fos = openFileOutput(fileName, Context.MODE_PRIVATE);
                    Log.d(TAG, "GC_ test4");
                    fos.write(data);
                    Log.d(TAG, "GC_ test5");
                    fos.close();
                    Log.d(TAG, "GC_ test6");
                } 
                catch(Exception e) {
                  Log.e(TAG, "saveJPEGBitmapToMediaStore: failed to save image", e);
                } 
            }
        });

比我使用 setReult:

if (view == takeAndUseBtn && takeAndUseBtn.getText().toString().equalsIgnoreCase(getResources().getString(R.string.use))) {
        if (fileName != null){
            Intent intent = new Intent();
            intent.putExtra("fileName", fileName);
            setResult(RESULT_OK, intent);
            this.finish();
        } else {
            Toast.makeText(this, "wait until picture is saved", Toast.LENGTH_SHORT).show();
            Log.d(TAG, "not saved yet");
        }
    }

如果用户对图片满意并希望在游戏中使用它,就会发生这种情况:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.d(TAG, "requestCode: " +requestCode);
    if (resultCode == RESULT_OK){
        String file = data.getExtras().getString("fileName");
        FileInputStream fis = null;
        try {
            fis = openFileInput(file);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "File not found :(");
            e.printStackTrace();
        }
        if (fis != null){
            StbApp.logHeapSizeInfo();
            Options opt = new Options();
            opt.inSampleSize = 5;
            opt.inPurgeable = true;
            Bitmap bm = BitmapFactory.decodeStream(fis, null, opt);

            bm = Bitmap.createScaledBitmap(bm, getResources().getDrawable(R.drawable.avatar1_big).getMinimumWidth(), getResources().getDrawable(R.drawable.avatar1_big).getMinimumHeight(), false);
            StbApp.getPlayer().get(requestCode).setAvatarBig(bm);

            bm = Bitmap.createScaledBitmap(bm, getResources().getDrawable(R.drawable.avatar1).getMinimumWidth(), getResources().getDrawable(R.drawable.avatar1).getMinimumHeight(), false);
            StbApp.getPlayer().get(requestCode).setAvatar(bm);

            bm.setDensity(DisplayMetrics.DENSITY_MEDIUM);
            Drawable drawable = new BitmapDrawable(bm);
            avatarBtn[requestCode].setBackgroundDrawable(drawable);

            bm.setDensity(getResources().getDisplayMetrics().densityDpi);
            deleteFile(file);
            try {
                fis.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            StbApp.logHeapSizeInfo();
        }
    }
    super.onActivityResult(requestCode, resultCode, data);
}

每次新一轮开始时,我都会显示特定的活动。在开始我真正想要开始的活动之前,我让它用 FLAG_CLEAR_TOP 启动标题屏幕,以确保没有任何不必要的活动正​​在运行:

    if (view == startBtn){
        StbApp.setRound(StbApp.getRound()+1);
        Intent intent = new Intent(this, SpinTheBottle.class);
        Intent intentToClear = new Intent(this, TitleScreen.class);
        intentToClear.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + Intent.FLAG_ACTIVITY_SINGLE_TOP);
        startActivity(intentToClear);
        startActivity(intent);
    }

现在这里是来自 GC 的一些日志: 游戏刚刚开始,我还没有拍照:

08-04 08:34:44.355: DEBUG/dalvikvm(317): GC_CONCURRENT freed 1194K, 53% free 3377K/7175K, external 2615K/2699K, paused 1ms+11ms

拍照后(在调用 OnPictureTaken 之前)(如果用户玩了一段时间并想要开始一个全新的游戏,应用程序就会崩溃。

08-04 08:36:21.875: DEBUG/dalvikvm(4216): GC_FOR_MALLOC freed 1473K, 60% free 2813K/6983K, external 10938K/12508K, paused 17ms

当我返回后从相机活动到上一个活动以使用图片

08-04 08:37:28.365: DEBUG/dalvikvm(317): GC_EXTERNAL_ALLOC freed 221K, 59% free 2992K/7175K, external 2875K/2877K, paused 100ms

第一轮开始:

08-04 08:38:54.005: DEBUG/dalvikvm(4216): GC_EXTERNAL_ALLOC freed 608K, 56% free 3742K/8391K, external 14229K/15604K, paused 28ms

几轮之后(它不断增加直到崩溃):

08-04 08:40:07.845: DEBUG/dalvikvm(4216): GC_CONCURRENT freed 1486K, 49% free 4945K/9671K, external 16187K/17938K, paused 2ms+7ms

有时,如果一轮刚刚开始,我会得到类似这样的东西(我绘制位图的活动画布上的玩家使用 canvas.drawBitmap

08-04 08:40:07.425: DEBUG/dalvikvm(317): GC_CONCURRENT freed 1312K, 54% free 3379K/7303K, external 2139K/2671K, paused 2ms+14ms

但在这一行之后会有一行,就像我之前发布的那样,

如果用户想要的话,就会发生这种情况 。开始一个新游戏,并准备选择这次有多少玩家加入游戏(这就是我尝试回收位图并手动使用 GC 的地方)

08-04 08:43:46.635: DEBUG/dalvikvm(4216): GC_EXPLICIT freed 69K, 70% free 3402K/11079K, external 18337K/20004K, paused 31ms

正如你所看到的.. 没有发生太多事情,

我知道这很漂亮 。但我就是不知道很多代码。我可以尝试改进代码以获得更多内存。如果有人知道一本关于 Android 内存管理的好书,如果他/她能推荐给我,我将不胜感激。

I am working on a game for android right now. It is a game where I have to change the activity very often. Actually it is a cycle of activities. After searching a bit in google I found this article:

http://ttlnews.blogspot.com/2010/01/attacking-memory-problems-on-android.html

One line says:
"But for any other "normal" app you can still run OOM after using it for a little while. For example even when you just open/close the same screen (Activity) for 20 times."

My app does need more memory every round. But isn't there a possibility to avoid this problem? I am saving quiet a lot of Bitmaps. But I do not think it is too much. The maximum of players are 8. And everey player got 2 Bitmap. On X10 one will be 120x120 and the other one 180x180. If I play a few rounds and go back to titlescreen to start a completelay new game the heap size might be pretty full already. By starting a new game I got the possibility to make a picture with the camera, which I can use as a Bitmap for the players. If I reach OnPictureTaken my app will need 7MB more for a short time. This causes MMOs sometimes depending on how much I played before.

However.. here a some code snippet I use to avoid OOM so far.

Before the game starts I have to make a few settings. Among others I have to set the number of players. If I reach this Activity I try to delete all Bitmaps of the player I created so far (which exist if I want to play a completely new game after terminating another)

    for (int i = 0; i < StbApp.getPlayer().size(); i++){
        StbApp.getPlayer().get(i).getAvaterBig().recycle();
        StbApp.getPlayer().get(i).getAvatar().recycle();
        StbApp.getPlayer().get(i).setAvatarBig(null);
        StbApp.getPlayer().get(i).setAvatar(null);
    }
    System.gc();
    StbApp.getPlayer().clear();

Here is the way how I take the photo:
First of all the parameters:

Camera.Parameters parameters = camera.getParameters();
List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes();
Camera.Size previewSize = previewSizes.get(0);
params.setPreviewSize(previewSize.width, previewSize.height);
params.setPictureFormat(PixelFormat.JPEG);
params.setJpegQuality(50);
camera.setParameters(params);
camera.startPreview();

That's my OnPictureTaken method:

camera.takePicture(null, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                try {
                    StbApp.logHeapSizeInfo();
                    //the name of the file
                    Log.d(TAG, "GC_ test2");
                    fileName = String.format("avatar_"+System.currentTimeMillis()+".jpg");
                    Log.d(TAG, "GC_ test3");
                    //will save
                    FileOutputStream fos = openFileOutput(fileName, Context.MODE_PRIVATE);
                    Log.d(TAG, "GC_ test4");
                    fos.write(data);
                    Log.d(TAG, "GC_ test5");
                    fos.close();
                    Log.d(TAG, "GC_ test6");
                } 
                catch(Exception e) {
                  Log.e(TAG, "saveJPEGBitmapToMediaStore: failed to save image", e);
                } 
            }
        });

Than I use setReult:

if (view == takeAndUseBtn && takeAndUseBtn.getText().toString().equalsIgnoreCase(getResources().getString(R.string.use))) {
        if (fileName != null){
            Intent intent = new Intent();
            intent.putExtra("fileName", fileName);
            setResult(RESULT_OK, intent);
            this.finish();
        } else {
            Toast.makeText(this, "wait until picture is saved", Toast.LENGTH_SHORT).show();
            Log.d(TAG, "not saved yet");
        }
    }

And that is what happening if the user is satisfied with the picture and wants to use it in game:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.d(TAG, "requestCode: " +requestCode);
    if (resultCode == RESULT_OK){
        String file = data.getExtras().getString("fileName");
        FileInputStream fis = null;
        try {
            fis = openFileInput(file);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "File not found :(");
            e.printStackTrace();
        }
        if (fis != null){
            StbApp.logHeapSizeInfo();
            Options opt = new Options();
            opt.inSampleSize = 5;
            opt.inPurgeable = true;
            Bitmap bm = BitmapFactory.decodeStream(fis, null, opt);

            bm = Bitmap.createScaledBitmap(bm, getResources().getDrawable(R.drawable.avatar1_big).getMinimumWidth(), getResources().getDrawable(R.drawable.avatar1_big).getMinimumHeight(), false);
            StbApp.getPlayer().get(requestCode).setAvatarBig(bm);

            bm = Bitmap.createScaledBitmap(bm, getResources().getDrawable(R.drawable.avatar1).getMinimumWidth(), getResources().getDrawable(R.drawable.avatar1).getMinimumHeight(), false);
            StbApp.getPlayer().get(requestCode).setAvatar(bm);

            bm.setDensity(DisplayMetrics.DENSITY_MEDIUM);
            Drawable drawable = new BitmapDrawable(bm);
            avatarBtn[requestCode].setBackgroundDrawable(drawable);

            bm.setDensity(getResources().getDisplayMetrics().densityDpi);
            deleteFile(file);
            try {
                fis.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            StbApp.logHeapSizeInfo();
        }
    }
    super.onActivityResult(requestCode, resultCode, data);
}

Every time I a new round starts I display the certain activity. I let it start the titlescreen with FLAG_CLEAR_TOP before I start the activity I really want to start to make sure there aren't any unnecessary activities running:

    if (view == startBtn){
        StbApp.setRound(StbApp.getRound()+1);
        Intent intent = new Intent(this, SpinTheBottle.class);
        Intent intentToClear = new Intent(this, TitleScreen.class);
        intentToClear.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + Intent.FLAG_ACTIVITY_SINGLE_TOP);
        startActivity(intentToClear);
        startActivity(intent);
    }

Now here are some Logs from the GC:
The game just started and I haven't take a picture yet:

08-04 08:34:44.355: DEBUG/dalvikvm(317): GC_CONCURRENT freed 1194K, 53% free 3377K/7175K, external 2615K/2699K, paused 1ms+11ms

After I take a picture (before OnPictureTaken will be called) (That's where the app crashes if the user plays a while and wants to start a completely new game.

08-04 08:36:21.875: DEBUG/dalvikvm(4216): GC_FOR_MALLOC freed 1473K, 60% free 2813K/6983K, external 10938K/12508K, paused 17ms

After I return to the previos activity from the cameraactivity to use the picutre

08-04 08:37:28.365: DEBUG/dalvikvm(317): GC_EXTERNAL_ALLOC freed 221K, 59% free 2992K/7175K, external 2875K/2877K, paused 100ms

The first round starts:

08-04 08:38:54.005: DEBUG/dalvikvm(4216): GC_EXTERNAL_ALLOC freed 608K, 56% free 3742K/8391K, external 14229K/15604K, paused 28ms

After a few rounds (it keeps on increasing until it crashs):

08-04 08:40:07.845: DEBUG/dalvikvm(4216): GC_CONCURRENT freed 1486K, 49% free 4945K/9671K, external 16187K/17938K, paused 2ms+7ms

Sometimes if the round just starts I get something like this (the activity where I draw the Bitmaps of the players on the canvas with canvas.drawBitmap

08-04 08:40:07.425: DEBUG/dalvikvm(317): GC_CONCURRENT freed 1312K, 54% free 3379K/7303K, external 2139K/2671K, paused 2ms+14ms

But right after this line there will be a line like the one I posted before. It keeps on increasing at the end.

And thats what happens if the user wants to start a new game and is about to choose how many players this time will join the game. (Thats where I try to recyle the Bitmaps and using the GC manually)

08-04 08:43:46.635: DEBUG/dalvikvm(4216): GC_EXPLICIT freed 69K, 70% free 3402K/11079K, external 18337K/20004K, paused 31ms

As you can see.. not much is happening.

I know it is pretty much code. But I just do not know where I can try to improve the code to get more memory. If anybody knows a good book about memory management in android I would be grateful if he/she could recommend me it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我ぃ本無心為│何有愛 2024-12-05 05:43:27

我在使用频繁切换活动的应用程序时遇到了同样的问题。我通过终止进程并在每次迭代中重新开始来解决这个问题。

    PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, tartActivity.class), 0);
    AlarmManager mgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
    mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 50, intent);
    android.os.Process.killProcess(android.os.Process.myPid());

我知道这是一个黑客行为。但我还没有找到其他解决方案。

顺便说一句,位图对象位于本机堆中,但不在托管堆中。所以GC没有帮助。
看这里 http://maximbogatov.wordpress.com/2011/08/ 03/bitmaps-in-android/

在我的应用程序中,托管堆一切正常,但本机堆正在增长。我回收了我创建的所有位图。我很确定。但无论如何,本机堆正在逐渐增长。

I had the same problem while working with app that switched activities too often. I solved it by killing the process and starting in again every iteration.

    PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, tartActivity.class), 0);
    AlarmManager mgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
    mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 50, intent);
    android.os.Process.killProcess(android.os.Process.myPid());

I know that it is a hack. But I hadn't coped to find another solution.

By the way Bitmap objects are on native heap but not in managed one. So GC doesn't help.
Look here http://maximbogatov.wordpress.com/2011/08/03/bitmaps-in-android/

And it was everything OK with managed heap in my application, but native heap was growing. I recycled all the bitmaps I created. I'm pretty sure. But native heap was gradually growing anyway.

执妄 2024-12-05 05:43:27

最后我发现我在几个layout.xml中设置的小部件中的大图像(例如android:background:“@drawable/resourceId”)已经分配了大部分堆大小。我给带有大图像的小部件一个 ID,这样如果我不需要它,我可以将其删除。如果用户返回屏幕,我必须再次设置图像。

boyImg = (ImageView) findViewById(R.id.imgv_boy);
if (boyImg.getDrawable() != null){
boyImg.getDrawable().setCallback(null);
boyImg.setImageDrawable(null);
}
System.gc();

At the end I found out that the large images in the widgets which I set in several layout.xml (e.g. android:background:"@drawable/resourceId") have allocate most of the heap size. I gave the widgets with a large image an ID, so I can delete it if I don't need it. If the user returns to the screen I have to set the image again though.

boyImg = (ImageView) findViewById(R.id.imgv_boy);
if (boyImg.getDrawable() != null){
boyImg.getDrawable().setCallback(null);
boyImg.setImageDrawable(null);
}
System.gc();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文