Когда именно это безопасно для утечки для использования (анонимных) внутренних классов?

Я читал некоторые статьи об утечке памяти в Android и смотрел это интересное видео из Google I/O по теме.

Тем не менее, я не совсем понимаю концепцию, и особенно когда она безопасна или опасна для пользователя внутренних классов внутри Activity.

Вот что я понял:

Утечка памяти произойдет, если экземпляр внутреннего класса сохранится дольше, чем его внешний класс (Activity). - > В каких ситуациях это может произойти?

В этом примере, я полагаю, нет никакого риска утечки, потому что анонимный класс, расширяющий OnClickListener, будет жить дольше, чем активность, правильно?

    final Dialog dialog = new Dialog(this);
    dialog.setContentView(R.layout.dialog_generic);
    Button okButton = (Button) dialog.findViewById(R.id.dialog_button_ok);
    TextView titleTv = (TextView) dialog.findViewById(R.id.dialog_generic_title);

    // *** Handle button click
    okButton.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            dialog.dismiss();
        }
    });

    titleTv.setText("dialog title");
    dialog.show();

Теперь, этот пример опасен и почему?

// We are still inside an Activity
_handlerToDelayDroidMove = new Handler();
_handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 10000);

private Runnable _droidPlayRunnable = new Runnable() { 
    public void run() {
        _someFieldOfTheActivity.performLongCalculation();
    }
};

У меня есть сомнение в том, что понимание этой темы связано с пониманием того, что сохраняется, когда действие уничтожается и воссоздается.

Это?

Скажем, я просто изменил ориентацию устройства (что является наиболее распространенной причиной утечек). Когда super.onCreate(savedInstanceState) будет вызываться в моем onCreate(), это восстановит значения полей (как это было до изменения ориентации)? Будет ли это также восстанавливать состояния внутренних классов?

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

Ответ 1

Себастьян,

То, что вы задаете, - довольно сложный вопрос. Хотя вы можете подумать, что это всего лишь один вопрос, вы на самом деле задаете сразу несколько вопросов. Я сделаю все возможное, зная, что я должен его покрыть, и, надеюсь, некоторые другие присоединятся к тому, что я могу пропустить.

Внутренние классы: Введение

Как я не уверен, насколько вам комфортно с ООП в Java, это приведет к паре основ. Внутренний класс - это когда определение класса содержится в другом классе. Существуют в основном два типа: статические и нестатические. Реальная разница между ними:

  • Статические внутренние классы:
    • Рассматриваются "верхний уровень".
    • Не нужно создавать экземпляр содержащего класса.
    • Может не ссылаться на содержащиеся члены класса без явной ссылки.
    • Имейте свою собственную жизнь.
  • Нестатические внутренние классы:
    • Всегда требуется экземпляр содержащего класса, который будет создан.
    • Автоматически иметь неявную ссылку на содержащий экземпляр.
    • Может получить доступ к элементам класса контейнера без ссылки.
    • Предполагается, что срок службы не должен превышать срок службы контейнера.

Сбор мусора и нестатические внутренние классы

Сбор мусора является автоматическим, но пытается удалить объекты на основе того, думает ли он, что они используются. Сборщик мусора довольно умный, но не безупречный. Он может только определить, используется ли что-то, независимо от того, активна ли ссылка на объект.

Реальная проблема здесь в том, что нестатический внутренний класс был сохранен дольше, чем его контейнер. Это связано с неявной ссылкой на содержащий класс. Единственный способ, которым это может произойти, - это если объект за пределами содержащего класса сохраняет ссылку на внутренний объект без учета содержащего объекта.

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

Важнейшим аспектом этой реализации является то, что не имеет значения, находится ли она в Activity или является ли она пригодной. Вы всегда должны быть методичными при использовании нестатических внутренних классов и следите за тем, чтобы они никогда не переживали объекты контейнера. К счастью, если это не основной объект вашего кода, утечки могут быть небольшими по сравнению. К сожалению, это самые трудные утечки, потому что они, вероятно, останутся незамеченными, пока многие из них не просочились.

