Основная активность не является сборщиком мусора после уничтожения, поскольку на нее косвенно ссылается InputMethodManager.

Я следил за статьей «Предотвращение утечек памяти» из здесь.

Однако предлагаемое решение не решает проблему утечки. Я проверил это с помощью эмулятора Android в Windows XP (SDK 2.3.1). Я сбросил кучу и проверил, что основная активность все еще находится в куче (я использовал MAT).

Вот что я сделал:

  1. создать приложение HelloWorld с HelloWorldActivity (у него нет дочерних представлений)
  2. запустите эмулятор и запустите приложение HelloWorld.
  3. закройте его, нажав клавишу «назад».
  4. Вызов gc в DDMS и дамп кучи ‹-- Здесь я нашел экземпляр HelloWorldActivity.
  5. «Путь к корням GC» из него показывает следующий путь.

HelloWorldActivity ‹- PhoneWindow$DecorView ‹- InputMethodManager

InputMethodManager — это синглтон и три ссылки на DecorView, который ссылается на HelloWorldActivity.

Я не могу понять, почему InputMethodManager все еще ссылается на экземпляр DecorView даже после уничтожения активности.

Есть ли способ убедиться, что основное действие уничтожено и может GC после его закрытия?


person hjy    schedule 18.02.2011    source источник
comment
Я проверил это на двух телефонах, и в обоих случаях активность (без переопределения) подвергается GC-редактированию после обратного нажатия.   -  person kupsef    schedule 28.06.2014
comment
ссылка в вопросе на статью не работает. Вот правильный (я предполагаю, что это предполагаемая ссылка в любом случае): android-developers.blogspot.com/2009/01/   -  person Andre Perkins    schedule 02.07.2014


Ответы (3)


Кажется, что вызов методов InputMethodManager «windowDismissed» и «startGettingWindowFocus» делает свое дело.

Что-то вроде этого:

@Override
protected void onDestroy()
{
    super.onDestroy();
    //fix for memory leak: http://code.google.com/p/android/issues/detail?id=34731
    fixInputMethodManager();
}

private void fixInputMethodManager()
{
    final Object imm = getSystemService(Context.INPUT_METHOD_SERVICE);

    final Reflector.TypedObject windowToken
        = new Reflector.TypedObject(getWindow().getDecorView().getWindowToken(), IBinder.class);

    Reflector.invokeMethodExceptionSafe(imm, "windowDismissed", windowToken);

    final Reflector.TypedObject view
        = new Reflector.TypedObject(null, View.class);

    Reflector.invokeMethodExceptionSafe(imm, "startGettingWindowFocus", view);
}

Код рефлектора:

public static final class TypedObject
{
    private final Object object;
    private final Class type;

    public TypedObject(final Object object, final Class type)
    {
    this.object = object;
    this.type = type;
    }

    Object getObject()
    {
        return object;
    }

    Class getType()
    {
        return type;
    }
}

public static void invokeMethodExceptionSafe(final Object methodOwner, final String method, final TypedObject... arguments)
{
    if (null == methodOwner)
    {
        return;
    }

    try
    {
        final Class<?>[] types = null == arguments ? new Class[0] : new Class[arguments.length];
        final Object[] objects = null == arguments ? new Object[0] : new Object[arguments.length];

        if (null != arguments)
        {
            for (int i = 0, limit = types.length; i < limit; i++)
            {
                types[i] = arguments[i].getType();
                objects[i] = arguments[i].getObject();
            }
        }

        final Method declaredMethod = methodOwner.getClass().getDeclaredMethod(method, types);

        declaredMethod.setAccessible(true);
        declaredMethod.invoke(methodOwner, objects);
    }
    catch (final Throwable ignored)
    {
    }
}
person Denis Gladkiy    schedule 27.05.2014
comment
Однако onStop должен быть лучшим кандидатом. - person Snicolas; 05.07.2014
comment
@Snicolas это зависит. onStop — это просто сигнал о том, что ваша активность не видна. onDestroy — это точка выхода активности. Вызов методов может испортить состояние и поведение после того, как onStart формально будет неопределенным. Поэтому нужно много тестировать после размещения обходного пути внутри onStop. - person Denis Gladkiy; 09.07.2014
comment
Спасибо! Однако я думаю, что вызов fixInputMethodManager(); должен произойти до super.onDestroy();. По крайней мере, это сработало для меня. - person Jonas Lüthke; 07.01.2016
comment
Я успешно использовал это исправление в ряде действий. В той, что содержит фрагменты, трюк не сработал. Вам нужно добавить это и в onDestroy фрагментов? - person barq; 22.02.2016
comment
windowDismissed кажется достаточно. Зачем запускать GettingWindowFocus? - person ernazm; 28.03.2017
comment
что такое импорт для Reflector? - person Riddhi Shah; 02.02.2021

