lifecycleScope — що це, прив’язка до Lifecycle та робота в Android

Автор: IT Sectr Опубліковано: 2026-06-23 Час читання: 9 хв

lifecycleScope — це вбудований CoroutineScope з бібліотеки androidx.lifecycle, який прив’язаний до життєвого циклу Activity, Fragment або будь-якого LifecycleOwner і автоматично скасовує корутини при знищенні компонента. За даними Google Android Developers, 2025, lifecycleScope дозволяє безпечно запускати корутини, пов’язані з UI-шарем, без ризику виконати код після знищення Activity або Fragment. Scope автоматично скасовується при переході LifecycleOwner в стан DESTROYED.

Головне

  • lifecycleScope — CoroutineScope з lifecycle-runtime-ktx, скасовується при стані DESTROYED Lifecycle
  • lifecycleScope.launch — запуск корутини, яка автоматично скасовується при знищенні LifecycleOwner
  • launchWhenStarted / launchWhenResumed — застарілі методи, замінені на repeatOnLifecycle
  • repeatOnLifecycle — сучасний API для запуску корутин, що слідують за конкретним станом Lifecycle
  • Dispatchers.Main.immediate — диспетчер за замовчуванням для lifecycleScope

Що таке lifecycleScope в Android?

lifecycleScope — це extension-властивість на інтерфейс LifecycleOwner (Activity, Fragment, Service), що надає готовий CoroutineScope, прив’язаний до повного життєвого циклу компонента. Коли LifecycleOwner досягає стану DESTROYED, lifecycleScope автоматично скасовує всі активні корутини.

kotlin
// In Fragment or Activity
lifecycleScope.launch {
    delay(1000)
    showSnackbar("Привіт!")
}

На відміну від viewModelScope, lifecycleScope скасовується при кожному знищенні LifecycleOwner — включаючи поворот екрана. Це робить його ідеальним для операцій, які повинні жити тільки поки видно конкретний екран.

Де доступний lifecycleScope

lifecycleScope доступний скрізь, де є LifecycleOwner:

  • Activity — AppCompatActivity успадковує LifecycleOwner
  • Fragment — Fragment успадковує LifecycleOwner
  • LifecycleService — сервіс з життєвим циклом
  • ProcessLifecycleOwner — життєвий цикл всього додатку
  • Кастомні LifecycleOwner — будь-які об’єкти, що реалізують LifecycleOwner

Як працює lifecycleScope: Lifecycle та автоматичне скасування

Механізм автоматичного скасування lifecycleScope заснований на підписці на події Lifecycle. Коли Lifecycle опускається нижче CREATED в DESTROYED, scope скасовується.

Стани Lifecycle

СтанОписScope активний
CREATEDLifecycleOwner створений, onCreate виконаноТак
STARTEDLifecycleOwner видимий (onStart)Так
RESUMEDLifecycleOwner на передньому плані (onResume)Так
DESTROYEDLifecycleOwner знищений (onDestroy)Ні (scope скасовано)

Внутрішній устрій lifecycleScope

lifecycleScope створюється як CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) і зберігається всередині Lifecycle. При переході Lifecycle в стан DESTROYED викликається scope.cancel(). Механізм реалізований через LifecycleEventObserver, який підписується на події життєвого циклу при першому зверненні до scope.

Поведінка при повороті

При повороті екрана Activity знищується (onDestroy) і створюється заново. lifecycleScope скасовується разом зі старою Activity, і новий екземпляр scope створюється для нової Activity. Це принципова відмінність від viewModelScope, який зберігається при повороті.

API lifecycleScope: launch, launchWhen та repeatOnLifecycle

Бібліотека lifecycle надає кілька способів запуску корутин через lifecycleScope. Розглянемо еволюцію API від застарілих методів до сучасних.

lifecycleScope.launch — базовий запуск

Найпростіший спосіб — lifecycleScope.launch { ... }. Корутина запускається негайно і скасовується при DESTROYED. Однак вона може виконувати код навіть коли UI невидимий (наприклад, у фоні після onStop). Це не завжди бажано.

