Как стать автором
Обновить

Комментарии 36

Шикарная статья. Спасибо!
Действительно шикарная, и комикс порадовал.
Статья про утечки памяти и ни слова про valgrind. Хм…
Да, в статье рассказывается только про поиск «утечек» java-объектов.
В начале статьи я написал про это, но, возможно, недостаточно четко.
Для людей делающих все из консоли или для любителей автоматизировать (требуется root доступ):
Можно получить *.hprof дамп сделав «adb shell kill -10 » для вашего dalvikvm-процесса. Стянув дамп из /data/misc к себе на компьютер с помощью adb pull его можно поизучать даже с помощью стандарной jvisualvm (standalone-приложение, кажется идет вместе с JDK, не требудет установки эклипса).
Статья, бесспорно, полезная для новичков.

Но я бы половину текста заменил 3-мя пунктами:
1. Придерживайтесь принципов ООП.
2. Используйте инструменты (классы, менеджеры, механизмы), предоставленные Android SDK.
3. Прочтите какую-нибудь книгу про «чистый код».

Например:
— Абстракция и инкапсуляция в кУпе с различными менеджерами SDK не дадут образоваться большей части описанных утечек.
— Использование локальных переменных вместо полей класса значительно облегчит работу сборщика мусора.
— …
И таких моментов много, которые следуют из основ. Читайте книги, они очень полезны.

И еще «4. питайтесь правильно».

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

Говоря про механизмы Android SDK тоже надо понимать, некоторые механизмы оказываются практически бесполезными для задач для которых они предназначались (у меня в голове пример с AsyncTask), так что для упрощения некоторых задач приходится использовать сторонние механизмы.
ООП бесполезно? Нуну. А как же классика в лице утечки через Context? Стоит ли мне рассказывать как много утечек связано с незнанием принципа наследования и использования в дальнейшем контекста активити в получении чего-либо?

инструменты SDK не гарантируют вам отсутствие утечек памяти

Вам вообще ничего не может гарантировать их отсутствие, как и это статья. Так стоит ли об этом говорить вообще?
Минусующие, вы не на GeekTimes, умейте аргументировать свои доводы, особенно в такой важной теме. А коль не знаете что сказать, то лучше и мышку не клацать, ибо ваше анонимное мнение может быть ошибочно воспринято другими.
Вам вообще ничего не может гарантировать их отсутствие, как и это статья. Так стоит ли об этом говорить вообще?

Жизнь вообще бессмысленная штука :)

Но вы правы — ничто не может гарантировать отсутствие ошибок. Даже если вы прекрасно понимаете всю теорию, вы все равно будете иногда делать опечатки или просто забывать про какие-то моменты.

Именно поэтому я рекомендую перед релизом приложения один раз пройтись по всему приложению и отловить все такие ошибки с помощью алгоритма, приведенного в конце статьи. Это занимает достаточно много времени, но это полезная практика. А теория — она больше для того, чтобы понимать что нужно искать.
MAT безусловно лучший инструмент которым можно отлавливать утечки и я трёмя руками за него, потому как и сам его активно использую. Правда я бы порекомендовал перед этим пройтись статическим анализатором кода, например Lint или FindBugs. Они порой указывают на места в коде с проблемами, при этом приводят текст проблемы. Бывает очень полезно.
Вы наверное как-то шире чем я трактуете слово ООП. Лично я не увидел какого-то нарушения ООП в примерах с утечками памяти. Речь идет о сохранении ссылок о которых разработчик может забывать или даже не подозревать, а не о таких вещах как инкапсуляция или полиморфизм.

Вам вообще ничего не может гарантировать их отсутствие, как и это статья. Так стоит ли об этом говорить вообще?

Ну в этой статье есть во первых рекомендации по работе с инструментами по поиску утечек. Во-вторых более-менее типичные проблемы с утечками в Android UI программировании. Наверное классика утечек через Context тоже могла бы быть здесь.
Никто и не говорил о нарушениях ООП. Говорили о
Но я бы половину текста заменил 3-мя пунктами:
1. Придерживайтесь принципов ООП.


