Пользователи ожидают, что приложения будут быстро загружаться и будут отзывчивыми. Приложение с медленным запуском не соответствует этим ожиданиям и может разочаровать пользователей. Такого рода плохой опыт может привести к тому, что пользователь плохо оценит ваше приложение в магазине Play или даже вообще откажется от него.
На этой странице представлена информация, которая поможет оптимизировать время запуска вашего приложения, включая обзор внутренних процессов запуска, способы профилирования производительности запуска, а также некоторые распространенные проблемы со временем запуска с советами по их решению.
Понимание различных состояний запуска приложения
Запуск приложения может происходить в одном из трех состояний: холодный старт, теплый старт или горячий старт. Каждое состояние влияет на то, сколько времени потребуется вашему приложению, чтобы стать видимым для пользователя. При холодном старте ваше приложение запускается с нуля. В других состояниях система должна перевести запущенное приложение из фонового режима на передний план.
Мы рекомендуем всегда проводить оптимизацию, исходя из предположения о холодном старте. Это может улучшить производительность как теплых, так и горячих стартов.
Чтобы оптимизировать приложение для быстрого запуска, полезно понимать, что происходит на уровне системы и приложения, а также как они взаимодействуют в каждом из этих состояний.
Два важных показателя для определения запуска приложения — это время до начального отображения (TTID) и время до полной прорисовки (TTFD) . TTID — это время, необходимое для отображения первого кадра, а TTFD — это время, необходимое для того, чтобы приложение стало полностью интерактивным. Оба показателя одинаково важны, поскольку TTID сообщает пользователю, что приложение загружается, а TTFD — это время, когда приложение фактически готово к использованию. Если любой из этих показателей слишком длинный, пользователь может выйти из вашего приложения еще до того, как оно полностью загрузится.
Холодный старт
Холодный старт относится к запуску приложения с нуля. Это означает, что до этого запуска процесс системы создает процесс приложения. Холодные старты происходят в таких случаях, когда ваше приложение запускается впервые после загрузки устройства или после того, как система завершила работу приложения.
Этот тип запуска представляет наибольшую сложность для минимизации времени запуска, поскольку системе и приложению приходится выполнять больше работы, чем в других состояниях запуска.
В начале холодного запуска система выполняет три следующие задачи:
- Загрузите и запустите приложение.
- Отобразить пустое стартовое окно приложения сразу после запуска.
- Создайте процесс приложения.
Как только система создает процесс приложения, процесс приложения отвечает за следующие этапы:
- Создайте объект приложения.
- Запустите основную ветку.
- Создайте основное занятие.
- Раздувать просмотры.
- Разметка экрана.
- Проведите первоначальную жеребьевку.
Когда процесс приложения завершает первую отрисовку, системный процесс заменяет отображаемое фоновое окно на основное действие. В этот момент пользователь может начать использовать приложение.
На рисунке 1 показано, как системные и прикладные процессы передают работу друг другу.