Застарілі: launchWhenCreated / launchWhenStarted / launchWhenResumed

Ці методи призупиняли виконання корутини, коли Lifecycle опускався нижче вказаного стану, і відновлювали при поверненні. Однак вони були позначені як @Deprecated в lifecycle-runtime-ktx 2.6.0, тому що:

  • Не скасовували корутину — тільки призупиняли
  • Призводили до накопичення висячих корутин, що споживають пам’ять
  • Створювали race condition при швидкому перемиканні станів

Сучасний API: repeatOnLifecycle

repeatOnLifecycle — рекомендований Google спосіб запуску корутин, синхронізованих з життєвим циклом. Він скасовує і перезапускає корутину кожного разу, коли Lifecycle досягає заданого стану.

kotlin
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { state ->
            updateUI(state)
        }
    }
}

Корутина, передана в repeatOnLifecycle, запускається коли Lifecycle досягає STARTED, і скасовується коли він опускається нижче STARTED. При поверненні в STARTED корутина перезапускається заново. Це безпечно і ефективно — жодні корутини не висять у паузі.

flowWithLifecycle — для Flow

Для збору даних з Flow з урахуванням життєвого циклу існує оператор flowWithLifecycle. Він автоматично зупиняє і відновлює збір при зміні стану Lifecycle:

kotlin
viewModel.uiState
    .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
    .onEach { state -> updateUI(state) }
    .launchIn(lifecycleScope)

Оператор flowWithLifecycle — найбільш лаконічний спосіб безпечної підписки на Flow в UI-шарі.

Приклади використання lifecycleScope

Розглянемо три реальних сценарії застосування lifecycleScope в Android-додатку на Kotlin.

Приклад 1: Підписка на Location-оновлення тільки коли екран видимий

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-оновлення не споживають батарею.

Приклад 2: Запуск анімації при старті фрагмента

kotlin
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.RESUMED) {
        animateFadeIn(titleView)
        delay(200)
        animateSlideUp(contentView)
    }
}

Анімація запускається тільки коли фрагмент на передньому плані (RESUMED). Якщо користувач згортає додаток під час анімації, корутина скасовується, і при поверненні анімація запускається заново.

Приклад 3: Періодична синхронізація даних на видимому екрані

kotlin
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        while (isActive) {
            syncData()
            delay(30_000L)
        }
    }
}

Дані синхронізуються кожні 30 секунд, але тільки коли екран видимий. isActive перевіряє, чи не скасована корутина, що забезпечує можливість безпечного виходу з циклу при відході з екрану.

lifecycleScope vs viewModelScope: сценарії застосування

Обидва scope прив’язані до життєвого циклу, але до різних його аспектів. Розуміння різниці критично важливо для правильної архітектури Android-додатків.

Ключова відмінність

viewModelScope прив’язаний до ViewModel, яка переживає поворот екрана. lifecycleScope прив’язаний до LifecycleOwner (Activity/Fragment), який при повороті знищується і створюється заново. Це визначає сценарії їх застосування.

Коли використовувати lifecycleScope

  • Підписка на системні події (Location, датчики, камера)
  • Анімації та UI-ефекти, прив’язані до конкретного екрану
  • Чанкова завантаження даних з урахуванням видимості екрана
  • Операції, які повинні припинитися при відході з екрану

Коли використовувати viewModelScope

  • Завантаження даних з Repository
  • Бізнес-логіка, яка повинна пережити поворот
  • Кешування та обробка даних
  • Будь-які операції, результат яких потрібен після повороту

Комбіноване використання

На практиці часто зустрічається комбінація обох scope: viewModelScope завантажує дані і керує станом, lifecycleScope підписується на Flow з ViewModel з урахуванням життєвого циклу екрану. Цей розподіл відповідальності вважається best practice в сучасній Android-розробці.

Типові помилки при роботі з lifecycleScope

Розглянемо чотири найбільш часті помилки, які допускають розробники при використанні lifecycleScope.

Помилка 1: Використання lifecycleScope замість viewModelScope для завантаження даних

