То, что вы спрашиваете, довольно сложный вопрос. Хотя вы можете думать, что это всего лишь один вопрос, вы на самом деле задаете несколько вопросов одновременно. Я приложу все усилия, зная, что я должен покрыть это, и, надеюсь, некоторые другие присоединятся, чтобы покрыть то, что я могу пропустить.
Вложенные классы: введение
Поскольку я не уверен, насколько вам удобно с ООП в Java, это затронет несколько основ. Вложенный класс - это когда определение класса содержится в другом классе. Существует два основных типа: статические вложенные классы и внутренние классы. Реальная разница между ними:
- Статические вложенные классы:
- Считаются "на высшем уровне".
- Не требуйте, чтобы экземпляр содержащего класса был создан.
- Не может ссылаться на содержащиеся члены класса без явной ссылки.
- Есть своя жизнь.
- Внутренние вложенные классы:
- Всегда требуйте, чтобы экземпляр содержащего класса был создан.
- Автоматически иметь неявную ссылку на содержащий экземпляр.
- Может получить доступ к членам класса контейнера без ссылки.
- Срок службы должен быть не дольше, чем у контейнера.
Сборка мусора и внутренние классы
Сборка мусора происходит автоматически, но пытается удалить объекты в зависимости от того, считает ли она, что они используются. Сборщик мусора довольно умен, но не безупречен. Он может только определить, используется ли что-то, существует ли активная ссылка на объект.
Реальная проблема здесь в том, что внутренний класс остается живым дольше, чем его контейнер. Это из-за неявной ссылки на содержащий класс. Единственный способ, которым это может произойти, - это если объект за пределами содержащего класса сохраняет ссылку на внутренний объект, безотносительно к содержащемуся объекту.
Это может привести к ситуации, когда внутренний объект жив (посредством ссылки), но ссылки на содержащий объект уже удалены из всех других объектов. Внутренний объект, следовательно, поддерживает вмещающий объект, потому что он всегда будет иметь ссылку на него. Проблема заключается в том, что, если он не запрограммирован, невозможно вернуться к содержащемуся объекту, чтобы проверить, жив ли он.
Наиболее важным аспектом этой реализации является то, что не имеет значения, находится ли она в Деятельности или является доступной для рисования. Вам всегда нужно быть методичным при использовании внутренних классов и следить за тем, чтобы они никогда не переживали объекты контейнера. К счастью, если это не основной объект вашего кода, утечки могут быть небольшими по сравнению. К сожалению, это некоторые из самых трудных утечек, которые можно найти, потому что они, вероятно, останутся незамеченными, пока многие из них не протекут.
Решения: внутренние классы
- Получить временные ссылки из содержащего объекта.
- Разрешить содержащему объекту быть единственным, чтобы хранить долгоживущие ссылки на внутренние объекты.
- Используйте установленные шаблоны, такие как Фабрика.
- Если внутренний класс не требует доступа к содержащим его членам класса, рассмотрите возможность превращения его в статический класс.
- Используйте с осторожностью, независимо от того, находится ли он в действии или нет.
Деятельность и взгляды: Введение
Действия содержат много информации, чтобы иметь возможность запускать и отображать. Действия определяются характеристикой, которая должна иметь представление. У них также есть определенные автоматические обработчики. Независимо от того, указали вы это или нет, у Activity есть неявная ссылка на содержащееся в ней представление.
Чтобы создать представление, оно должно знать, где его создать и есть ли у него дочерние элементы, чтобы его можно было отобразить. Это означает, что каждое представление имеет ссылку на действие (через getContext()
). Более того, каждый View хранит ссылки на своих потомков (то есть getChildAt()
). Наконец, каждое представление хранит ссылку на отображаемое растровое изображение, которое представляет его отображение.
Всякий раз, когда у вас есть ссылка на действие (или контекст действия), это означает, что вы можете следовать всей цепочке вниз по иерархии макета. Вот почему утечки памяти в отношении действий или представлений так важны. Это может быть тонна утечки памяти за один раз.
Деятельность, представления и внутренние классы
Учитывая приведенную выше информацию о внутренних классах, это наиболее распространенные утечки памяти, но их также чаще всего избегают. Хотя желательно, чтобы внутренний класс имел прямой доступ к членам класса «Деятельности», многие хотят просто сделать их статичными, чтобы избежать потенциальных проблем. Проблема с деятельностью и представлениями гораздо глубже.
Утечка активности, просмотров и контекстов активности
Все сводится к контексту и жизненному циклу. Существуют определенные события (например, ориентация), которые убивают контекст действия. Так как для многих классов и методов требуется контекст, разработчики иногда пытаются сохранить некоторый код, захватывая ссылку на контекст и удерживая его. Просто так получилось, что многие объекты, которые мы должны создать, чтобы запустить наше действие, должны существовать вне жизненного цикла действия, чтобы позволить действию делать то, что ему нужно. Если какой-либо из ваших объектов, когда он уничтожен, имеет ссылку на Activity, ее Context или какой-либо из его Views, вы только что слили эту Activity и все дерево View.
Решения: деятельность и взгляды
- Любой ценой избегайте статических ссылок на представление или действие.
- Все ссылки на контексты деятельности должны быть кратковременными (продолжительность функции)
- Если вам нужен долгоживущий контекст, используйте контекст приложения (
getBaseContext()
или getApplicationContext()
). Они не хранят ссылки неявно.
- Кроме того, вы можете ограничить уничтожение Действия, переопределив Изменения конфигурации. Однако это не мешает другим потенциальным событиям разрушить Деятельность. Хотя вы можете сделать это, вы все равно можете обратиться к вышеупомянутым практикам.
Runnables: Введение
Runnables на самом деле не так уж и плохо. Я имею в виду, они могли бы быть, но на самом деле мы уже поразили большинство опасных зон. Runnable - это асинхронная операция, которая выполняет задачу независимо от потока, в котором она была создана. Большинство исполняемых объектов создаются из потока пользовательского интерфейса. По сути, использование Runnable создает другой поток, чуть более управляемый. Если вы классифицируете Runnable как стандартный класс и следуете приведенным выше рекомендациям, у вас должно быть несколько проблем. Реальность такова, что многие разработчики не делают этого.
Из-за простоты, удобочитаемости и логического выполнения программы многие разработчики используют анонимные внутренние классы для определения своих Runnables, как, например, в примере, который вы создали выше. Это приводит к примеру, подобному тому, который вы напечатали выше. Анонимный внутренний класс - это в основном дискретный внутренний класс. Вам просто не нужно создавать совершенно новое определение и просто переопределять соответствующие методы. Во всех других отношениях это внутренний класс, что означает, что он хранит неявную ссылку на свой контейнер.
Runnables и Действия / Представления
Ура! Этот раздел может быть коротким! Из-за того, что Runnables работают вне текущего потока, опасность с ними связана с длительными асинхронными операциями. Если выполняемый объект определен в Деятельности или Представлении как Анонимный Внутренний Класс ИЛИ вложенный Внутренний Класс, существуют некоторые очень серьезные опасности. Это потому, что, как уже говорилось, он должен знать, кто его контейнер. Введите изменение ориентации (или уничтожение системы). Теперь просто вернитесь к предыдущим разделам, чтобы понять, что только что произошло. Да, ваш пример довольно опасен.
Решения: Runnables
- Попробуйте расширить Runnable, если он не нарушает логику вашего кода.
- Сделайте все возможное, чтобы сделать расширенные Runnables статичными, если они должны быть вложенными классами.
- Если вам необходимо использовать Anonymous Runnables, избегайте их создания в любом объекте, который имеет долговременную ссылку на используемое действие или представление.
- Многие Runnables также легко могли быть AsyncTasks. Подумайте об использовании AsyncTask, поскольку по умолчанию это VM Managed.
Ответ на последний вопрос
Теперь ответим на вопросы, которые не были напрямую рассмотрены в других разделах этого поста. Вы спросили: «Когда объект внутреннего класса может выжить дольше, чем его внешний класс?» Прежде чем мы вернемся к этому, позвольте мне еще раз подчеркнуть: хотя вы и вправе беспокоиться об этом в разделе «Действия», это может привести к утечке в любом месте. Я приведу простой пример (без использования Activity) просто для демонстрации.
Ниже приведен типичный пример базовой фабрики (отсутствует код).
public class LeakFactory
{//Just so that we have some data to leak
int myID = 0;
// Necessary because our Leak class is an Inner class
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's 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...
}
}
Теперь у нас есть утечки, но нет фабрики. Несмотря на то, что мы выпустили Фабрику, она останется в памяти, потому что каждая утечка имеет ссылку на нее. Даже не имеет значения, что у внешнего класса нет данных. Это происходит гораздо чаще, чем можно подумать. Нам не нужен создатель, только его творения. Таким образом, мы создаем его временно, но используем творения бесконечно.
Представьте, что происходит, когда мы слегка меняем конструктор.
public class SwissCheese
{//Can't have swiss cheese without some holes
public Leak[] myHoles;
public SwissCheese()
{//Now, let's 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 только что просочился. Что вы думаете об этом? Это два очень распространенных примера того, как внутренний класс может пережить внешний класс любого типа. Если бы этот внешний класс был Деятельностью, представьте, насколько хуже было бы.
Вывод
В них перечислены наиболее известные опасности использования этих объектов ненадлежащим образом. В целом, этот пост должен был охватить большинство ваших вопросов, но я понимаю, что это был недолговечный пост, поэтому, если вам нужны разъяснения, просто дайте мне знать. Пока вы будете следовать вышеупомянутым методам, вы будете очень мало беспокоиться о утечке.