Если я правильно понимаю ваш вопрос, ответ таков: нет, вы не можете убедиться, что активность проверена. Должен был быть вызван метод onDestroy() вашей активности, и активность была закрыта. Это, однако, не означает, что процесс убит или что активность собрана; которым управляет система.

person Ted Hopp    schedule 18.02.2011
comment
На самом деле, когда вы спешите или находитесь под давлением, вы склонны следовать рабочему процессу «старт-стоп-hprof-dump». Но не забывайте, что вы не можете знать, когда произойдет GC, следовательно, ваша «утечка» активности может быть собрана за 3 минуты, но опять же, вы проверили дамп кучи без сбора мусора... Это было случай со мной, во всяком случае. - person Bondax; 12.06.2012
comment
вы всегда можете сделать дамп после выполнения GC, это помогает - person redDragonzz; 24.01.2014

Я заметил, что некоторые слушатели, как правило, сохраняют ссылку на действие при некоторых обстоятельствах, даже после того, как действие предположительно было завершено. Поворот из книжной в альбомную может, например, перезапустить вашу активность, и, если вам не повезло, ваша первая активность не будет правильно проверена (в моем случае из-за того, что некоторые слушатели все еще хранят ссылку на нее).

Будучи бывшим программистом на C/C++, я имплантировал его себе в позвоночник, чтобы «отменить установку» любых слушателей в Activity.onDestroy() (setXyzListener(null)).

РЕДАКТИРОВАТЬ:

Так же, как Тед прокомментировал ниже, действительно следует «устанавливать» и «не устанавливать» слушателей в Activity.onResume() и Activity.onPause() соответственно.

person dbm    schedule 18.02.2011
comment
Это правильная идея. Однако, как правило, правильное время для отмены регистрации слушателей — onPause(), а не onDestroy(). (Это также означает, что слушатели должны быть зарегистрированы в onResume(), а не раньше в жизненном цикле.) Почему это так? После того, как onPause() вернется, нет гарантии, что какие-либо дальнейшие обратные вызовы будут выполняться для вашей активности. (То есть ваша активность может быть уничтожена после возврата onPause().) - person Ted Hopp; 18.02.2011
comment
@Ted Hopp: Отличный комментарий! Я изменил свой ответ соответственно. - person dbm; 18.02.2011
comment
@ Тед, спасибо. В моем случае я обнаружил, что InputMethodManager продолжает ссылаться на DecorView активности как на текущий корневой вид даже после его завершения. Обратите внимание, что действие пусто (просто расширяет действие и не переопределяет его), и я не устанавливал для него никаких слушателей. Нужно ли что-то делать, чтобы отменить регистрацию активности в InputMethodManager? Если да, то как я могу это сделать? - person hjy; 19.02.2011
comment
На самом деле я не знаю ничего дополнительно, что вы должны сделать. Как вы упомянули: это пустое действие, вы сами активно не добавляли никаких слушателей или других зависимостей. Каждая существующая зависимость добавляется системой, поэтому я думаю, что справедливо ожидать, что система также удалит эти зависимости. Проверяли ли вы ситуацию в долгосрочной перспективе? Что произойдет, если вы подождете пару минут? Является ли ваша активность GC-ed, скажем, через 5 минут? Что делать, если вы регулярно пользуетесь телефоном/эмулятором? Является ли утечка памяти действительно последовательной утечкой памяти? - person dbm; 19.02.2011