Warm Start — це сценарій запуску Android-додатку, при якому процес додатку вже існує в пам'яті (наприклад, після згортання), але Activity була знищена системою для економії ресурсів. Application.onCreate вже виконано, класи завантажено, але UI створюється заново. За даними Google, 2024, Warm Start займає від 200 до 800 мс і становить близько 40% всіх запусків на пристроях з 4 ГБ ОЗП.
Головне
Warm Start — це стан між Cold Start і Hot Start: процес додатку існує в пам'яті (іноді в фоновому кеші Linux), але Activity неактивна і буде створена заново. Коли Android не вистачає оперативної пам'яті, він може вивантажити Activity зі стеку, залишивши процес живим. Коли користувач повертається в додаток, відбувається Warm Start: створюється новий екземпляр Activity, виконуються lifecycle-методи onCreate → onStart → onResume, але Application.onCreate і завантаження класів пропускаються.
Android приймає рішення про вивантаження Activity на основі пріоритету процесу (importance rank). Activity у фоні (рівень PROCESS_STATE_IMPORTANT_FOREGROUND або PROCESS_STATE_TOP_SLEEPING) може бути знищена через 5–30 хвилин після згортання додатку, залежно від доступної ОЗП. На пристроях з 3 ГБ ОЗП Activity може бути вивантажена вже через 10 хвилин, на пристроях з 8 ГБ ОЗП — через кілька годин. Важливо: при Warm Start onSaveInstanceState викликається до знищення Activity, і розробник може зберегти стан UI.
Користувач не бачить різниці між Warm і Cold Start — він просто натискає на іконку додатку і чекає. Однак при Warm Start білий екран може з'явитися, якщо додаток не налаштував власний theme для стартового вікна. Google рекомендує встановити кастомну тему в маніфесті (Theme.AppCompat.Light або Theme.Material3.DayNight) для стартової Activity, щоб уникнути мерехтіння білого/чорного екрану при Warm Start. На Android 12+ SplashScreen API також приховує цей ефект.
Розуміння різниці між трьома типами запуску необхідне для вибору правильної стратегії профілювання та оптимізації. Кожен тип має свою тривалість, свої вузькі місця та свої інструменти вимірювання.
| Критерій | Cold Start | Warm Start | Hot Start |
|---|---|---|---|
| Процес | Створюється заново | Існує в пам'яті | Існує в пам'яті |
| Application.onCreate | Виконується | Не виконується | Не виконується |
| Activity | Створюється з нуля | Створюється з нуля | Відновлюється зі стеку |
| Час | 1–5 секунд | 200–800 мс | < 200 мс |
| onCreate Activity | Повний | Повний (з restore) | Пропускається |
На практиці Warm Start становить від 30% до 60% всіх запусків додатку, залежно від звичок користувача та об'єму ОЗП пристрою. Користувачі, які тримають багато додатків відкритими (multitasker), частіше стикаються з Warm Start. Для соціальних мереж і месенджерів Warm Start — найчастіший сценарій, оскільки додаток весь час у фоні. Для банківських додатків, навпаки, переважає Cold Start (примусове очищення процесу з міркувань безпеки).
Warm Start складається з трьох фаз, кожна з яких може бути виміряна та оптимізована. На відміну від Cold Start, тут немає фази fork і завантаження класів, але є фаза відновлення стану (restore), яка може бути дорогою.
Система перевіряє, чи є у додатку theme для стартового вікна. Якщо тема не встановлена, відображається білий (або чорний, залежно від системи) екран. Якщо тема встановлена, відображається background з теми. Ця фаза займає 10–30 мс, але візуально відчутна, якщо тема не збігається з реальним UI додатку. Використовуйте Theme.Material3.DayNight з кастомним windowBackground, колір якого збігається з фоном першого екрану — це створює ефект миттєвого завантаження.
Система викликає onCreate з передачею Bundle savedInstanceState, який був збережений в onSaveInstanceState перед знищенням Activity. Якщо додаток правильно зберіг стан (текст полів, позицію скролу, дані ViewModel), відновлення відбувається швидко. Якщо ні — Activity починається з чистого аркуша і користувач бачить лоадер, поки дані підвантажуються. Ключовий момент: ViewModel-об'єкти переживають Warm Start тільки в тому випадку, якщо процес не був знищений — при Warm Start ViewModel зберігається в пам'яті.
Після onCreate виконується onStart → onResume, і система викликає першу відрисовку. TTFD (Time To First Draw) для Warm Start повинен бути менше 300 мс на середньому пристрої. Якщо перший екран містить складний RecyclerView з важкими View або завантажує зображення по мережі, TTFD може перевищити поріг. Використовуйте Placeholder та Shimmer для плавного завантаження контенту після першого кадру.
Вимірювання Warm Start складніше ніж Cold Start, оскільки потрібно симулювати стан «процес живий, Activity знищена». Стандартна команда ADB з прапорцем -S не підходить — вона вбиває процес. Для Warm Start використовуйте інші підходи.
Спочатку запустіть додаток через adb shell monkey або tap на іконку, потім згорніть його (adb shell input keyevent 3 keyevent HOME). Зачекайте 5–10 секунд, щоб система могла вивантажити Activity, і запустіть adb shell am start -W (без -S). Команда поверне час старту, який буде коротшим за Cold Start. Для відтворюваності використовуйте скрипт: запустити → почекати → home → почекати → запустити.
# Симуляція Warm Start через ADB
$ adb shell am start -W \
com.example.app/.MainActivity
# Вивід (Warm Start):
# ThisTime: 412 ms
# TotalTime: 412 ms
# WaitTime: 423 ms
Бібліотека androidx.benchmark.macro підтримує вимірювання Warm Start. Для цього в тесті встановіть startupMode = StartupMode.WARM — бібліотека запустить додаток, згорне його, почекає (configurable delay), а потім виміряє повторний запуск. Macrobenchmark робить 10–20 прогонів і обчислює процентилі. В CI/CD можна налаштувати поріг: якщо P50 Warm Start перевищує 600 мс — тест падає. Це дозволяє відстежувати регресії при кожному коміті.
Firebase автоматично розрізняє Cold і Warm Start на основі часу з моменту попереднього закриття додатку. Якщо додаток було відкрито протягом останніх 30 хвилин, Firebase класифікує запуск як Warm. У консолі Firebase ви побачите окремі графіки для кожного типу старту, що дозволяє оцінити ефективність оптимізацій. Наприклад, після впровадження збереження стану в ViewModel можна побачити зниження Warm Start на 30%.
Оптимізація Warm Start зосереджується на двох напрямках: прискорення Activity.onCreate і правильне відновлення стану. Оскільки Application.onCreate і завантаження класів вже виконані, головне вузьке місце — UI-код першого екрану.
Якщо збережений стан (savedInstanceState) містить дані, які потрібно десеріалізувати (Bitmap, String, JSON), робіть це в фоновому потоці. Замість прямого читання з Bundle в onCreate запустіть корутину і покажіть shimmer-екран. На практиці десеріалізація Bundle на середньому пристрої займає 20–100 мс — здається небагато, але для Warm Start це 10–50% всього часу. Використовуйте Saved State Module бібліотеки Jetpack, яка автоматично зберігає та відновлює стан ViewModel в Bundle або базі даних.
Роздуття XML-макету (layout inflation) — один з найдорогих етапів Warm Start. Якщо перший екран використовує складний CoordinatorLayout з AppBar, CollapsingToolbar, NestedScrollView плюс три RecyclerView, час inflation може досягати 300 мс. Рішення: використовуйте ConstraintLayout для плоскої ієрархії, застосовуйте ViewStub для невидимих на старті секцій (bottom sheet, dialog), вмикайте асинхронне роздуття для важких фрагментів через AsyncLayoutInflater. В Jetpack Compose inflation не потрібен, але компіляція Compose-дерева на Warm Start може займати аналогічний час.
При Warm Start дані, які додаток завантажував у попередню сесію, можуть бути вже в кеші: Room-база даних, SharedPreferences, in-memory кеш в ViewModel. Якщо ваш перший екран показує список з сервера, перевіряйте кеш при старті та оновлюйте дані у фоні. Використовуйте стратегію cache-then-network: спочатку відобразити кешовані дані (миттєво), потім оновити з сервера (асинхронно). Це скорочує сприйнятий час Warm Start до 100–200 мс.
// ViewModel з кешуванням для Warm Start
class FeedViewModel : ViewModel() {
private val cache = MutableStateFlow<List<Item>>(emptyList())
init {
// Спочатку кеш, потім мережа
viewModelScope.launch {
cache.emit(db.getItems()) // Warm Start: дані вже в БД
cache.emit(api.fetchItems()) // Оновлення у фоні
}
}
}
Правильне збереження стану — ключовий фактор, що відрізняє хороший Warm Start від поганого. Користувач очікує повернутися в додаток і побачити те ж саме, що залишив — включаючи позицію скролу, текст в полях, вибрані вкладки.
Система викликає onSaveInstanceState при знищенні Activity, але ДО того, як процес може бути вбитий. В Bundle зберігаються тільки прості дані (String, Int, Parcelable, Serializable). Для складних даних використовуйте SavedStateHandle в ViewModel — він автоматично зберігає та відновлює поля при Warm Start. На відміну від onSaveInstanceState, SavedStateHandle працює навіть якщо процес пережив Warm Start (ViewModel не знищується). Приклад: для тексту в EditText використовуйте SavedStateHandle.getLiveData(«text») — текст збережеться та відновиться автоматично.
Якщо при Warm Start процес не був убитий, ViewModel залишається в пам'яті і onCleared не викликається. Це означає, що всі дані, завантажені в попередній сесії, доступні миттєво. Однак якщо процес був убитий (пристрій в deep sleep більше 30 хвилин), ViewModel знищується і створюється заново з SavedStateHandle. Для коректної роботи ViewModel при Warm Start використовуйте SavedStateHandle з полями, які потрібно відновити за будь-якого сценарію. Різниця: ViewModel з @HiltViewModel підтримує SavedStateHandle автоматично.
| Механізм | Процес живий | Процес убитий |
|---|---|---|
| ViewModel | Дані в пам'яті | Знищена, створюється заново |
| SavedStateHandle | Дані в пам'яті | Відновлюються з Bundle |
| onSaveInstanceState | Викликається при вивантаженні Activity | Не викликається |
| Room DB | Кеш доступний | Кеш доступний (диск) |
Одна з найчастіших проблем Warm Start — втрата позиції скролу. Користувач прогорнув стрічку до 50-го елементу, згорнув додаток, повернувся — і бачить початок списку. Рішення: зберігайте layoutManager.onSaveInstanceState (зберігає позицію та offset першого видимого елементу) і відновлюйте його в onRestoreInstanceState. Також можна зберігати останню видиму позицію в SharedPreferences з ключем по даті/часу, щоб при Warm Start швидко відновити позицію.
// Збереження позиції скролу RecyclerView
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(
"rv_state", binding.recyclerView
.layoutManager?.onSaveInstanceState()
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedInstanceState?.getParcelable<Parcelable>("rv_state")
?.let { binding.recyclerView.layoutManager?.onRestoreInstanceState(it) }
}
Два практичних приклади оптимізації Warm Start: використання SavedStateHandle в ViewModel і асинхронне відновлення складних даних після старту.
SavedStateHandle автоматично зберігає поля в Bundle і відновлює їх при Warm Start. Поле профілю користувача (String, JSON) буде відновлено без зайвих запитів до сервера. Якщо процес був убитий, SavedStateHandle завантажить останній збережений стан з Bundle.
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val profile: StateFlow<Profile?>
get() = savedStateHandle
.getStateFlow("profile", null)
fun loadProfile(id: String) {
viewModelScope.launch {
savedStateHandle["profile"] =
api.getProfile(id)
}
}
}
// Warm Start: profile не null, UI без лоадера
// Після завантаження: profile оновлюється в SavedStateHandle
Якщо перший екран містить складний макет (карта, градієнт, кілька списків), використовуйте AsyncLayoutInflater для роздуття важких елементів у фоні. Поки макет роздувається, покажіть placeholder з shimmer-ефектом. Це особливо важливо для Warm Start, де кожна мілісекунда на рахунку. AsyncLayoutInflater працює на фоновому потоці і передає готовий View в callback на головному потоці.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Placeholder-макет для миттєвої відрисовки
setContentView(R.layout.placeholder_shimmer)
// Асинхронне завантаження важкого макету
AsyncLayoutInflater(this).inflate(
R.layout.activity_main_complex,
findViewById(R.id.container)
) { view, resId, parent ->
parent?.removeAllViews()
parent?.addView(view)
}
}
}
Часто задавані питання
Так, якщо в момент Warm Start система вирішить убити процес додатку (наприклад, для звільнення пам'яті під інший додаток), запуск стане Cold Start з нуля. Це трапляється на пристроях з 2–3 ГБ ОЗП при одночасній роботі кількох додатків. Фактично Warm Start гарантований тільки протягом 10–20 хвилин після згортання на пристроях середнього класу.
Так, якщо процес не був убитий, ViewModel зберігається в пам'яті і onCleared не викликається. Це ключова перевага Warm Start: всі дані, завантажені мережеві запити, кеш в ViewModel — доступні миттєво. Якщо процес був убитий, ViewModel створюється заново через ViewModelProvider.Factory або @HiltViewModel, і SavedStateHandle відновлює збережені поля.
Теоретично Warm Start завжди швидший за Cold Start, але на практиці є сценарії, коли різниця мінімальна: якщо Application.onCreate був легким (50 мс), а Activity.onCreate — важким (800 мс), то Warm Start (800 мс) майже дорівнює Cold Start (850 мс). У цьому випадку оптимізувати потрібно не Application, а Activity.onCreate — саме він стає вузьким місцем для Warm Start.
SplashScreen API на Android 12+ показує системний сплеш (іконка на кольоровому фоні) відразу при старті — і для Cold, і для Warm Start. Для Warm Start сплеш відображається всього 100–300 мс, після чого його змінює UI додатку. SplashScreen не прискорює сам запуск, але маскує час створення Activity, покращуючи сприйняття.
Так, тому що Warm Start трапляється в 2–3 рази частіше, ніж Cold Start. Якщо Cold Start займає 1.2 секунди, а Warm Start — 600 мс, то 40% запусків (Warm) все ще займають 0.6 секунди, що відчутно. Оптимізація Warm Start до 200–300 мс дає користувачеві відчуття миттєвого повернення. На пристроях з 6+ ГБ ОЗП Warm Start може становити до 80% всіх запусків, і його оптимізація стає пріоритетом.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також