В данной заметке мы рассмотрим несколько примеров утечек памяти в Android приложении. Также мы рассмотрим исправления этих утечек. Я хочу показать, что с утечками памяти можно и нужно бороться. Я не буду рассказывать про инструменты для анализа памяти, только лишь приведу ссылки на самые важные из них.

Неправильный AnimationListener


Для привлечения внимания к новому функционалу было принято решение воспользоваться анимацией. Необходимо было показать на короткий период View с подсказкой. Пример можно увидеть на изображении ниже:

Гифка с примером


Позже обнаружилась утечка Activity.



Проблема заключалась в том, что анимация fadeOut использовала слушателя от анимации fadeIn, что приводило к бесконечному запуску анимации fadeOut.



Исправить это было достаточно легко.



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

android.widget.Editor$Blink


Бывает утечки памяти находятся и в самой платформе. Однажды я получил такой отчет:



Чтобы воспроизвести утечку достаточно создать Dialog с EditText внутри и что-нибудь в нем напечатать.

Гифка с примером


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



Утечка от производителя. Singleton — зло


В одном из отчетов я получил утечку с не публичным, неизвестным мне ранее классом. Оказалось, что это приватный класс от одного популярного производителя.



На этот раз мне опять повезло и я нашел готовое решение в сети (1, 2).



Результаты




В результате количество OutOfMemoryError уменьшилось почти в 7 раз. Так же уменьшилось количество RuntimeException,InflateException: часть из них — это пойманные и обернутые OutOfMemoryError. Одним из ограничивающих факторов в исправлении такого рода ошибок является то, что я не могу делать обновления, которые не содержат внешних изменений (для пользователей выглядит как “Пустое обновление”) В таких случаях приходится ждать реализации нового функционала для проверки результатов.

Выводы


  • Документируйте не тривиальные/не очевидные моменты в коде, которые исправляют утечки памяти. Часто они выглядят подозрительно.
  • Не ошибается тот, кто ничего не делает. Иногда приходится фиксить утечки памяти в чужом коде.
  • Нужно знать свой инструментарий (hprof-conv, VisualVM, OQL, leakcanary)
  • Обязательно делайте измерения и следите за ними(crash-report tools).
  • Не допускайте увеличения количества ошибок.

Ссылки


Комментарии (10)


  1. TARAKAN
    09.12.2015 21:10
    +1

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

    Я понимаю, что из-за небольших ошибок не выкладывать, но если исправлено их несколько или какая-то массовая ошибка с тем же OutOfMemory, то почему нельзя выложить? Многие приложения выкладываются с одним единственным пунктом «Bugs fixed» бывает еще описывается какие баги, но зачастую только общий текст, что исправлены ошибки и улучшена стабильность приложения.


    1. korniltsev
      10.12.2015 15:29

      В собственных проектах я не стесняюсь делать обновления из-за подобного рода ошибок.
      Однако на текущем месте работы не принятно делать пустых обновлений. У нас пустые обновления негативно влияют на рейтинг в маркете. Рейтинг может влиять на выдачу в поиске.


      1. Bringoff
        10.12.2015 16:17
        +1

        У нас пустые обновления негативно влияют на рейтинг в маркете

        У всех, конечно, свои странности, но… А падения от OOM на рейтинг полжительно влияют, что ли?


      1. TARAKAN
        12.12.2015 13:33
        +1

        Не в курсе всей логики пользователей, может у вас там какое-то «исследование» проводили, но я по наблюдению отзывов заметил, что люди быстрее бегут ставить рейтинг после нескольких падений и понятно, что рейтинг зачастую ставят сразу 1, а вот после обновления часто забывают обновить рейтинг.

        Вот смотрю сейчас Instagram приложение, у них в «Что нового» уже давным давно один и тот же текст, а приложение помню обновлялось раз 5 точно, аналогично с другими, там может быть 10 обновлений без новых фич.


  1. Nucleotide
    10.12.2015 06:30

    Позже обнаружилась утечка Activity.

    Да, но как только Вью задестроится, задестроится и анимация, и утечки не будет. Разве нет?
    Вопрос в том, действительно останавливать бесконечную анимацию вручную, ведь она со всеми вью задестроится и всё, никаких проблем?


    1. thelongrunsmoke
      10.12.2015 08:30

      Это может привести к OutOfMemoryError до уничтожения View. Особенно при сложном интерфейсе или ошибках разработки. Например, ViewPager со сложным фрагментом, который почему-то не выгружают из памяти.


    1. korniltsev
      10.12.2015 16:02
      +1

      Да, но как только Вью задестроится, задестроится и анимация, и утечки не будет. Разве нет?

      Нет, это не так.
      Необходимо понимать как работают Animator и Choreographer. До тех пор, пока анимация не завершится, аниматор будет добавлять колбэк(AnimationHandler) в одну из очередей Choreographer. Этот колбэк исполняется на каждый фрейм и меняет значение пропертей(альфа, смещение и т.д.). Получается бесконечная/долгая анимация будет бесконечно/долго отправдять такие сообщение в очередь на исполнение.
      Получается следующая цепочка, которая не дает gc собрать вьюшку(а с ней и контекст):
      ThreadLocal(Choreographer) -> у Choreographer жесткая ссылка на очередь колбэков -> у колбэков жесткая ссылка на аниматор -> у аниматора жесткая ссылка на вьюху


  1. DZVang
    10.12.2015 10:59
    +1

    А с чем связано использование VisualVM, а не MAT? Есть какие-то плюсы, минусы, подводные камни. Во всех статьях, с которыми я сталкивался всегда MAT использовался.


    1. korniltsev
      10.12.2015 15:16

      jvisualvm поставляется вместе с jdk. Еще в jvisualvm есть удобные сниппеты для OQL.

      скрин


  1. Mikhail_dev
    11.12.2015 00:17

    О чем статья? О том что в анимации могут быть утечки? Вот и назовите тогда так статью, хотя на статью это не тянет.

    Причем тут singleton, при этом еще и зло? Недавно только дебаты устраивали на счет этого, и пришли к выводу что зло он только потому, что с ним сложно (или может невозможно) использовать автоматические тесты. Так что не особо понятно как к статье это относится, что он зло, и что он синглтон.

    В итоге ничего толком не написано. Сухие ссылки на инструменты, и не более того. Последний вывод вообще убил.

    Не допускайте увеличения количества ошибок.

    Серьёзно? Ну ок, а я то думал.

    P.S. и если уж на то пошло, то надо было и про WeakReference словечко замолвить, ну или как вы сделали, ссылку дать.