Этот документ поможет вам выявить и устранить ключевые проблемы с производительностью вашего приложения.
Ключевые проблемы производительности
Существует множество проблем, которые могут привести к низкой производительности приложения, но ниже перечислены некоторые распространенные проблемы, на которые следует обратить внимание:
- Задержка запуска
Задержка запуска — это время, которое проходит между нажатием на значок приложения, уведомление или другую точку входа и отображением данных пользователя на экране.
При разработке своих приложений для стартапов ставьте перед собой следующие цели:
Холодный запуск менее чем за 500 мс. Холодный запуск происходит, когда запускаемое приложение отсутствует в памяти системы. Это случается при первом запуске приложения после перезагрузки или после остановки процесса приложения пользователем или системой.
Напротив, « тёплый старт» происходит, когда приложение уже работает в фоновом режиме. «Холодный старт» требует от системы наибольших усилий, поскольку ей необходимо загрузить всё из хранилища и инициализировать приложение. Постарайтесь, чтобы время холодного старта составляло 500 мс или меньше.
Задержки P95 и P99 очень близки к медианной задержке. Длительный запуск приложения ухудшает пользовательский опыт. Межпроцессное взаимодействие (IPC) и ненужные операции ввода-вывода во время критического пути запуска приложения могут привести к конфликтам блокировок и несогласованности данных.
- Скролл-джанк
Термин «заикание» описывает визуальные сбои, возникающие, когда система не может вовремя сформировать и предоставить кадры для их отображения на экране с требуемой частотой 60 Гц или выше. Заикание наиболее заметно при прокрутке, когда вместо плавной анимации наблюдаются рывки. Заикание появляется, когда движение приостанавливается на один или несколько кадров, поскольку приложению требуется больше времени для рендеринга контента, чем длится один кадр в системе.
Приложения должны ориентироваться на частоту обновления 90 Гц. Традиционная частота рендеринга составляет 60 Гц, но многие новые устройства работают в режиме 90 Гц во время взаимодействия с пользователем, например, при прокрутке. Некоторые устройства поддерживают даже более высокую частоту — до 120 Гц.
Чтобы увидеть, какую частоту обновления использует устройство в данный момент времени, включите наложение в разделе «Отладка» > «Параметры разработчика» > «Показать частоту обновления» .
- Переходы, которые не проходят гладко
Это особенно заметно при таких взаимодействиях, как переключение между вкладками или загрузка нового окна. Подобные переходы должны быть плавными анимациями и не содержать задержек или визуального мерцания.
- Неэффективность использования энергии
Выполнение работы снижает заряд батареи, а выполнение ненужной работы сокращает срок службы батареи.
Выделение памяти, происходящее при создании новых объектов в коде, может значительно увеличить нагрузку на систему. Это связано не только с самим выделением памяти, но и с необходимостью последующего освобождения этих объектов ( сборкой мусора ). Как выделение, так и сборка мусора происходят гораздо быстрее и эффективнее, особенно для временных объектов. Хотя раньше считалось лучшей практикой избегать выделения объектов, мы рекомендуем поступать так, как наиболее целесообразно для вашего приложения и архитектуры. Экономия на выделении памяти за счет риска создания неподдерживаемого кода — не лучшая практика, учитывая возможности ART.
Однако это требует усилий, поэтому имейте в виду, что выделение большого количества объектов во внутреннем цикле может привести к проблемам с производительностью.
Выявление проблем
Для выявления и устранения проблем с производительностью мы рекомендуем следующий алгоритм действий:
- Выявите и проанализируйте следующие критически важные сценарии взаимодействия пользователя с системой:
- Типичные сценарии запуска, в том числе из панели запуска и через уведомления.
- Экраны, на которых пользователь прокручивает данные.
- Переходы между экранами.
- Длительные процессы, такие как навигация или воспроизведение музыки.
- Проанализируйте происходящее на протяжении предыдущих этапов с помощью следующих инструментов отладки:
- Perfetto : позволяет отслеживать происходящее на всем устройстве с помощью точных данных о времени.
- Memory Profiler : позволяет увидеть, какие операции выделения памяти происходят в куче.
- Simpleperf : отображает график распространения ошибки, показывающий, какие вызовы функций используют больше всего ресурсов ЦП в течение определенного периода времени. Если вы обнаружили в Systrace функцию, которая занимает много времени, но не знаете причину, Simpleperf может предоставить дополнительную информацию.
Для понимания и устранения этих проблем с производительностью крайне важно вручную отлаживать отдельные тестовые запуски. Анализ агрегированных данных не заменит описанные выше шаги. Однако, чтобы понять, что на самом деле видят пользователи, и определить, когда могут возникать регрессии, важно настроить сбор метрик в автоматизированном тестировании и в реальных условиях:
- Стартап-потоки
- Полевые показатели: время запуска Play Console
- Лабораторные тесты: запуск тестов с помощью Macrobenchmark.
- Джанк
- Полевые показатели
- Основные показатели работы Play Console: в Play Console нельзя сузить метрики до конкретного пользовательского сценария. Она сообщает только об общих проблемах в работе приложения.
- Пользовательские измерения с помощью
FrameMetricsAggregator: вы можете использоватьFrameMetricsAggregatorдля записи метрик задержки во время выполнения определенного рабочего процесса.
- Лабораторные анализы
- Прокрутка с помощью Macrobenchmark .
- Macrobenchmark собирает данные о времени отрисовки кадров с помощью команд
dumpsys gfxinfo, которые охватывают определенный пользовательский сценарий. Это позволяет понять вариации в задержках отрисовки на протяжении конкретного пользовательского сценария. МетрикиRenderTime, которые показывают, сколько времени занимает отрисовка кадров, важнее, чем количество кадров с задержками, для выявления регрессий или улучшений.
- Полевые показатели
Проблемы с проверкой ссылок на приложения.
Ссылки на приложения — это прямые ссылки, основанные на URL-адресе вашего веб-сайта, которые проверены на принадлежность к вашему веб-сайту. Ниже перечислены причины, по которым проверка ссылок на приложения может завершиться неудачей.
- Области действия фильтров намерений: добавляйте
autoVerifyв фильтры намерений только для тех URL-адресов, на которые ваше приложение может отвечать. - Непроверенные переключения протоколов: непроверенные перенаправления на стороне сервера и поддоменов считаются угрозой безопасности и приводят к сбою проверки. Они приводят к сбою всех ссылок
autoVerify. Например, перенаправление ссылок с HTTP на HTTPS, таких как example.com на www.example.com, без проверки ссылок HTTPS может привести к сбою проверки. Обязательно проверяйте ссылки приложений , добавляя фильтры намерений. - Непроверяемые ссылки: добавление непроверяемых ссылок в целях тестирования может привести к тому, что система не проверит ссылки приложения.
- Ненадежные серверы: убедитесь, что ваши серверы могут подключаться к клиентским приложениям.
Настройте ваше приложение для анализа производительности.
Для получения точных, воспроизводимых и применимых на практике результатов тестирования приложения крайне важно правильно настроить систему. Тестирование следует проводить на системе, максимально приближенной к рабочей, при этом подавляя источники шума. В следующих разделах описан ряд шагов, специфичных для APK-файлов и систем, которые можно предпринять для подготовки тестовой среды, некоторые из которых зависят от конкретного сценария использования.
Точки трассировки
Приложения могут инструментировать свой код с помощью пользовательских событий трассировки .
Во время записи трассировки она влечет за собой небольшие накладные расходы, примерно 5 мкс на участок, поэтому не стоит включать ее во все методы. Трассировка больших фрагментов работы длительностью более 0,1 мс может дать ценную информацию о узких местах.
Вопросы, касающиеся APK
Отладочные варианты могут быть полезны для устранения неполадок и символизации примеров стека, но они оказывают серьезное влияние на производительность. Устройства под управлением Android 10 (уровень API 29) и выше могут использовать profileable android:shell="true" в своем манифесте для включения профилирования в релизных сборках.
Используйте конфигурацию сжатия кода, соответствующую производственным требованиям. В зависимости от ресурсов, используемых вашим приложением, это может существенно повлиять на производительность. Некоторые конфигурации ProGuard удаляют точки трассировки, поэтому рассмотрите возможность удаления этих правил для конфигурации, на которой вы запускаете тесты.
Компиляция
Скомпилируйте приложение на устройстве до известного состояния — как правило, для простоты speed или speed-profile для более точного соответствия производительности в производственной среде (хотя это требует предварительной настройки приложения и сохранения профилей, или компиляции базовых профилей приложения).
И speed , и speed-profile уменьшают объем кода, выполняемого интерпретируемым из dex, и, следовательно, объем фоновой компиляции just-in-time (JIT), которая может вызывать значительные помехи. Только speed-profile уменьшает влияние загрузки классов из dex во время выполнения.
Следующая команда компилирует приложение в режиме speed :
adb shell cmd package compile -m speed -f com.example.packagename
Режим speed компиляции полностью компилирует методы приложения. Режим speed-profile компилирует методы и классы приложения в соответствии с профилем используемых участков кода, который собирается во время работы приложения. Собирать профили последовательно и корректно может быть сложно, поэтому, если вы решите их использовать, убедитесь, что они собирают то, что вы ожидаете. Профили находятся в следующем месте:
/data/misc/profiles/ref/[package-name]/primary.prof
Системные аспекты
Для проведения измерений низкого уровня и высокой точности откалибруйте свои устройства. Проведите A/B-сравнение на одном и том же устройстве и одной и той же версии ОС. Даже на устройствах одного типа могут наблюдаться значительные различия в производительности.
На устройствах с root-доступом рекомендуется использовать скрипт lockClocks для Microbenchmarks. Среди прочего, эти скрипты выполняют следующие действия:
- Устанавливайте процессоры на фиксированную частоту.
- Отключите маломощные ядра и настройте графический процессор.
- Отключить терморегулирование.
Мы не рекомендуем использовать скрипт lockClocks для тестов, ориентированных на пользовательский опыт, таких как запуск приложения, тестирование DoU и тестирование на задержки, но он может быть необходим для снижения уровня шума в тестах Microbenchmark.
По возможности, рассмотрите возможность использования тестовой среды, такой как Macrobenchmark , которая может уменьшить шум в ваших измерениях и предотвратить неточности измерений.
Медленный запуск приложения: ненужные действия, напоминающие батут.
Активность типа «батут» может неоправданно увеличивать время запуска приложения, и важно знать, делает ли это ваше приложение. Как показано в следующем примере трассировки, за одним activityStart сразу следует другое activityStart при этом первое событие не отрисовывает никаких кадров.
Рисунок 1. График, демонстрирующий активность на батуте.
Это может произойти как в точке входа уведомления, так и в обычной точке входа при запуске приложения, и часто это можно решить с помощью рефакторинга. Например, если вы используете это действие для выполнения настройки перед запуском другого действия, вынесите этот код в многоразовый компонент или библиотеку.
Ненужные выделения ресурсов приводят к частым сборкам мусора.
В Systrace вы можете заметить, что сборка мусора происходит чаще, чем вы ожидаете.
В следующем примере, каждые 10 секунд во время длительной операции указывают на то, что приложение может выделять ресурсы без необходимости, но постоянно в течение определенного времени:
Рисунок 2. График, показывающий промежутки между событиями GC.
Вы также можете заметить, что при использовании профилировщика памяти подавляющее большинство выделений памяти происходит за счет определенного стека вызовов. Нет необходимости агрессивно исключать все выделения памяти, так как это может затруднить сопровождение кода. Вместо этого начните с работы над проблемными местами выделения памяти.
Некачественные рамки
Графический конвейер довольно сложен, и при определении того, увидит ли пользователь в итоге пропущенный кадр, могут возникнуть некоторые нюансы. В некоторых случаях платформа может «спасти» кадр с помощью буферизации. Однако большую часть этих нюансов можно игнорировать, чтобы определить проблемные кадры с точки зрения вашего приложения.
Когда отрисовка кадров выполняется с минимальными усилиями со стороны приложения, точки трассировки Choreographer.doFrame() возникают с частотой 16,7 мс на устройстве с частотой 60 кадров в секунду:
Рисунок 3. График, демонстрирующий частое появление быстрых кадров.
Если уменьшить масштаб и просмотреть трассировку, иногда можно заметить, что обработка кадров занимает немного больше времени, но это все равно нормально, поскольку они не превышают отведенные им 16,7 мс:
Рисунок 4. График, демонстрирующий частые быстрые кадры с периодическими всплесками работы.
Когда вы видите нарушение этого регулярного ритма, это означает некорректную работу кадра, как показано на рисунке 5:
Рисунок 5. Траектория, демонстрирующая некачественный кадр.
Вы можете потренироваться в их определении.
Рисунок 6. График, демонстрирующий еще больше некачественных кадров.
В некоторых случаях для получения более подробной информации о том, какие представления создаются или что делает RecyclerView , необходимо увеличить масштаб точки трассировки. В других случаях может потребоваться более детальное исследование.
Для получения дополнительной информации об обнаружении проблем с отображением кадров и устранении причин их возникновения см. раздел «Медленная отрисовка» .
Распространенные ошибки в RecyclerView
Излишняя аннулирование всех базовых данных RecyclerView может привести к длительному времени отрисовки кадров и рывкам. Вместо этого, чтобы минимизировать количество обновляемых представлений, аннулируйте только те данные, которые изменились.
См. раздел «Отображение динамических данных» для получения информации о способах избежать дорогостоящих вызовов notifyDatasetChanged() , которые приводят к обновлению содержимого вместо его полной замены.
Если не обеспечить надлежащую поддержку каждого вложенного RecyclerView , это может привести к тому, что внутренний RecyclerView будет полностью пересоздаваться каждый раз. Для каждого вложенного внутреннего RecyclerView необходимо установить RecycledViewPool , чтобы обеспечить возможность повторного использования представлений между каждым внутренним RecyclerView .
Недостаточное или несвоевременное предварительное получение данных может привести к тому, что при достижении конца списка прокрутки пользователю будет неприятно ждать получения дополнительных данных с сервера. Хотя это технически не является "рывком", поскольку сроки загрузки кадров не пропускаются, можно значительно улучшить пользовательский опыт, изменив время и количество предварительной загрузки, чтобы пользователю не приходилось ждать данных.
Отладьте ваше приложение
Ниже описаны различные методы отладки производительности вашего приложения. Для ознакомления с трассировкой системы и использованием профилировщика Android Studio посмотрите следующее видео.
Отладка запуска приложения с помощью Systrace
Обзор процесса запуска приложения можно найти в разделе «Время запуска приложения», а обзор трассировки системы — в следующем видеоролике.
Различить типы стартапов можно на следующих этапах:
- Холодный запуск: запуск с создания нового процесса без сохранения состояния .
- «Теплый запуск»: либо воссоздает активность с повторным использованием процесса, либо воссоздает процесс с сохраненным состоянием.
- Горячий старт: перезапускает активность и начинает с инфляции.
Мы рекомендуем создавать трассировочные файлы Systrace с помощью приложения System Tracing на устройстве . Для Android 10 и выше используйте Perfetto . Для Android 9 и ниже используйте Systrace . Мы также рекомендуем просматривать трассировочные файлы с помощью веб-просмотрщика трассировок Perfetto . Для получения дополнительной информации см. раздел «Обзор трассировки системы» .
Следует обратить внимание на следующие моменты:
- Конкуренция за ресурсы, защищенные монитором, может привести к значительной задержке при запуске приложения.
Синхронные транзакции привязки: выявите ненужные транзакции в критическом пути вашего приложения. Если необходимая транзакция обходится дорого, рассмотрите возможность сотрудничества с соответствующей командой платформы для внесения улучшений.
Одновременная сборка мусора: это распространенная проблема, оказывающая относительно небольшое влияние, но если вы сталкиваетесь с ней часто, стоит изучить ее с помощью профилировщика памяти Android Studio.
Ввод-вывод: проверьте операции ввода-вывода, выполняемые во время запуска, и найдите длительные задержки.
Активность в других потоках может мешать работе пользовательского интерфейса, поэтому следите за фоновыми процессами во время запуска.
Для улучшения отчетности по метрикам запуска приложения мы рекомендуем вызывать reportFullyDrawn после завершения запуска с точки зрения приложения. Дополнительную информацию об использовании reportFullyDrawn см. в разделе «Время до полного отображения» . Вы можете извлечь время запуска, определенное в RFD, с помощью процессора трассировки Perfetto, и будет сгенерировано видимое пользователю событие трассировки.
Используйте трассировку системы на устройстве.
Для записи трассировки системы на устройстве можно использовать системное приложение System Tracing. Это приложение позволяет записывать трассировку с устройства без необходимости его подключения к сети или через adb .
Используйте профилировщик памяти Android Studio.
С помощью инструмента Android Studio Memory Profiler можно проверить нехватку памяти, которая может быть вызвана утечками памяти или некорректным использованием. Он предоставляет информацию о выделении памяти для объектов в режиме реального времени.
Вы можете устранить проблемы с памятью в своем приложении, используя информацию из профилировщика памяти, чтобы отследить, почему и как часто происходят сборки мусора.
Для профилирования памяти приложения выполните следующие действия:
Выявление проблем с памятью.
Запишите сеанс профилирования памяти для пользовательского сценария, на котором вы хотите сосредоточиться. Обратите внимание на увеличение количества объектов, как показано на рисунке 7, что в конечном итоге приводит к сборке мусора, как показано на рисунке 8.
Рисунок 7. Увеличение количества объектов.
Рисунок 8. Сбор мусора.После определения сценария использования пользователем, вызывающего нехватку памяти, проанализируйте первопричины этой нехватки.
Диагностика очагов перегрузки памяти.
Выберите диапазон на временной шкале, чтобы визуализировать как распределение средств , так и размер неглубоких резервов , как показано на рисунке 9.
Рисунок 9. Значения для распределения и размера неглубокого участка.Существует несколько способов сортировки этих данных. Ниже приведены некоторые примеры того, как каждый из этих способов может помочь вам в анализе проблем.
Сортировка по классам : полезна, когда нужно найти классы, которые создают объекты, которые в противном случае кэшируются или повторно используются из пула памяти.
Например, если вы видите, что приложение каждую секунду создает 2000 объектов класса "Vertex", это увеличивает количество выделенных памяти на 2000 в секунду, и вы видите это при сортировке по классу. Если вы хотите повторно использовать эти объекты, чтобы избежать генерации мусора, реализуйте пул памяти.
Сортировка по стеку вызовов : полезна, когда нужно найти наиболее часто используемый путь выделения памяти, например, внутри цикла или внутри конкретной функции, выполняющей большой объем работы по выделению памяти.
Небольшой размер : отслеживает только память самого объекта. Это полезно для отслеживания простых классов, состоящих в основном из примитивных типов данных.
Остаточный размер : показывает общий объем памяти, выделенный объектом и ссылками, на которые ссылается только этот объект. Он полезен для отслеживания нагрузки на память, вызванной сложными объектами. Чтобы получить это значение, сделайте полный дамп памяти, как показано на рисунке 10, и добавьте столбец «Остаточный размер» , как показано на рисунке 11.
Рисунок 10. Полный дамп памяти. 
Рисунок 11. Столбец «Сохраненный размер».
Оцените влияние оптимизации.
Влияние оптимизации памяти на процессы сборки мусора становится более очевидным и его легче измерить. Когда оптимизация снижает нагрузку на память, вы наблюдаете меньшее количество запусков сборщика мусора.
Чтобы оценить влияние оптимизации, на временной шкале профилировщика измерьте время между сборками мусора. Вы увидите, что интервал между сборками мусора увеличивается.
В конечном итоге, результаты улучшения памяти следующие:
- Вероятность сбоев из-за нехватки памяти значительно снизится, если приложение не будет постоянно испытывать нехватку памяти.
- Меньшее количество сборок мусора улучшает показатели задержки, особенно в P99. Это связано с тем, что сборка мусора вызывает конкуренцию за ресурсы ЦП, что может привести к отсрочке задач рендеринга на время выполнения сборки мусора.
Рекомендуем вам
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Анализ и оптимизация при запуске приложения {:#app-startup-analysis-optimization}
- Застывшие кадры
- Напишите макротест производительности