Проблемы с производительностью могут возникнуть во время создания приложения и создания активности.
Создание приложений
При запуске вашего приложения пустое стартовое окно остается на экране до тех пор, пока система не закончит отрисовку приложения в первый раз. В этот момент системный процесс меняет стартовое окно на ваше приложение, позволяя пользователю взаимодействовать с приложением.
Если вы переопределяете Application.onCreate()
в своем собственном приложении, система вызывает метод onCreate()
в вашем объекте приложения. После этого приложение порождает основной поток, также известный как поток пользовательского интерфейса , и поручает ему создание вашей основной активности.
С этого момента процессы на уровне системы и приложения развиваются в соответствии с этапами жизненного цикла приложения .
Создание активности
После того, как процесс приложения создает вашу активность, активность выполняет следующие операции:
- Инициализирует значения.
- Вызывает конструкторы.
- Вызывает метод обратного вызова, например
Activity.onCreate()
, соответствующий текущему состоянию жизненного цикла активности.
Как правило, метод onCreate()
оказывает наибольшее влияние на время загрузки, поскольку он выполняет работу с наибольшими накладными расходами: загрузку и расширение представлений, а также инициализацию объектов, необходимых для выполнения действия.
Теплый старт
Теплый старт охватывает подмножество операций, которые происходят во время холодного старта. В то же время он представляет собой больше накладных расходов, чем горячий старт. Существует много потенциальных состояний, которые можно считать теплыми стартами, например следующие:
Пользователь выходит из вашего приложения, но затем снова его запускает. Процесс может продолжать работать, но приложение должно заново создать активность с нуля, используя вызов
onCreate()
.Система вытесняет ваше приложение из памяти, а затем пользователь перезапускает его. Процесс и активность должны быть перезапущены, но задача может выиграть от некоторой выгоды от сохраненного пакета состояния экземпляра, переданного в
onCreate()
.
Горячий старт
Горячий запуск вашего приложения имеет меньшие накладные расходы, чем холодный запуск. При горячем запуске система выводит вашу активность на передний план. Если все активности вашего приложения все еще находятся в памяти, то приложение может избежать повторной инициализации объектов, инфляции макета и рендеринга.
Однако если часть памяти очищается в ответ на события обрезки памяти, такие как onTrimMemory()
, то эти объекты необходимо создать заново в ответ на событие горячего запуска.
Горячий старт отображает то же поведение на экране, что и сценарий холодного старта. Системный процесс отображает пустой экран, пока приложение не завершит рендеринг активности.
Как определить запуск приложения в Perfetto
Для отладки проблем запуска приложения полезно определить, что именно включено в фазу запуска приложения. Чтобы определить всю фазу запуска приложения в Perfetto , выполните следующие действия:
В Perfetto найдите строку с производной метрикой Android App Startups. Если вы ее не видите, попробуйте захватить трассировку с помощью приложения трассировки системы на устройстве .
Рисунок 3. Срез метрики Android App Startups, полученный в Perfetto. Щелкните связанный срез и нажмите m , чтобы выбрать срез. Скобки появляются вокруг среза и обозначают, сколько времени это заняло. Длительность также отображается на вкладке Текущий выбор .
Закрепите строку «Запуск приложений Android», нажав на значок булавки, который отображается при наведении указателя мыши на строку.
Прокрутите страницу до строки с нужным приложением и щелкните первую ячейку, чтобы развернуть строку.
Увеличьте масштаб основной ветки, обычно находящейся вверху, нажав w (нажмите s, a, d для уменьшения масштаба, перемещения влево и перемещения вправо соответственно).
Рисунок 4. Срез метрики Android App Startups рядом с основным потоком приложения. Срез полученных метрик упрощает просмотр того, что именно включено в запуск приложения, что позволяет продолжить отладку более подробно.
Используйте метрики для проверки и улучшения стартапов
Чтобы правильно диагностировать производительность времени запуска, вы можете отслеживать показатели, которые показывают, сколько времени требуется вашему приложению для запуска. Android предоставляет несколько способов показать вам, что у вашего приложения есть проблема, и помочь вам диагностировать ее. Android Vitals может предупредить вас о возникновении проблемы, а диагностические инструменты могут помочь вам диагностировать проблему.
Преимущества использования показателей стартапа
Android использует метрики времени до начального отображения (TTID) и времени до полного отображения (TTFD) для оптимизации холодных и теплых запусков приложений. Android Runtime (ART) использует данные из этих метрик для эффективной предварительной компиляции кода для оптимизации будущих запусков.
Более быстрый запуск обеспечивает более устойчивое взаимодействие пользователя с вашим приложением, что сокращает количество случаев преждевременного выхода, перезапуска экземпляра или перехода к другому приложению.
Android Vitals
Android Vitals может помочь повысить производительность вашего приложения, оповещая вас в Play Console, если время запуска вашего приложения становится слишком большим.
Android Vitals считает следующие значения времени запуска вашего приложения чрезмерными:
- Холодный запуск занимает 5 секунд или дольше.
- Теплый запуск занимает 2 секунды или дольше.
- Горячий запуск занимает 1,5 секунды или дольше.
Android Vitals использует метрику времени до начального отображения (TTID) . Информацию о том, как Google Play собирает данные Android Vitals, см. в документации Play Console .
Время до первоначального отображения
Время до начального отображения (TTID) — это время, необходимое для отображения первого кадра пользовательского интерфейса приложения. Эта метрика измеряет время, необходимое приложению для создания своего первого кадра, включая инициализацию процесса во время холодного запуска, создание активности во время холодного или теплого запуска и отображение первого кадра. Поддержание низкого значения TTID вашего приложения помогает улучшить пользовательский опыт, позволяя пользователям быстро увидеть запуск вашего приложения. TTID автоматически сообщается для каждого приложения Android Framework. При оптимизации для запуска приложения мы рекомендуем реализовать reportFullyDrawn
, чтобы получить информацию до TTFD .
TTID измеряется как значение времени, представляющее собой общее прошедшее время, включающее следующую последовательность событий:
- Запуск процесса.
- Инициализация объектов.
- Создание и инициализация активности.
- Раздувание макета.
- Рисую приложение в первый раз.
Получить TTID
Чтобы найти TTID, найдите в командной строке Logcat строку вывода, содержащую значение Displayed
. Это значение является TTID и выглядит примерно так, как в следующем примере, где TTID равен 3s534ms:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
Чтобы найти TTID в Android Studio, отключите фильтры в представлении Logcat из раскрывающегося списка фильтров, а затем найдите Displayed
время, как показано на рисунке 5. Отключение фильтров необходимо, поскольку этот журнал обслуживает системный сервер, а не само приложение.

