Android: AlertDialog вызывает утечку памяти - PullRequest
14 голосов
/ 16 августа 2011

Мое приложение показывает AlertDialog с ListView внутри. Все работало нормально, тогда я решил проверить это на утечки памяти. После запуска приложения в течение некоторого времени я открыл MAT и сгенерировал отчет Leak Suspect. МАТ обнаружил несколько похожих утечек:

Один экземпляр "com.android.internal.app.AlertController $ RecycleListView" , загруженный "<системный загрузчик классов>" , занимает ...

Я потратил много времени на поиск причины этой утечки. Проверка кода мне не помогла, и я начал гуглить. Вот что я нашел:

Проблема 5054: похоже, что AlertDialog вызывает утечку памяти из-за сообщения в MessageQueue

Я решил проверить, воспроизводит ли этот баг или нет. Для этого я создал небольшую программу, которая состоит из двух упражнений. MainActivity это начальная точка. Он содержит только кнопки, которые запускаются LeakedActivity. Последний просто показывает AlertDialog в своем методе onCreate(). Вот код:

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        findViewById(R.id.button).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(
                    new Intent(MainActivity.this, LeakedActivity.class));
            }
        });
    }
}

public class LeakedActivity extends Activity {
    private static final int DIALOG_LEAK = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (savedInstanceState == null) {
            showDialog(DIALOG_LEAK);
        }
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        if (id == DIALOG_LEAK) {
            return new AlertDialog.Builder(this)
                .setTitle("Title")
                .setItems(new CharSequence[] { "1", "2" },
                    new OnClickListener() {
                        private final byte[] junk = new byte[10*1024*1024];

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            // nothing
                        }
                    })
                .create();
        }
        return super.onCreateDialog(id);
    }
}

MAT сообщает об утечке этого приложения com.android.internal.app.AlertController$RecycleListView каждый раз, когда AlertDialog отклоняется и LeakedActivity завершается.

Я не могу найти ни одной ошибки в этой маленькой программе. Это выглядит как очень простой случай использования AlertDialog, и он должен хорошо работать, но, похоже, это не так. Поэтому я хотел бы знать, как избежать утечек памяти при использовании AlertDialog с элементами. И почему эта проблема еще не исправлена? Заранее спасибо.

Ответы [ 4 ]

27 голосов
/ 20 августа 2011

(2/12/2012): см. ОБНОВЛЕНИЕ ниже.

Эта проблема на самом деле не вызвана AlertDialog, а больше связана с ListView.Вы можете воспроизвести ту же проблему, используя следующее действие:

public class LeakedListActivity extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Use an existing ListAdapter that will map an array
    // of strings to TextViews
    setListAdapter(new ArrayAdapter<String>(this,
            android.R.layout.simple_list_item_1, mStrings));
    getListView().setOnItemClickListener(new OnItemClickListener() {
        private final byte[] junk = new byte[10*1024*1024];
        @Override
        public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                long arg3) {
        }
    });     
}
    private String[] mStrings = new String[] {"1", "2"};
}

Поверните устройство несколько раз, и вы получите OOM.

У меня нету меня есть время, чтобы узнать больше о том, что является реальной причиной (я знаю что происходит , но не ясно почему это происходит; может быть ошибкой или разработкой).Но вот один обходной путь , который вы можете сделать, по крайней мере, чтобы избежать OOM в вашем случае.

Во-первых, вам нужно сохранить ссылку на утечку AlertDialog.Вы можете сделать это в onCreateDialog().Когда вы используете setItems(), AlertDialog внутренне создаст ListView.И когда вы устанавливаете onClickListener() в своем вызове setItems(), внутренне он будет назначен на ListView onItemClickListener().

Затем в onDestroy() утечки активности установите AlertDialogListView с onItemClickListener() до null, который освобождает ссылку на слушателя и делает любую память, выделенную в этом слушателе, чтобы иметь право на GC.Таким образом, вы не получите OOM.Это просто обходной путь , и реальное решение на самом деле должно быть включено в ListView.

Вот пример кода для вашего onDestroy():

@Override
protected void onDestroy() {
    super.onDestroy();
    if(leakedDialog != null) {
            ListView lv = leakedDialog.getListView();
            if(lv != null)  lv.setOnItemClickListener(null);
    }
}