[quote]Речь идет о сохранении ссылок о которых разработчик может забывать или даже не подозревать[/quote]
О чем речь идет в принципе понятно. Просто это база для людей. Да, материал неплохой, но он содержит рекомендации для новичков, при этом говорит о MAT, который в свою очередь анализирует хип памяти, а эта тема уже далеко не для новичков.

И да, я не придираюсь, я просто заметил что ООП тут «причем». Инкапсуляции и полиморфизма тут нет, но вот наследование есть. Народ привык передавать контекст в методы и их не парит что переданная Activity или контекст активности = сама Activity. Нуачо, работает же.
Я не писал про гарантии и панацею. И я ни в коем случае не отрицал полезность тулзов для мониторинга памяти и статьи в целом.
Но проблемы с лишними ссылками на объекты (коим посвящено >50% статьи) возникают именно по причинам не следования принципам ООП.
Конечно же есть много других причин утечек памяти. Но именно описанные в статье проблемы решаются продуманной архитектурой и знанием инструментов SDK.

Никогда не сохраняйте ссылки на activity (view, fragment, service) в статических переменных

Думаю что нужно просто очищать (=null) эту ссылку в onDestroy(). После вызова onDestroy() activity становится не валидной (т.е. не живой), поэтому ссылку в любом случае правильно было бы инвалидировать чтобы никто не воспользовался.
не рекомендую полагаться на это
Если пройдете дальше по вашим ссылкам то увидете что полагаться вполне можно. onDestroy не вызовется если Android убьет процесс который хостит Activity — в этом случае беспокоится об утечках памяти не стоит. onDestory не рекомендуют для сохранения данных.

Впрочем я не настаиваю на onDestroy(). Если ссылка на activity нужна только когда activity видима можно управлять ею в onResume/OnPause, onStart/onStop. Однако пока выглядит так что обнуление ссылкив onDestroy() решит проблему утечек памяти.
Приличных размеров статья о том, как избегать проблем с менеджментом памяти в языке, который создан чтобы упростить менеджмент памяти.
Все таки ИМХО куда проще писать на языке без GC, где всегда четко видно время жизни объекта и для избегания утечек достаточно придерживаться только одного правила: объект должен быть удален там же, где был создан.
> Приличных размеров статья о том, как избегать проблем с менеджментом памяти в языке, который создан чтобы упростить менеджмент памяти.

Упростить != сделать неважным.
Представьте сколько способов накосячить в плюсах…
Но при этом все возможные способы покрываются одним коротким правилом, которые я выше написал. :)
А о факте утечек можно узнать просто подключив профайлер, который при закрытии приложения укажет на все участки не освобожденной памяти.
Пожалуй первый за долгое время блог компании, стартовавший отличным постом. Спасибо и так держать.
Один из первых вопросов, с которым сталкивается каждый начинающий разработчик, это как передать объект из одного activity в следующий.
Используйте передачу объектов через Intent, либо вообще передавайте не объект, а id объекта (если у вас есть база данных, из которой этот id потом можно достать)

Каким же образом поступать, если надо передать сложный несериализуемый объект? Имхо, из-за невозможности сделать это простым, очевидным способом и растут кучи костылей.
если у вас сложный несериализуемый объект, то как поведёт себя приложение при принудительной его выгрузке и перезапуске потом?
Создается заново при каждом открытии приложения. Пример такого объекта менеджер закачек, кэширования и прочего. Служит для того чтобы не запрашивать много кратно одни и те-же данные, но так же не держать их устаревшими.
зачем передавать объект? хороший пример тут — наличие менеджера, где хранить эти сложные объекты. а между Activity передавать ID объектов.
Менеджер тоже объект и его тоже надо передавать между Activity.
Этот манагер как раз может быть статическим с виклинками.
Сразу вспоминается фабрика абстрактных фабрик.
Благодаря вашей статье увидел, что реклама от Гугла очень «течет» у меня в приложении.
Что было

image


Оказалось достаточно было в onStop удалять adView
Вот так
AdView adView = (AdView) findViewById(R.id.adView);
adView.destroy();
adView.removeView(adView);


И после повторных тестов утечек уже не было!

