Вы задаете довольно сложный вопрос. Хотя вы можете подумать, что это всего лишь один вопрос, на самом деле вы задаете сразу несколько вопросов. Я сделаю все возможное, зная, что я должен это осветить, и, надеюсь, некоторые другие присоединятся, чтобы осветить то, что я могу пропустить.
Вложенные классы: введение
Поскольку я не уверен, насколько вам комфортно с ООП в Java, это затронет пару основ. Вложенный класс - это когда определение класса содержится в другом классе. В основном есть два типа: статические вложенные классы и внутренние классы. Настоящая разница между ними:
- Static Nested Classes:
- Are considered "top-level".
- Не требует создания экземпляра содержащего класса.
- Не может ссылаться на содержащиеся члены класса без явной ссылки.
- Имейте свою жизнь.
- Inner Nested Classes:
- Always require an instance of the containing class to be constructed.
- Автоматически иметь неявную ссылку на содержащий экземпляр.
- Может получить доступ к членам класса контейнера без ссылки.
- Срок службы предполагается не должен превышать срок службы контейнера.
Сборка мусора и внутренние классы
Сборка мусора выполняется автоматически, но пытается удалить объекты в зависимости от того, считает ли он, что они используются. Сборщик мусора довольно умен, но не безупречен. Он может только определить, используется ли что-либо, по наличию активной ссылки на объект.
Настоящая проблема здесь в том, что внутренний класс сохраняется дольше, чем его контейнер. Это из-за неявной ссылки на содержащий класс. Единственный способ, которым это может произойти, - это если объект за пределами содержащего класса сохраняет ссылку на внутренний объект без учета содержащего объекта.
Это может привести к ситуации, когда внутренний объект жив (через ссылку), но ссылки на содержащий объект уже удалены из всех других объектов. Следовательно, внутренний объект поддерживает содержащий объект, потому что он всегда будет иметь ссылку на него. Проблема в том, что, если он не запрограммирован, нет возможности вернуться к содержащему объекту, чтобы проверить, жив ли он.
Самым важным аспектом этого осознания является то, что не имеет значения, находится ли он в Activity или является доступным для рисования. Вы должны всегда быть методичными при использовании внутренних классов и следить за тем, чтобы они никогда не пережили объекты контейнера. К счастью, если это не основной объект вашего кода, утечки могут быть небольшими по сравнению. К сожалению, это одни из самых трудных для поиска утечек, потому что они, вероятно, останутся незамеченными, пока многие из них не выйдут.
Решения: внутренние классы
- Получить временные ссылки от содержащего объекта.
- Разрешить содержащему объекту быть единственным, который сохраняет долгоживущие ссылки на внутренние объекты.
- Используйте установленные шаблоны, такие как Factory.
- Если внутренний класс не требует доступа к содержащим его членам класса, подумайте о том, чтобы превратить его в статический класс.
- Используйте с осторожностью, независимо от того, находится ли он в действии или нет.
Действия и просмотры: введение
Действия содержат много информации, которую можно запускать и отображать. Действия определяются характеристикой, согласно которой они должны иметь представление. У них также есть определенные автоматические обработчики. Независимо от того, указываете вы это или нет, Activity имеет неявную ссылку на View, который он содержит.
Чтобы представление было создано, оно должно знать, где его создать и есть ли у него дочерние элементы, чтобы его можно было отображать. Это означает, что каждое представление имеет ссылку на действие (через getContext()
). Более того, каждое представление хранит ссылки на своих дочерних элементов (т.е. getChildAt()
). Наконец, каждое представление хранит ссылку на обработанное растровое изображение, которое представляет его отображение.
Всякий раз, когда у вас есть ссылка на действие (или контекст действия), это означает, что вы можете следовать по ВСЕЙ цепочке вниз по иерархии макета. Вот почему утечки памяти относительно действий или представлений так важны. Это может быть сразу тонна утечки памяти.
Действия, представления и внутренние классы
С учетом приведенной выше информации о внутренних классах, это наиболее частые утечки памяти, но их также чаще всего избегают. Хотя желательно, чтобы внутренний класс имел прямой доступ к членам класса Activity, многие хотят просто сделать их статическими, чтобы избежать потенциальных проблем. Проблема с Activity и Views гораздо глубже.
Просочившиеся действия, просмотры и контексты действий
Все сводится к контексту и жизненному циклу. Есть определенные события (например, ориентация), которые убивают контекст активности. Поскольку для очень многих классов и методов требуется контекст, разработчики иногда пытаются сохранить некоторый код, захватывая ссылку на контекст и удерживая ее. Так уж случилось, что многие из объектов, которые мы должны создать для запуска нашей Activity, должны существовать вне жизненного цикла Activity, чтобы позволить Activity делать то, что ей нужно. Если какой-либо из ваших объектов имеет ссылку на Activity, его контекст или любое из его представлений при его уничтожении, вы только что утекли в это действие и все его дерево представлений.
Решения: действия и просмотры
- Любой ценой избегайте статических ссылок на View или Activity.
- Все ссылки на контексты деятельности должны быть недолговечными (продолжительность функции).
- Если вам нужен долговечный контекст, используйте контекст приложения (
getBaseContext()
или getApplicationContext()
). Они не сохраняют ссылки неявно.
- В качестве альтернативы вы можете ограничить уничтожение Activity, отменив изменения конфигурации. Однако это не останавливает другие потенциальные события от разрушения Activity. Хотя вы можете это сделать, вы все же можете воспользоваться описанными выше методами.
Runnables: введение
Runnables на самом деле не так уж и плохи. Я имею в виду, что они могли быть, но на самом деле мы уже достигли большинства опасных зон. Runnable - это асинхронная операция, которая выполняет задачу независимо от потока, в котором она была создана. Большинство исполняемых файлов создаются из потока пользовательского интерфейса. По сути, использование Runnable создает еще один поток, чуть более управляемый. Если вы классифицируете Runnable как стандартный класс и следуете приведенным выше рекомендациям, у вас должно возникнуть несколько проблем. На самом деле многие разработчики этого не делают.
Из-за простоты, удобочитаемости и логичности программы многие разработчики используют анонимные внутренние классы для определения своих Runnables, как в примере, который вы создали выше. В результате получается пример, подобный тому, который вы ввели выше. Анонимный внутренний класс - это, по сути, дискретный внутренний класс. Вам просто не нужно создавать совершенно новое определение и просто переопределять соответствующие методы. Во всем остальном это внутренний класс, что означает, что он сохраняет неявную ссылку на свой контейнер.
Выполняемые файлы и действия / просмотры
Ура! Этот раздел может быть коротким! Из-за того, что Runnables запускаются вне текущего потока, они опасны для длительных асинхронных операций. Если runnable определен в Activity или View как анонимный внутренний класс ИЛИ вложенный внутренний класс, существует несколько очень серьезных опасностей. Это связано с тем, что, как указывалось ранее, он должен знать, кем является его контейнер. Введите изменение ориентации (или уничтожение системы). Теперь просто вернитесь к предыдущим разделам, чтобы понять, что только что произошло. Да, ваш пример довольно опасен.
Решения: исполняемые файлы
- Попробуйте расширить Runnable, если это не нарушает логику вашего кода.
- Постарайтесь сделать расширенные Runnables статическими, если они должны быть вложенными классами.
- Если вам необходимо использовать анонимные запускаемые объекты, не создавайте их в любом объекте, который имеет долгоживущие ссылки на используемое действие или представление.
- Многие Runnables с такой же легкостью могли бы быть AsyncTasks. Рассмотрите возможность использования AsyncTask, поскольку по умолчанию они управляются виртуальными машинами.
Ответ на последний вопрос. Теперь ответим на вопросы, которые не были напрямую рассмотрены в других разделах этого сообщения. Вы спросили: «Когда объект внутреннего класса может прожить дольше, чем его внешний класс?» Прежде чем мы перейдем к этому, позвольте мне еще раз подчеркнуть: хотя вы правы, что беспокоитесь об этом в действиях, это может вызвать утечку где угодно. Я приведу простой пример (без использования 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...
}
}
Теперь у нас есть утечки, но нет Factory. Несмотря на то, что мы выпустили Factory, она останется в памяти, потому что каждая утечка имеет на нее ссылку. Даже неважно, что у внешнего класса нет данных. Это происходит гораздо чаще, чем можно подумать. Нам не нужен создатель, нам нужны только его творения. Итак, мы создаем его временно, но используем творения бесконечно.
Представьте, что произойдет, если мы немного изменим конструктор.
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 только что просочились. Что вы думаете об этом? Это два очень распространенных примера того, как внутренний класс может пережить внешний класс любого типа. Если бы этот внешний класс был Activity, представьте, насколько он был бы хуже.
Заключение
В них перечислены в первую очередь известные опасности ненадлежащего использования этих объектов. В общем, этот пост должен был охватывать большинство ваших вопросов, но я понимаю, что это был длинный пост, поэтому, если вам нужны разъяснения, просто дайте мне знать. Если вы будете следовать описанным выше методам, вы не будете беспокоиться об утечке.
person
Community
schedule
10.06.2012