lifecycleScope — е вграден CoroutineScope от библиотеката androidx.lifecycle, който е свързан с жизнения цикъл на Activity, Fragment или всеки LifecycleOwner и автоматично отменя корутините при унищожаване на компонента. Според Google Android Developers, 2025, lifecycleScope позволява безопасно стартиране на корутини, свързани с UI слоя, без риск от изпълнение на код след унищожаване на Activity или Fragment. Scope автоматично се отменя при преминаване на LifecycleOwner в състояние DESTROYED.
Основни точки
lifecycleScope — е разширяващо свойство на интерфейса LifecycleOwner (Activity, Fragment, Service), което предоставя готов CoroutineScope, свързан с пълния жизнен цикъл на компонента. Когато LifecycleOwner достигне състояние DESTROYED, lifecycleScope автоматично отменя всички активни корутини.
// В Fragment или 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). Ако потребителят превключи към друго приложение, актуализациите на местоположение не консумират батерия.
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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също