通过切换许多活动并使用相机而出现oufOfMemoryError?
我现在正在开发一款安卓游戏。这是一款我必须经常改变活动的游戏。实际上这是一个活动的循环。在谷歌搜索了一下后,我发现了这篇文章:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我在使用频繁切换活动的应用程序时遇到了同样的问题。我通过终止进程并在每次迭代中重新开始来解决这个问题。
我知道这是一个黑客行为。但我还没有找到其他解决方案。
顺便说一句,位图对象位于本机堆中,但不在托管堆中。所以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.
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.
最后我发现我在几个layout.xml中设置的小部件中的大图像(例如android:background:“@drawable/resourceId”)已经分配了大部分堆大小。我给带有大图像的小部件一个 ID,这样如果我不需要它,我可以将其删除。如果用户返回屏幕,我必须再次设置图像。
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.