ОБНОВЛЕНИЕ (2/12/2012): После дальнейшего исследования эта проблема на самом деле не имеет отношения ни к ListView, ни к OnItemClickListener, но к тому факту, что сборщик мусора не происходит немедленно и требует времени длярешить, какие объекты имеют право и готовы к GC.Попробуйте это:

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // this will create reference from button to 
        // the listener which in turn will create the "junk"
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            private byte[] junk = new byte[10*1024*1024];
            @Override
            public void onClick(View v) {
                // do nothing
            }
        });
    }
}

Поверните пару раз, и вы получите OOM.Проблема в том, что после поворота junk все еще сохраняется, потому что GC еще не сделал и не может произойти (если вы используете MAT, вы увидите, что это junk все еще сохраняется слушателем кнопки в глубине отGCroot, и GC потребуется время, чтобы решить, является ли этот junk приемлемым и может ли он быть GCed.) Но в то же время, новый junk должен быть создан после поворота, и из-за мемвыделите размер (10M на мусор), это приведет к OOM.

Решение состоит в том, чтобы прервать любые ссылки на слушателя (владельца junk), в данном случае, с кнопки, которая практически превращает слушателя в GCroot с коротким путем к мусору и делает GCрешите быстрее вернуть ненужную память.Это можно сделать в onDestroy():

@Override
protected void onDestroy() {
    // this will break the reference from the button
    // to the listener (the "junk" owner)
    findViewById(R.id.button).setOnClickListener(null);
    super.onDestroy();
}
1 голос
/ 19 августа 2011

Невозможно воспроизвести для Android 2.3.4 2.3.3.Я проверил ваш точный код на реальном устройстве, и из того, что я вижу в LogCat, размер кучи остается постоянным с течением времени.К сожалению, я не смог hprof-conf мои дампы (ОШИБКА: ожидается 1.0.3)

