lifecycleScope — це вбудований CoroutineScope з бібліотеки androidx.lifecycle, який прив’язаний до життєвого циклу Activity, Fragment або будь-якого LifecycleOwner і автоматично скасовує корутини при знищенні компонента. За даними Google Android Developers, 2025, lifecycleScope дозволяє безпечно запускати корутини, пов’язані з UI-шарем, без ризику виконати код після знищення Activity або Fragment. Scope автоматично скасовується при переході LifecycleOwner в стан DESTROYED.
Головне
lifecycleScope — це extension-властивість на інтерфейс LifecycleOwner (Activity, Fragment, Service), що надає готовий CoroutineScope, прив’язаний до повного життєвого циклу компонента. Коли LifecycleOwner досягає стану DESTROYED, lifecycleScope автоматично скасовує всі активні корутини.
// In Fragment or Activity
lifecycleScope.launch {
delay(1000)
showSnackbar("Привіт!")
}
На відміну від viewModelScope, lifecycleScope скасовується при кожному знищенні LifecycleOwner — включаючи поворот екрана. Це робить його ідеальним для операцій, які повинні жити тільки поки видно конкретний екран.
lifecycleScope доступний скрізь, де є LifecycleOwner:
Механізм автоматичного скасування lifecycleScope заснований на підписці на події Lifecycle. Коли Lifecycle опускається нижче CREATED в DESTROYED, scope скасовується.
| Стан | Опис | Scope активний |
|---|---|---|
| CREATED | LifecycleOwner створений, onCreate виконано | Так |
| STARTED | LifecycleOwner видимий (onStart) | Так |
| RESUMED | LifecycleOwner на передньому плані (onResume) | Так |
| DESTROYED | LifecycleOwner знищений (onDestroy) | Ні (scope скасовано) |
lifecycleScope створюється як CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) і зберігається всередині Lifecycle. При переході Lifecycle в стан DESTROYED викликається scope.cancel(). Механізм реалізований через LifecycleEventObserver, який підписується на події життєвого циклу при першому зверненні до scope.
При повороті екрана Activity знищується (onDestroy) і створюється заново. lifecycleScope скасовується разом зі старою Activity, і новий екземпляр scope створюється для нової Activity. Це принципова відмінність від viewModelScope, який зберігається при повороті.
Бібліотека lifecycle надає кілька способів запуску корутин через lifecycleScope. Розглянемо еволюцію API від застарілих методів до сучасних.
Найпростіший спосіб — lifecycleScope.launch { ... }. Корутина запускається негайно і скасовується при DESTROYED. Однак вона може виконувати код навіть коли UI невидимий (наприклад, у фоні після onStop). Це не завжди бажано.
Ці методи призупиняли виконання корутини, коли Lifecycle опускався нижче вказаного стану, і відновлювали при поверненні. Однак вони були позначені як @Deprecated в lifecycle-runtime-ktx 2.6.0, тому що:
repeatOnLifecycle — рекомендований Google спосіб запуску корутин, синхронізованих з життєвим циклом. Він скасовує і перезапускає корутину кожного разу, коли Lifecycle досягає заданого стану.
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
updateUI(state)
}
}
}
Корутина, передана в repeatOnLifecycle, запускається коли Lifecycle досягає STARTED, і скасовується коли він опускається нижче STARTED. При поверненні в STARTED корутина перезапускається заново. Це безпечно і ефективно — жодні корутини не висять у паузі.
Для збору даних з Flow з урахуванням життєвого циклу існує оператор flowWithLifecycle. Він автоматично зупиняє і відновлює збір при зміні стану Lifecycle:
viewModel.uiState
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.onEach { state -> updateUI(state) }
.launchIn(lifecycleScope)
Оператор flowWithLifecycle — найбільш лаконічний спосіб безпечної підписки на Flow в UI-шарі.
Розглянемо три реальних сценарії застосування lifecycleScope в Android-додатку на Kotlin.
class MapFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
locationProvider.observeLocation().collect { loc ->
updateMapMarker(loc)
}
}
}
}
}
Корутина стартує коли фрагмент стає видимим (STARTED) і скасовується коли він йде з екрану (STOPPED). Якщо користувач перемикається на інший додаток, Location-оновлення не споживають батарею.
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
animateFadeIn(titleView)
delay(200)
animateSlideUp(contentView)
}
}
Анімація запускається тільки коли фрагмент на передньому плані (RESUMED). Якщо користувач згортає додаток під час анімації, корутина скасовується, і при поверненні анімація запускається заново.
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
while (isActive) {
syncData()
delay(30_000L)
}
}
}
Дані синхронізуються кожні 30 секунд, але тільки коли екран видимий. isActive перевіряє, чи не скасована корутина, що забезпечує можливість безпечного виходу з циклу при відході з екрану.
Обидва scope прив’язані до життєвого циклу, але до різних його аспектів. Розуміння різниці критично важливо для правильної архітектури Android-додатків.
viewModelScope прив’язаний до ViewModel, яка переживає поворот екрана. lifecycleScope прив’язаний до LifecycleOwner (Activity/Fragment), який при повороті знищується і створюється заново. Це визначає сценарії їх застосування.
На практиці часто зустрічається комбінація обох scope: viewModelScope завантажує дані і керує станом, lifecycleScope підписується на Flow з ViewModel з урахуванням життєвого циклу екрану. Цей розподіл відповідальності вважається best practice в сучасній Android-розробці.
Розглянемо чотири найбільш часті помилки, які допускають розробники при використанні lifecycleScope.
Якщо запустити завантаження даних в lifecycleScope.launch, при повороті екрана корутина скасовується, і дані доведеться завантажувати заново. Використовуйте viewModelScope для довгоживучих операцій. lifecycleScope — тільки для UI-прив’язаних завдань.
Прямий виклик viewModel.someFlow.collect { ... } всередині lifecycleScope.launch продовжує збирати дані навіть коли екран невидимий. Це може призвести до оновлення UI у фоні та зайвих накладних витрат. Завжди використовуйте repeatOnLifecycle або flowWithLifecycle.
Хоча lifecycleScope скасовується при DESTROYED, код після точки призупинення (suspend) може не виконатися при раптовому скасуванні. Не покладайтеся на виконання пост-коду після suspend-виклику, якщо тільки не використовуєте NonCancellable.
launchWhenStarted та його аналоги не скасовують корутину, а тільки призупиняють. Якщо екран багато разів перемикається між переднім і заднім планом, корутина накопичує відкладені виклики. Переходьте на repeatOnLifecycle — це єдиний правильний спосіб синхронізації з Lifecycle.
Часто задавані питання
lifecycleScope автоматично скасовується при знищенні LifecycleOwner. GlobalScope живе весь час роботи додатку. Корутина в lifecycleScope не може оновлювати UI після знищення компонента, в GlobalScope — може, що призводить до падінь. Завжди використовуйте lifecycleScope в UI-шарі.
Ні, ViewModel не є LifecycleOwner, тому lifecycleScope в ній недоступний. ViewModel використовує viewModelScope. Якщо код повинен виконуватися в обох контекстах — винесіть логіку в use case або repository з suspend-функціями.
Кожен виклик repeatOnLifecycle створює нову корутину, яка запускає блок при досягненні вказаного стану Lifecycle. Якщо викликати repeatOnLifecycle двічі для одного стану, обидва блоки будуть запущені незалежно. Зазвичай достатньо одного виклику в onViewCreated.
Безпосередньо змінити диспетчер lifecycleScope не можна — він використовує Dispatchers.Main.immediate. Всередині блоку корутини можна перемкнутися на інший диспетчер через withContext. Для тестів використовуйте TestDispatcher з LifecycleOwner.
lifecycleScope скасовується при переході LifecycleOwner в стан DESTROYED (після onDestroy). Прості виклики lifecycleScope.launch не скасовуються в onPause або onStop. Для паузи при відході у фон використовуйте repeatOnLifecycle(STARTED) або repeatOnLifecycle(RESUMED).
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також