Решения: нестатические внутренние классы

  • Получить временные ссылки из объекта-объекта.
  • Разрешить содержащему объекту быть единственным, кто сохранит долгоживущие ссылки на внутренние объекты.
  • Используйте установленные шаблоны, например Factory.
  • Если внутренний класс не требует доступа к содержащимся членам класса, подумайте о его превращении в статический класс.
  • Использовать с осторожностью, независимо от того, находится ли она в действии или нет.

Действия и представления: Введение

Мероприятия содержат много информации для запуска и отображения. Действия определяются характеристикой, что у них должен быть вид. У них также есть определенные автоматические обработчики. Указываете ли вы это или нет, Activity имеет неявную ссылку на содержащуюся в нем View.

Чтобы созданный вид был создан, он должен знать, где его создать, и есть ли у него какие-либо дети, чтобы он мог отображаться. Это означает, что каждый вид имеет ссылку на Activity (через getContext()). Более того, каждый просмотр сохраняет ссылки на своих детей (т.е. getChildAt()). Наконец, каждый просмотр сохраняет ссылку на визуализированный растровый файл, который представляет его отображение.

Всякий раз, когда у вас есть ссылка на Activity (или контекст активности), это означает, что вы можете следовать за цепью ENTIRE вниз по иерархии компоновки. Вот почему утечка памяти в отношении "Деятельности" или "Виды" - вот такая огромная сделка. Это может быть утечка тонны всей памяти.

Действия, представления и нестатические внутренние классы

Учитывая информацию выше о внутренних классах, это наиболее распространенные утечки памяти, но также наиболее часто избегаемые. Хотя желательно, чтобы внутренний класс имел прямой доступ к членам класса "Деятельность", многие из них готовы сделать их статическими, чтобы избежать потенциальных проблем. Проблема с действиями и представлениями намного глубже, чем это.

Пропущенные действия, представления и контексты активности

Все сводится к контексту и жизненному циклу. Существуют определенные события (например, ориентация), которые будут убивать контекст активности. Поскольку для многих классов и методов требуется контекст, разработчики иногда пытаются сохранить некоторый код, захватив ссылку на контекст и удерживая его. Так получилось, что многие объекты, которые мы должны создать для запуска нашей деятельности, должны существовать вне цикла Life Activity, чтобы позволить Activity делать то, что нужно. Если у какого-либо из ваших объектов есть ссылка на Activity, его Контекст или любой из его представлений, когда он был уничтожен, вы просто просочились из этой Activity и всего дерева View.

Решения: действия и представления

  • Избегайте, во что бы то ни стало, статическую ссылку на представление или активность.
  • Все ссылки на контексты активности должны быть короткими (продолжительность функции)
  • Если вам нужен долговечный контекст, используйте контекст приложения (getBaseContext() или getApplicationContext()). Они не поддерживают ссылки неявно.
  • В качестве альтернативы вы можете ограничить уничтожение Activity путем переопределения изменений конфигурации. Однако это не останавливает другие потенциальные события от уничтожения Activity. Хотя вы можете это сделать, вы все равно можете обратиться к вышеуказанным практикам.

Runnables: Введение

Runnables на самом деле не так уж плохи. Я имею в виду, они могут быть, но на самом деле мы уже попали в большинство опасных зон. Runnable - это асинхронная операция, выполняющая задачу независимо от потока, на котором она была создана. Большинство runnables создаются из потока пользовательского интерфейса. По сути, использование Runnable создает другой поток, чуть более управляемый. Если вы классифицируете Runnable как стандартный класс и следуете приведенным выше рекомендациям, вы должны столкнуться с несколькими проблемами. Реальность такова, что многие разработчики этого не делают.

Из-за непринужденности, удобочитаемости и логического потока программы многие разработчики используют Анонимные Внутренние Классы для определения своих Runnables, таких как пример, который вы создали выше. Это приводит к примеру, подобному тому, который вы набрали выше. Анонимный внутренний класс - это, в основном, дискретный нестатический внутренний класс. Вам просто не нужно создавать совершенно новое определение и просто переопределять соответствующие методы. Во всех других отношениях это нестатический внутренний класс, что означает, что он сохраняет неявную ссылку на свой контейнер.