08-19 08:41:58.026: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 13K, 50% free 2698K/5379K, external 83K/519K, paused 16ms
08-19 08:41:58.056: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1K, 43% free 3720K/6471K, external 83K/519K, paused 18ms
08-19 08:45:30.113: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1076K, 58% free 2723K/6471K, external 595K/1042K, paused 18ms
08-19 08:45:30.143: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 43% free 3740K/6471K, external 507K/1019K, paused 19ms
08-19 08:45:35.869: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1087K, 58% free 2726K/6471K, external 595K/1019K, paused 18ms
08-19 08:45:35.899: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 43% free 3744K/6471K, external 552K/1019K, paused 18ms
08-19 08:45:39.112: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2734K/6471K, external 595K/1019K, paused 17ms
08-19 08:45:39.152: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 43% free 3752K/6471K, external 530K/1019K, paused 20ms
08-19 08:46:14.186: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1089K, 58% free 2736K/6471K, external 595K/1019K, paused 23ms
08-19 08:46:14.216: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 4K, 42% free 3755K/6471K, external 552K/1019K, paused 21ms
08-19 08:46:16.519: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1092K, 58% free 2736K/6471K, external 595K/1019K, paused 23ms
08-19 08:46:16.549: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3753K/6471K, external 530K/1019K, paused 22ms
08-19 08:47:15.686: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1089K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:47:15.716: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 3K, 42% free 3756K/6471K, external 561K/1019K, paused 18ms
08-19 08:48:01.391: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2736K/6471K, external 595K/1019K, paused 19ms
08-19 08:48:01.421: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 43% free 3753K/6471K, external 530K/1019K, paused 19ms
08-19 08:48:09.409: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1089K, 58% free 2737K/6471K, external 758K/1019K, paused 18ms
08-19 08:48:09.449: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 4K, 42% free 3756K/6471K, external 561K/1019K, paused 21ms
08-19 08:48:11.771: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1076K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:48:11.811: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3754K/6471K, external 530K/1019K, paused 20ms
08-19 08:48:13.653: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1089K, 58% free 2737K/6471K, external 595K/1019K, paused 18ms
08-19 08:48:13.683: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 3K, 42% free 3758K/6471K, external 561K/1019K, paused 19ms
08-19 08:48:15.785: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:48:15.825: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3754K/6471K, external 530K/1019K, paused 19ms
08-19 08:48:18.227: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1088K, 58% free 2737K/6471K, external 595K/1019K, paused 19ms
08-19 08:48:18.257: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 4K, 42% free 3756K/6471K, external 552K/1019K, paused 20ms
08-19 08:49:06.575: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1075K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:06.605: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3754K/6471K, external 530K/1019K, paused 17ms
08-19 08:49:09.668: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1097K, 58% free 2729K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:09.708: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 4K, 43% free 3748K/6471K, external 552K/1019K, paused 20ms
08-19 08:49:12.440: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:12.470: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 43% free 3753K/6471K, external 530K/1019K, paused 17ms
08-19 08:49:15.473: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1088K, 58% free 2736K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:15.503: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 4K, 42% free 3756K/6471K, external 561K/1019K, paused 17ms
08-19 08:49:18.476: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1091K, 58% free 2737K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:18.506: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3754K/6471K, external 507K/1019K, paused 20ms
08-19 08:49:21.289: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1090K, 58% free 2737K/6471K, external 595K/1019K, paused 18ms
08-19 08:49:21.319: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3754K/6471K, external 484K/996K, paused 20ms
08-19 08:51:43.307: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1071K, 58% free 2723K/6471K, external 595K/996K, paused 17ms
08-19 08:51:43.338: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed <1K, 43% free 3747K/6471K, external 595K/996K, paused 20ms
08-19 08:51:45.620: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1086K, 58% free 2729K/6471K, external 595K/974K, paused 18ms
08-19 08:51:45.660: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 7K, 43% free 3745K/6471K, external 462K/974K, paused 20ms
08-19 08:51:47.421: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1080K, 58% free 2738K/6471K, external 595K/974K, paused 17ms
08-19 08:51:47.452: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3755K/6471K, external 484K/974K, paused 19ms
08-19 08:52:56.949: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 27K, 58% free 2733K/6471K, external 83K/595K, paused 18ms
08-19 08:52:56.979: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed <1K, 42% free 3757K/6471K, external 83K/595K, paused 17ms
08-19 08:53:01.233: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1072K, 58% free 2727K/6471K, external 595K/1107K, paused 18ms
08-19 08:53:01.274: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 2K, 43% free 3749K/6471K, external 578K/1090K, paused 20ms
08-19 08:53:04.046: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1081K, 58% free 2740K/6471K, external 595K/1064K, paused 18ms
08-19 08:53:04.086: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3758K/6471K, external 530K/1042K, paused 20ms
08-19 08:53:25.948: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1090K, 58% free 2740K/6471K, external 595K/1042K, paused 19ms
08-19 08:53:25.978: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3758K/6471K, external 552K/1042K, paused 19ms
08-19 08:57:51.246: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1099K, 58% free 2753K/6471K, external 595K/1042K, paused 18ms
08-19 08:57:51.286: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 13K, 42% free 3764K/6471K, external 561K/1042K, paused 20ms
08-19 09:00:47.699: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1106K, 58% free 2731K/6471K, external 595K/1019K, paused 18ms
08-19 09:00:47.729: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 7K, 43% free 3748K/6471K, external 484K/996K, paused 17ms
08-19 09:00:56.817: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1080K, 58% free 2741K/6471K, external 595K/996K, paused 18ms
08-19 09:00:56.848: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3758K/6471K, external 530K/996K, paused 23ms
08-19 09:01:00.701: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2739K/6471K, external 595K/996K, paused 19ms
08-19 09:01:00.731: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3756K/6471K, external 530K/996K, paused 22ms
08-19 09:01:25.916: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 1077K, 58% free 2739K/6471K, external 595K/996K, paused 18ms
08-19 09:01:25.946: DEBUG/dalvikvm(3510): GC_FOR_MALLOC freed 6K, 42% free 3756K/6471K, external 530K/996K, paused 20ms
0 голосов
/ 19 августа 2011

Я запустил ваш код, и когда я впервые нажимаю кнопку, он показывает LeakedActivity с диалоговым окном и onClick удаляет диалог, но активность остается на переднем плане с черным экраном.При нажатии клавиши «Назад» и последующем запуске операции показывается исключение ошибки памяти:

ERROR/AndroidRuntime(263): java.lang.OutOfMemoryError

Затем я удалил строку private final byte[] junk = new byte[10*1024*1024]; из кода диалога, после чего такой проблемы не существует .... не знаю почему, если кто-то может выразить это словами, спасибо ему / ей ..

I tested the code in emulator android 2.1
0 голосов
/ 17 августа 2011

Вам необходимо закрыть или отменить диалог из метода onClick, как показано в следующем примере: Диалоги предупреждений в Android

...