开发者

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