Runnables and Activities/Views

Ура! Этот раздел может быть коротким! В связи с тем, что Runnables работают за пределами текущего потока, опасность для них связана с длительными асинхронными операциями. Если runnable определен в Activity или View как анонимный внутренний класс или нестатический внутренний класс, существуют некоторые очень серьезные опасности. Это связано с тем, что, как было сказано ранее, имеет, чтобы узнать, кто его контейнер. Введите изменение ориентации (или систему). Теперь просто вернитесь к предыдущим разделам, чтобы понять, что только что произошло. Да, ваш пример весьма опасен.

Решения: Runnables

  • Попробуйте расширить Runnable, если он не нарушит логику вашего кода.
  • Сделайте все возможное, чтобы сделать расширенные Runnables статическими, если они должны быть внутренними классами.
  • Если вы должны использовать Анонимные Runnables, не создавайте их в любом объекте, который имеет долговечную ссылку на активную или Просмотр, которая используется.
  • Многие Runnables могли так же легко быть AsyncTasks. Рассмотрите возможность использования AsyncTask, поскольку по умолчанию управляются VM.

Отвечая на окончательный вопрос Теперь, чтобы ответить на вопросы, которые не были напрямую решены другими разделами этого сообщения. Вы спросили: "Когда объект внутреннего класса выживет дольше, чем его внешний класс?" Прежде чем мы дойдем до этого, позвольте мне еще раз подчеркнуть: хотя вы правы, чтобы беспокоиться об этом в действиях, это может вызвать утечку в любом месте. Я приведу простой пример (без использования Activity) только для демонстрации.

Ниже приведен общий пример базового factory (отсутствует код).

public class LeakFactory
{//Just so that we have some data to leak
    int myID = 0;
// Necessary because our Leak class is non-static
    public Leak createLeak()
    {
        return new Leak();
    }

// Mass Manufactured Leak class
    public class Leak
    {//Again for a little data.
       int size = 1;
    }
}

Это не такой распространенный пример, но достаточно простой, чтобы продемонстрировать. Ключевым здесь является конструктор...

public class SwissCheese
{//Can't have swiss cheese without some holes
    public Leak[] myHoles;

    public SwissCheese()
    {//Gotta have a Factory to make my holes
        LeakFactory _holeDriller = new LeakFactory()
    // Now, let get the holes and store them.
        myHoles = new Leak[1000];

        for (int i = 0; i++; i<1000)
        {//Store them in the class member
            myHoles[i] = _holeDriller.createLeak();
        }

    // Yay! We're done! 

    // Buh-bye LeakFactory. I don't need you anymore...
    }
}

Теперь у нас есть утечки, но нет Factory. Несмотря на то, что мы выпустили Factory, он останется в памяти, потому что у каждого Leak есть ссылка на него. Даже не имеет значения, что внешний класс не имеет данных. Это происходит гораздо чаще, чем можно было бы подумать. Нам не нужен создатель, просто его творения. Поэтому мы создаем один временно, но используем творения бесконечно.

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

public class SwissCheese
{//Can't have swiss cheese without some holes
    public Leak[] myHoles;

    public SwissCheese()
    {//Now, let get the holes and store them.
        myHoles = new Leak[1000];

        for (int i = 0; i++; i<1000)
        {//WOW! I don't even have to create a Factory... 
        // This is SOOOO much prettier....
            myHoles[i] = new LeakFactory().createLeak();
        }
    }
}

Теперь, каждый из этих новых LeakFactories только что просочился. Что вы думаете об этом? Это два очень распространенных примера того, как внутренний класс может пережить внешний класс любого типа. Если бы этот внешний класс был Управлением, представьте, насколько он был бы хуже.

Заключение

Они перечисляют прежде всего известные опасности использования этих объектов ненадлежащим образом. В общем, этот пост должен был охватить большинство ваших вопросов, но я понимаю, что это была должность loooong, поэтому, если вам нужно разъяснение, просто дайте мне знать. Пока вы будете следовать вышеуказанным практикам, вы будете очень мало беспокоиться об утечке.

Надеюсь, что это поможет,

FuzzicalLogic