Перейти к содержанию

Производительность и память

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

Отзывчивость и отрисовка

ANR

ANR (Application Not Responding) означает, что, по оценке системы, компонент приложения не ответил за отведённое время. Самый известный случай - ожидание обработки события ввода около пяти секунд, но у сервисов, BroadcastReceiver и callback-методов JobService тоже есть свои временные ограничения.

Типичные причины: синхронный ввод-вывод или тяжёлые вычисления в главном потоке, медленные Binder-вызовы, конкуренция за блокировки, взаимные блокировки и вызовы wait(), join() или sleep(). BroadcastReceiver также может вызвать ANR, если выполняет слишком много работы в onReceive() или вызывает goAsync(), но не завершает PendingResult вовремя.

Делайте callback-методы компонентов короткими, переносите блокирующий ввод-вывод и тяжёлые вычисления на подходящие диспетчеры или исполнители и не удерживайте блокировки во время медленных операций. В разработке используйте StrictMode, а для проблем в production изучайте ANR traces и Android vitals. Перенос работы из главного потока улучшает отзывчивость, но не исправляет небезопасное общее состояние или неограниченный объём работы.

Выбор между WorkManager, foreground service и другими фоновыми API разобран в статье Фоновая работа и поведение системы.

Jank

Jank - заметные рывки интерфейса из-за того, что кадры не укладываются в сроки отрисовки. При 60 Гц интервал кадра составляет около 16,7 мс, при 90 Гц - 11,1 мс, а при 120 Гц - 8,3 мс. На результат может влиять работа главного потока, RenderThread, GPU и других частей графического конвейера.

Типичные причины: дорогие проходы measure/layout/draw, тяжёлая композиция или привязка данных, синхронная работа в главном потоке, частые выделения памяти и сборка мусора, декодирование изображений и плохо согласованные анимации. Бюджет кадра - это общий срок для всего конвейера, а не допустимое время для одного метода приложения.

Воспроизводите проблему в сборке, близкой к release, желательно на типичном не самом быстром устройстве. Используйте System Trace или Perfetto, чтобы найти медленные кадры, а затем уточняйте причину с помощью Android Studio Profiler, Layout Inspector, собственных trace-секций, JankStats или Macrobenchmark. После изменения повторите измерение.

Overdraw

Overdraw возникает, когда один и тот же пиксель рисуется несколько раз за кадр. Например, фон окна, фон корневого layout и непрозрачная карточка могут перекрывать друг друга, хотя отрисовываются все три.

Некоторый overdraw нормален и сам по себе не означает проблему. Исследуйте его, когда traces или инструменты анализа GPU указывают на высокую стоимость заполнения, особенно на сложных экранах и слабых устройствах. Убирайте лишние фоны и невидимые слои, сокращайте ненужные перекрытия, осознанно используйте прозрачность, clipping, тени и внеэкранную отрисовку. Упрощать иерархию стоит только тогда, когда это уменьшает измеренную стоимость layout или draw.

Память и инструменты

Утечки памяти и давление на память

Утечка памяти возникает, когда объект уже не нужен, но остаётся достижимым по сильным ссылкам от GC root. Типичные Android-примеры: singleton удерживает Activity, callback не удаляется, долгоживущая coroutine захватывает View, или binding представления фрагмента сохраняется после onDestroyView().

Fragment может жить дольше своего представления. Очищайте binding, принадлежащие представлению адаптеры и listeners в onDestroyView(), а данные для UI собирайте с учётом viewLifecycleOwner. Долгоживущим объектам передавайте applicationContext, если его достаточно, а ViewModel не должен хранить Activity, Fragment или View. Слабые ссылки не заменяют правильное владение объектами.

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

Android Studio Profiler

Инструменты профилирования Android Studio помогают проверять гипотезы. CPU recordings показывают горячие участки, активность потоков, конкуренцию за блокировки и работу главного потока. Heap dump и запись аллокаций показывают количество объектов, места их создания и цепочки ссылок. Network Inspector помогает изучать время запросов и размер данных.

Профилируйте конкретный сценарий: холодный запуск, прокрутку, открытие тяжёлого экрана, загрузку или анимацию данных. Debug-сборки и инструментальная трассировка методов создают дополнительные накладные расходы, поэтому для оценки пользовательского опыта используйте release-like или profileable build. Сравнивайте один и тот же сценарий до и после оптимизации.

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

LeakCanary

LeakCanary обнаруживает в debug-сборках объекты, которые уже должны были стать доступными для сборки мусора. Если отслеживаемый объект остаётся удержанным, библиотека анализирует heap dump и показывает путь ссылок от GC root до этого объекта.

Типичные находки: утечка binding представления фрагмента, неочищенный listener, адаптер, удерживающий уничтоженное представление, контекст Activity в singleton или coroutine, захватившая UI. LeakCanary показывает цепочку удержания, но разработчик всё равно должен найти неправильного владельца или ошибку жизненного цикла и исправить её. Не каждый временно удерживаемый объект является постоянной утечкой, поэтому проверяйте сценарий и путь ссылок.

DEX и multidex

DEX (Dalvik Executable) - формат байткода, который выполняет Android Runtime. Инструменты сборки Android преобразуют скомпилированный байткод Java и Kotlin в один или несколько DEX-файлов.

Один DEX-файл может содержать ссылки максимум на 65 536 методов. Приложения с minSdk 21 и выше имеют встроенную поддержку multidex; для более старых версий нужны библиотека multidex и специальная обработка запуска. Современные проекты часто пересекают эту границу без ручной настройки, но рост зависимостей всё равно влияет на время сборки, размер загрузки, запуск и сопровождаемость.

Предпочитайте узкие зависимости, удаляйте неиспользуемые библиотеки и включайте R8 shrinking для release-сборок. Multidex - это поддержка упаковки, а не замена контролю зависимостей. Keep rules влияют на то, что R8 может удалить, поэтому проверяйте оптимизированные release-сборки тестами.