oufOfMemoryError by switching through many activites and using camera?
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].setBackgroundDrawabl开发者_如何学Pythone(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.
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.
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();
精彩评论