Оптимизируйте потребление памяти приложением Android с помощью профилирования в Android Studio

Что такое профилирование приложений и чем оно может помочь?

Разработчики приложений должны задать себе один вопрос: что я буду делать, когда пользователи сообщают, что они отстают при использовании данного приложения? Ответ не всегда очевиден, но в большинстве случаев он связан с задачами, интенсивно использующими процессор, которые блокируют основной поток, а также бывают случаи, когда такого рода проблемы с производительностью связаны с памятью.

Вы, конечно, можете распечатать некоторые журналы, чтобы помочь вам в устранении неполадок, но вам нужно хорошо знать кодовую базу, чтобы разместить журнал в соответствующих местах.

Если вы хотите изучить другой вариант, который не полагается исключительно на журналы, взгляните на Android Profiler, который был представлен в Android Studio 3.0. Разработчики могут использовать этот инструмент для мониторинга использования ЦП и памяти, перехвата сетевых ответов и даже наблюдения за потреблением энергии.

Основываясь на данных метрик, предоставленных Android Profiler, мы можем лучше понять, как наши приложения используют процессор и другие ресурсы памяти. Это панировочные сухари, которые в конечном итоге могут привести нас к первопричине проблемы.

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

Проблемы с памятью

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

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

Когда часть памяти больше не требуется, ОС Android автоматически освобождает этот ресурс памяти, чтобы его можно было повторно использовать для обслуживания нового запроса на выделение памяти. Этот процесс широко известен как Сборка мусора.

Сборка мусора обычно не влияет на производительность приложения, так как время паузы приложения, вызванное одним процессом сборки мусора, незначительно. Однако если за короткий промежуток времени произойдет слишком много событий «Сборка мусора», у пользователей начнется вялый опыт работы с приложением.

Профилирование памяти с помощью Android Profiler

Предварительные условия для Android Profile - это копия Android Studio 3.0 или более поздней версии и подключенное тестовое устройство или эмулятор, работающий как минимум с Android SDK 26 уровня. После того, как эти начальные компоненты будут готовы к работе, нажмите Profiler tab на нижней панели, чтобы запустите Android Profiler.

Запустите приложение в режиме отладки, и вы увидите, что Android Profiler отображает в реальном времени показатели для CPU, Memory, Network и Energy.

Щелкните раздел Memory, чтобы просмотреть подробные показатели использования памяти - Android Profiler предоставляет визуализированный обзор использования памяти с течением времени.

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

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

Android Profiler в действии

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

1. Растущий график

Если вы наблюдаете линию тренда, которая продолжает расти и редко идет вниз, это может быть связано с утечкой памяти, что означает, что некоторые участки памяти не могут быть освобождены. Или просто не хватает памяти для работы с приложением. Когда приложение достигает предела памяти и ОС Android не может выделить дополнительную память для приложения, будет выдано OutOfMemoryError.

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

В этом действии не используются AdapterView или RecyclerView для повторного использования представлений элементов; поэтому для завершения выполнения addRowsOfTextView() необходимо создание 100 тыс. представлений.

Теперь нажмите кнопку «Пуск» и проследите за использованием памяти в Android Studio. Использование памяти продолжает увеличиваться, и в конечном итоге приложение вылетает. Это ожидаемое поведение.

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

2. Турбулентность за короткий промежуток времени

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

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

Чтобы воспроизвести эту проблему, запустите пример «Многочисленные сборщики мусора» из демонстрационного приложения. В этом примере используется RecyclerView для отображения двух альтернативных растровых изображений: одно большое растровое изображение с разрешением 1000 x 1000 и меньшее с разрешением 256 x 256. Прокрутите RecyclerView, и вы увидите очевидную турбулентность в профилировщике памяти и вялого пользователя опыт работы в мобильном приложении.

В этом случае в примере кода используется правильная реализация RecyclerView, но мы по-прежнему сталкиваемся с проблемами памяти. Несмотря на то, что RecyclerView является решением предыдущей проблемы с памятью, это не серебряная пуля, которая решает все проблемы с памятью. Чтобы выяснить основную причину, нам нужна дополнительная информация для анализа.

Нажмите кнопку «Запись» в профилировщике памяти, прокрутите некоторое время RecyclerView и нажмите кнопку «Стоп». Профилировщик представит вам подробный список использования памяти, классифицированный по типам объектов.

Отсортируйте список по мелкому размеру, а верхний элемент - это байтовый массив, поэтому мы знаем, что большая часть выделения памяти приписывается созданию байтовых массивов. Для байтового массива всего 32 выделения, а его общий размер составляет 577 871 888 бит, что составляет 72,23 МБ.

Чтобы получить дополнительную информацию, щелкните один из экземпляров в представлении экземпляров и просмотрите стек вызовов распределения. Выделенный метод - это onBindViewHolder() из NumerousGCRecyclerViewAdapter, но мы не создаем массив байтов явно с помощью этого метода.

Давайте посмотрим не только на метод onBindViewHolder() в стеке вызовов. Вызов метода идет с decodeResource() -> decodeResourceStream() -> decodeStream() -> nativeDecodeAsset() и, наконец, прибывает в newNonMovableArray(). В документе этого метода написано Returns an array allocated in an area of the Java heap where it will never be moved. This is used to implement native allocations on the Java heap, such as DirectByteBuffers and Bitmaps.

Таким образом, можно сделать вывод, что огромное количество байтовых массивов создается именно здесь с помощью метода nativeDecodeAsset().

Каждый раз, когда мы вызываем BitmapFactory.decodeResource(), создается новый экземпляр объекта растрового изображения и, следовательно, его базовые данные массива байтов. Если мы сможем уменьшить частоту вызовов BitmapFactory.decodeResource(), мы сможем избежать необходимости в дополнительном выделении памяти и, следовательно, уменьшить количество случаев сборки мусора.

Это улучшенная версия RecyclerViewAdapter, которая создает экземпляры растровых изображений только один раз, кэширует их и повторно использует эти растровые изображения для imageView в методе onBindViewHolder(). Любое другое ненужное выделение памяти избегается. Давайте посмотрим на график памяти после улучшения.

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

Резюме

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

Обсудите этот пост в Hacker News.

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

Независимая редакция, Heartbeat спонсируется и публикуется Comet, платформой MLOps, которая позволяет специалистам по обработке данных и группам машинного обучения отслеживать, сравнивать, объяснять и оптимизировать свои эксперименты. Мы платим участникам и не продаем рекламу.

Если вы хотите внести свой вклад, отправляйтесь на наш призыв к участникам. Вы также можете подписаться на наши еженедельные информационные бюллетени (Deep Learning Weekly и Comet Newsletter), присоединиться к нам в » «Slack и подписаться на Comet в Twitter и LinkedIn для получения ресурсов, событий и гораздо больше, что поможет вам быстрее и лучше строить лучшие модели машинного обучения.