Якщо запустити завантаження даних в lifecycleScope.launch, при повороті екрана корутина скасовується, і дані доведеться завантажувати заново. Використовуйте viewModelScope для довгоживучих операцій. lifecycleScope — тільки для UI-прив’язаних завдань.

Помилка 2: Збір Flow без repeatOnLifecycle

Прямий виклик viewModel.someFlow.collect { ... } всередині lifecycleScope.launch продовжує збирати дані навіть коли екран невидимий. Це може призвести до оновлення UI у фоні та зайвих накладних витрат. Завжди використовуйте repeatOnLifecycle або flowWithLifecycle.

Помилка 3: Забули про скасування при відході з екрану

Хоча lifecycleScope скасовується при DESTROYED, код після точки призупинення (suspend) може не виконатися при раптовому скасуванні. Не покладайтеся на виконання пост-коду після suspend-виклику, якщо тільки не використовуєте NonCancellable.

Помилка 4: Використання застарілого launchWhenStarted замість repeatOnLifecycle

launchWhenStarted та його аналоги не скасовують корутину, а тільки призупиняють. Якщо екран багато разів перемикається між переднім і заднім планом, корутина накопичує відкладені виклики. Переходьте на repeatOnLifecycle — це єдиний правильний спосіб синхронізації з Lifecycle.

Часто задавані питання

В чому різниця між lifecycleScope та GlobalScope?

lifecycleScope автоматично скасовується при знищенні LifecycleOwner. GlobalScope живе весь час роботи додатку. Корутина в lifecycleScope не може оновлювати UI після знищення компонента, в GlobalScope — може, що призводить до падінь. Завжди використовуйте lifecycleScope в UI-шарі.

Чи можна використовувати lifecycleScope в ViewModel?

Ні, ViewModel не є LifecycleOwner, тому lifecycleScope в ній недоступний. ViewModel використовує viewModelScope. Якщо код повинен виконуватися в обох контекстах — винесіть логіку в use case або repository з suspend-функціями.

Що станеться при повторному виклику repeatOnLifecycle?

Кожен виклик repeatOnLifecycle створює нову корутину, яка запускає блок при досягненні вказаного стану Lifecycle. Якщо викликати repeatOnLifecycle двічі для одного стану, обидва блоки будуть запущені незалежно. Зазвичай достатньо одного виклику в onViewCreated.

Чи можна задати кастомний Dispatcher для lifecycleScope?

Безпосередньо змінити диспетчер lifecycleScope не можна — він використовує Dispatchers.Main.immediate. Всередині блоку корутини можна перемкнутися на інший диспетчер через withContext. Для тестів використовуйте TestDispatcher з LifecycleOwner.

lifecycleScope скасовується в onPause чи onDestroy?

lifecycleScope скасовується при переході LifecycleOwner в стан DESTROYED (після onDestroy). Прості виклики lifecycleScope.launch не скасовуються в onPause або onStop. Для паузи при відході у фон використовуйте repeatOnLifecycle(STARTED) або repeatOnLifecycle(RESUMED).

Підсумки

  • lifecycleScope — CoroutineScope, прив’язаний до LifecycleOwner і автоматично скасовуваний при DESTROYED через LifecycleEventObserver
  • Dispatchers.Main.immediate — диспетчер за замовчуванням, що забезпечує безпечне оновлення UI без зайвих перемикань
  • repeatOnLifecycle — сучасний API для запуску корутин у заданому стані Lifecycle з автоматичним скасуванням і перезапуском
  • flowWithLifecycle — оператор для безпечного збору Flow з UI з урахуванням життєвого циклу
  • launchWhenStarted застарів — замість призупинення використовуйте repeatOnLifecycle, який скасовує корутину, а не призупиняє
  • lifecycleScope vs viewModelScope — lifecycleScope для UI-операцій (анімації, Location), viewModelScope для даних і бізнес-логіки
  • Поворот екрану — lifecycleScope скасовується при повороті, viewModelScope зберігається; вибирайте scope залежно від завдання

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також