Displayed
значение в logcat. Метрика Displayed
в выходных данных Logcat не обязательно фиксирует количество времени, пока все ресурсы не будут загружены и отображены. Она не учитывает ресурсы, на которые нет ссылок в файле макета или которые приложение создает как часть инициализации объекта. Она исключает эти ресурсы, поскольку их загрузка является встроенным процессом и не блокирует начальное отображение приложения.
Иногда строка Displayed
в выводе Logcat содержит дополнительное поле для общего времени. Например:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)
В этом случае первое измерение времени относится только к активности, которая отображается первой. total
измерение времени начинается с момента запуска процесса приложения и может включать другую активность, которая запускается первой, но не отображает ничего на экране. total
измерение времени отображается только в том случае, если есть разница между временем запуска отдельной активности и общим временем запуска.
Мы рекомендуем использовать Logcat в Android Studio, но если вы не используете Android Studio, вы также можете измерить TTID, запустив свое приложение с помощью команды adb
shell activity manager . Вот пример:
adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN
Displayed
метрика отображается в выходных данных Logcat, как и прежде. В окне терминала отображается следующее:
Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete
Аргументы -c
и -a
являются необязательными и позволяют указать <category>
и <action>
.
Время до полного отображения
Время до полного отображения (TTFD) — это время, необходимое для того, чтобы приложение стало интерактивным для пользователя. Оно указывается как время, необходимое для отображения первого кадра пользовательского интерфейса приложения, а также контента, который загружается асинхронно после отображения начального кадра. Как правило, это основной контент, загружаемый из сети или с диска, о котором сообщает приложение. Другими словами, TTFD включает TTID, а также время, необходимое для того, чтобы приложение стало пригодным для использования. Поддержание низкого значения TTFD вашего приложения помогает улучшить пользовательский опыт, позволяя пользователям быстро взаимодействовать с вашим приложением.
Система определяет TTID, когда Choreographer
вызывает метод onDraw()
активности, и когда он знает, что вызывает это в первый раз. Однако система не знает, когда определять TTFD, поскольку каждое приложение ведет себя по-разному. Чтобы определить TTFD, приложение должно подать сигнал системе, когда оно достигнет полностью нарисованного состояния.
Получить TTFD
Чтобы найти TTFD, сообщите о полностью нарисованном состоянии, вызвав метод reportFullyDrawn()
ComponentActivity
. Метод reportFullyDrawn
сообщает, когда приложение полностью нарисовано и находится в пригодном для использования состоянии. TTFD — это время, прошедшее с момента получения системой намерения запуска приложения до момента вызова reportFullyDrawn()
. Если вы не вызываете reportFullyDrawn()
, значение TTFD не сообщается.
Чтобы измерить TTFD, вызовите reportFullyDrawn()
после того, как вы полностью отрисуете UI и все данные. Не вызывайте reportFullyDrawn()
до того, как окно первой активности будет впервые отрисовано и отображено, как измерено системой, потому что тогда система сообщит измеренное системой время. Другими словами, если вы вызовете reportFullyDrawn()
до того, как система обнаружит TTID, система сообщит и TTID, и TTFD как одно и то же значение, и это значение будет значением TTID.
При использовании reportFullyDrawn()
Logcat отображает вывод, подобный следующему примеру, в котором TTFD составляет 1с54мс:
system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
Вывод Logcat иногда включает в себя total
время, как обсуждалось в разделе Время до первоначального отображения .
Если время отображения информации медленнее, чем хотелось бы, можно попытаться выявить узкие места в процессе запуска.
Вы можете использовать reportFullyDrawn()
для сигнализации о полностью нарисованном состоянии в основных случаях, когда вы знаете, что полностью нарисованное состояние достигнуто. Однако в случаях, когда фоновые потоки должны завершить фоновую работу до достижения полностью нарисованного состояния, вам необходимо отложить reportFullyDrawn()
для более точного измерения TTFD. Чтобы узнать, как отложить reportFullyDrawn()
, см. следующий раздел.
Улучшить точность времени запуска
Если ваше приложение выполняет отложенную загрузку и начальное отображение не включает все ресурсы, например, когда ваше приложение извлекает изображения из сети, вы можете отложить вызов reportFullyDrawn
до тех пор, пока ваше приложение не станет пригодным для использования, чтобы вы могли включить заполнение списка в качестве части вашего эталонного времени.
Например, если пользовательский интерфейс содержит динамический список, такой как RecyclerView
или ленивый список, он может быть заполнен фоновой задачей, которая завершается после того, как список впервые отрисован и, следовательно, после того, как пользовательский интерфейс отмечен как полностью отрисованный. В таких случаях заполнение списка не включается в бенчмаркинг.
Чтобы включить заполнение списка в качестве части вашего эталонного времени, получите FullyDrawnReporter
с помощью getFullyDrawnReporter()
и добавьте к нему репортер в коде вашего приложения. Отпустите репортер после того, как фоновая задача завершит заполнение списка.
FullyDrawnReporter
не вызывает метод reportFullyDrawn()
пока не будут освобождены все добавленные репортеры. При добавлении репортера до завершения фонового процесса время также включает время, необходимое для заполнения списка в данных времени запуска. Это не меняет поведение приложения для пользователя, но позволяет данным времени запуска включать время, необходимое для заполнения списка. reportFullyDrawn()
не вызывается, пока не будут завершены все задачи, независимо от порядка.
В следующем примере показано, как можно одновременно запускать несколько фоновых задач, при этом каждая из них регистрирует своего собственного репортера:
Котлин
class MainActivity : ComponentActivity() {
sealed interface ActivityState {
data object LOADING : ActivityState
data object LOADED : ActivityState
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var activityState by remember {
mutableStateOf(ActivityState.LOADING as ActivityState)
}
fullyDrawnReporter.addOnReportDrawnListener {
activityState = ActivityState.LOADED
}
ReportFullyDrawnTheme {
when(activityState) {
is ActivityState.LOADING -> {
// Display the loading UI.
}
is ActivityState.LOADED -> {
// Display the full UI.
}
}
}
SideEffect {
fullyDrawnReporter.addReporter()
lifecycleScope.launch(Dispatchers.IO) {
// Perform the background operation.
fullyDrawnReporter.removeReporter()
}
fullyDrawnReporter.addReporter()
lifecycleScope.launch(Dispatchers.IO) {
// Perform the background operation.
fullyDrawnReporter.removeReporter()
}
}
}
}
}
Ява
public class MainActivity extends ComponentActivity {
private FullyDrawnReporter fullyDrawnReporter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fullyDrawnReporter = getFullyDrawnReporter();
fullyDrawnReporter.addOnReportDrawnListener(() -> {
// Trigger the UI update.
return Unit.INSTANCE;
});
new Thread(new Runnable() {
@Override
public void run() {
fullyDrawnReporter.addReporter();
// Do the background work.
fullyDrawnReporter.removeReporter();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
fullyDrawnReporter.addReporter();
// Do the background work.
fullyDrawnReporter.removeReporter();
}
}).start();
}
}
Если ваше приложение использует Jetpack Compose, вы можете использовать следующие API для указания полностью отрисованного состояния:
-
ReportDrawn
: указывает, что ваш компонуемый объект немедленно готов к взаимодействию. -
ReportDrawnWhen
: принимает предикат, напримерlist.count > 0
, чтобы указать, когда ваш компонуемый объект готов к взаимодействию. -
ReportDrawnAfter
: принимает метод приостановки, который после завершения указывает, что ваш компонуемый объект готов к взаимодействию.
Определить узкие места
Для поиска узких мест можно использовать Android Studio CPU Profiler. Для получения дополнительной информации см. Inspect CPU activity with CPU Profiler .
Вы также можете получить представление о потенциальных узких местах с помощью встроенной трассировки внутри методов onCreate()
ваших приложений и действий. Чтобы узнать о встроенной трассировке, см. документацию по функциям Trace
и обзор системной трассировки .
Решайте общие проблемы
В этом разделе обсуждаются несколько проблем, которые часто влияют на производительность запуска приложения. Эти проблемы в основном касаются инициализации объектов приложения и активности, а также загрузки экранов.
Тяжелая инициализация приложения
Производительность запуска может пострадать, когда ваш код переопределяет объект Application
и выполняет тяжелую работу или сложную логику при инициализации этого объекта. Ваше приложение может тратить время во время запуска, если ваши подклассы Application
выполняют инициализации, которые пока не нужно выполнять.
Некоторые инициализации могут быть совершенно ненужными, например, при инициализации информации о состоянии для основной активности, когда приложение фактически запускается в ответ на намерение. С намерением приложение использует только подмножество ранее инициализированных данных о состоянии.
Другие проблемы во время инициализации приложения включают в себя события сборки мусора, которые оказывают влияние или многочисленны, или дисковый ввод-вывод, происходящий одновременно с инициализацией, что еще больше блокирует процесс инициализации. Сборка мусора является особенно важным со средой выполнения Dalvik; Android Runtime (ART) выполняет сборку мусора одновременно, минимизируя влияние этой операции.
Диагностировать проблему
Чтобы попытаться диагностировать проблему, можно использовать трассировку методов или встроенную трассировку.
Метод отслеживания
Запуск CPU Profiler показывает, что метод callApplicationOnCreate()
в конечном итоге вызывает ваш метод com.example.customApplication.onCreate
. Если инструмент показывает, что эти методы долго выполняются, исследуйте дальше, чтобы увидеть, какая работа там происходит.
Встроенная трассировка
Используйте встроенную трассировку для расследования вероятных виновников, включая следующие:
- Начальная функция
onCreate()
вашего приложения. - Любые глобальные одиночные объекты, инициализируемые вашим приложением.
- Любой дисковый ввод-вывод, десериализация или замкнутые циклы, которые могут возникнуть во время узкого места.
Решения проблемы
Независимо от того, связана ли проблема с ненужными инициализациями или с дисковым вводом/выводом, решением является ленивая инициализация. Другими словами, инициализируйте только те объекты, которые нужны немедленно. Вместо создания глобальных статических объектов перейдите к шаблону singleton, в котором приложение инициализирует объекты только в первый раз, когда они ему нужны.
Также рассмотрите возможность использования фреймворка внедрения зависимостей, например Hilt , который создает объекты и зависимости при их первом внедрении.
Если ваше приложение использует поставщиков контента для инициализации компонентов приложения при запуске, рассмотрите возможность использования библиотеки App Startup .
Инициализация тяжелой активности
Создание активности часто влечет за собой много высоконакладных расходов. Часто есть возможности оптимизировать эту работу, чтобы добиться улучшения производительности. К таким распространенным проблемам относятся следующие:
- Раздувание больших или сложных макетов.
- Блокировка отрисовки экрана на диске или сетевого ввода-вывода.
- Загрузка и декодирование растровых изображений.
- Растеризация объектов
VectorDrawable
. - Инициализация других подсистем деятельности.
Диагностировать проблему
В этом случае также может быть полезна как трассировка методов, так и встроенная трассировка.
Метод отслеживания
При использовании CPU Profiler обратите внимание на конструкторы подкласса Application
вашего приложения и методы com.example.customApplication.onCreate()
.
Если инструмент показывает, что выполнение этих методов занимает много времени, изучите проблему более подробно, чтобы узнать, какая работа там выполняется.
Встроенная трассировка
Используйте встроенную трассировку для расследования вероятных виновников, включая следующие:
- Начальная функция
onCreate()
вашего приложения. - Любые глобальные одиночные объекты, которые он инициализирует.
- Любой дисковый ввод-вывод, десериализация или замкнутые циклы, которые могут возникнуть во время узкого места.
Решения проблемы
Потенциально узких мест может быть много, но две наиболее распространенные проблемы и способы их устранения следующие:
- Чем больше ваша иерархия представлений, тем больше времени требуется приложению для ее раздувания. Два шага, которые вы можете предпринять для решения этой проблемы, следующие:
- Уменьшите количество избыточных или вложенных макетов, сделав иерархию представлений более простой.
- Не раздувайте части пользовательского интерфейса, которые не должны быть видны во время запуска. Вместо этого используйте объект
ViewStub
в качестве заполнителя для подиерархий, которые приложение может раздуть в более подходящее время.
- Инициализация всех ресурсов в основном потоке также может замедлить запуск. Вы можете решить эту проблему следующим образом:
- Перенесите всю инициализацию ресурсов, чтобы приложение могло выполнять ее лениво в другом потоке.
- Позвольте приложению загрузить и отобразить ваши представления, а затем обновите визуальные свойства, зависящие от растровых изображений и других ресурсов.
Пользовательские заставки
Вы можете заметить дополнительное время запуска, если ранее вы использовали один из следующих методов для реализации пользовательского экрана-заставки в Android 11 (уровень API 30) или более ранних версиях:
- Использование атрибута темы
windowDisablePreview
для отключения начального пустого экрана, отрисовываемого системой во время запуска. - Используя специальную
Activity
.
Начиная с Android 12, требуется переход на API SplashScreen
. Этот API обеспечивает более быстрое время запуска и позволяет настраивать экран-заставку следующими способами:
- Установите тему , чтобы изменить внешний вид экрана-заставки.
- Управляйте длительностью отображения заставки с помощью
windowSplashScreenAnimationDuration
. - Настройте анимацию заставки и изящно управляйте анимацией закрытия заставки.
Кроме того, библиотека Compat портирует API SplashScreen
для обеспечения обратной совместимости и создания единого внешнего вида и поведения экрана-заставки во всех версиях Android.
Подробную информацию смотрите в руководстве по миграции экрана-заставки .
{% дословно %}Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Медленная отрисовка
- Захват макро-бенчмарк-показателей
- Создать базовые профили{:#creating-profile-rules}