oufOfMemoryError, переключая много активностей и используя камеру? - PullRequest
0 голосов
/ 04 августа 2011

Я сейчас работаю над игрой для Android. Это игра, в которой мне приходится очень часто менять активность. На самом деле это цикл деятельности. После поиска немного в Google я нашел эту статью:

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

Одна строка говорит: «Но для любого другого« нормального »приложения вы все равно можете запустить OOM после некоторого использования. Например, даже когда вы просто открываете / закрываете один и тот же экран (Activity) 20 раз».

Мое приложение требует больше памяти каждый раунд. Но разве нет возможности избежать этой проблемы? Я экономлю много битмапов. Но я не думаю, что это слишком много. Максимальное количество игроков - 8. И каждый игрок получил 2 битмапа. На X10 один будет 120x120, а другой 180x180. Если я сыграю несколько раундов и вернусь на титульный экран, чтобы начать полноценную новую игру, размер кучи может быть уже достаточно полным. Запустив новую игру, я получил возможность сделать снимок с камеры, который я могу использовать в качестве растрового изображения для игроков. Если я достигну OnPictureTaken, моему приложению понадобится еще 7 МБ на короткое время. Это иногда вызывает 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);
    }

Теперь вот несколько журналов из ГХ: Игра только началась и я еще не сфотографировал:

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

После того, как я возвращаюсь к предыдущей активности с камеры, чтобы использовать picutre

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, я был бы признателен, если бы он / она мог порекомендовать ее мне.

Ответы [ 2 ]

2 голосов
/ 04 августа 2011

У меня была такая же проблема при работе с приложением, которое слишком часто переключало действия.Я решил это, убив процесс и начав заново каждую итерацию.

    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/

И все было в порядке с управляемой кучей в моем приложении, но собственная куча росла.Я переработал все созданные мной растровые изображения.Я точно уверен.Но родная куча все равно постепенно росла.

0 голосов
/ 15 ноября 2011

В конце я обнаружил, что большие изображения в виджетах, которые я установил в нескольких layout.xml (например, android: background: "@ drawable / resourceId"), выделяют большую часть размера кучи. Я дал виджетам с большим изображением идентификатор, чтобы я мог удалить его, если он мне не нужен. Если пользователь возвращается к экрану, я должен снова установить изображение.

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