Я бы, даже не подумал туда посмотреть, если бы не вы.
Спасибо за отличный пост!
который продолжает существовать после того, как он должен быть уничтожен
после того, как он перестал быть нужным.
А для Android Studio (IDEA) есть тулза, аналогичная MAT?
Цель — получить список ссылок на тот или иной объект.
На данный момент плагинов для Android Studio, решающих эту задачу, нет.
Поставьте MAT как отдельную программу и воспользуйтесь инструкцией со stackoverflow.

Либо поставьте Eclipse с ADT и MAT и воспользуйтесь им. Вам даже проект импортировать в eclipse не потребуется.
Я открыл приложение и начал прокликивать экраны, ожидая, когда же приложение наконец упадет на моем Nexus 5. Наконец, через 5 минут и 55 экранов, приложение упало.

Вот поэтому я и проверяю плавность работы анимаций, скорость перехода между экраанами и прочие визуальные штуки (включая и OutOfMemoryError, причины которых я нет-нет, и допускаю в коде) в приложениях, которые я пишу, не на Nexus 5, а на довольно хилом HTC Desire X.
Не только потому, что он моментально приводит к утечке памяти (статическая переменная продолжит существовать пока существует приложение, и activity, на который она ссылается, никогда не будет выгружен). Этот подход также может привести к ситуации, когда вы будете обмениваться информацией не с тем экраном, ведь экран, невидимый пользователю, может в любой момент быть уничтожен и пересоздан лишь когда пользователь к нему вернется.

Вижу противоречие. Либо не будет выгружен, либо все-таки будет в любой момент уничтожен. Собственно, ссылки на GUI элементы и держат, когда нужно чтобы их не убило, и не надо было заново создавать и грузить экран при каждом переключении.
Я неправильно выразился. Он не может быть уничтожен, он может быть «забыт» системой.

Я поясню на примере:

Представьте, что ваше приложение состоит из двух Activity: ListActivity и DetailsActivity.
Когда пользователь запускает приложение, он попадает на ListActivtiy. Когда он выбирает элемент в ListActivity, открывается DetailsActivity.

Пользователь запускает приложение и система автоматически создает экземпляр класса ListActivity; назовем его объект 1. Вы сохраняете ссылку на этот объект в статической переменной. После этого пользователь переходит на экран DetailsActivity. В этот момент система создает экземпляр класса DetailsActivity; назовем его объект 2. После некоторого времени пользователь нажимает кнопку back и возвращается на экран ListActivity. Вот тут и начинаются проблемы.

В любой момент между тем, как пользователь ушел с экрана ListActivity и вернулся к нему обратно, система может «забыть» про объект 1. Это не значит, что объект будет обязательно уничтожен сборщиком мусора (вы же держите на него ссылку). Это значит, что в момент, когда пользователь вернется на экран ListActivity вместо того, чтобы использовать старый объект 1, система создаст новый экземпляр класса ListActivity, объект 3. У вас в системе будет одновременно два экземпляра класса ListActivity: объект 1, хранящийся в вашей статической переменной и объект 3, отображаемый пользователю.

Про объект 1 будете знать только вы — он уже никогда не будет показан пользователю. Вы можете изменять его поля, вызывать его методы, но визуально это не будет приводить ни к чему — ведь пользователь видит на экране объект 3, а не объект 1.

Вы можете легко добиться описанного эффекта с двумя экземплярами ListActivity включив опцию Settings>Developer options>Don't keep activities.

Поэтому ваше утверждение о том, что
Собственно, ссылки на GUI элементы и держат, когда нужно чтобы их не убило, и не надо было заново создавать и грузить экран при каждом переключении.

в корне неверно относительно Activity (с fragments или views так получится — их созданием управляете вы сами, а не система).
Это значит, что в момент, когда пользователь вернется на экран ListActivity вместо того, чтобы использовать старый объект 1, система создаст новый экземпляр класса ListActivity, объект 3. У вас в системе будет одновременно два экземпляра класса ListActivity: объект 1, хранящийся в вашей статической переменной и объект 3, отображаемый пользователю.


А, понятно, специфическое поведение активити.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.