> Как управлять жизненным циклом корутины в Kotlin (Kotlin, Android)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: IBS

Стек: Kotlin, Android

> Пример ответа

Короткий ответ

Жизненный цикл корутины управляется через Job и CoroutineScope. Основные инструменты: отмена через cancel(), отслеживание состояния через isActive, ensureActive() и CoroutineExceptionHandler. На Android жизненный цикл привязывается к ViewModel через viewModelScope или к LifecycleOwner через lifecycleScope. Ключевой принцип - корутина должна быть отменена, когда её результат больше не нужен, чтобы избежать утечек памяти и лишней работы.

Подробное объяснение

Корутина в Kotlin - это легковесная единица конкурентности, которая выполняется в рамках CoroutineScope. Жизненный цикл корутины состоит из состояний: New, Active, Completing, Completed, Cancelling, Cancelled. Управление происходит через Job - дескриптор корутины, который возвращается при запуске.

Основные методы управления:

  • job.cancel() - запрашивает отмену. Корутина получит CancellationException в ближайшей точке приостановки.
  • job.cancel(cause) - отмена с указанием причины.
  • job.join() - ожидает завершения корутины.
  • job.cancelAndJoin() - отменяет и ожидает завершения.

Важно понимать: отмена - это кооперативный процесс. Корутина должна проверять состояние отмены. Это делается через:

  • isActive - свойство CoroutineScope или Job.
  • ensureActive() - бросает CancellationException, если корутина отменена.
  • yield() - приостанавливает выполнение и проверяет отмену.

Структурированная конкурентность гарантирует, что дочерние корутины автоматически отменяются при отмене родительской. Это ключевой механизм управления жизненным циклом.

На Android используются предопределённые скоупы:

  • viewModelScope - отменяется при очистке ViewModel.
  • lifecycleScope - привязан к LifecycleOwner, отменяется при уничтожении.
  • repeatOnLifecycle - для реактивных потоков в UI.

На практике

При работе с Android важно привязывать корутины к правильному скоупу. Для UI-операций используйте lifecycleScope с repeatOnLifecycle(Lifecycle.State.STARTED). Для бизнес-логики - viewModelScope. Не создавайте собственные скоупы без необходимости - это частая причина утечек.

При отмене корутины, которая выполняет блокирующие операции (например, чтение из БД), нужно убедиться, что операция прерываемая. Для этого используйте withContext(Dispatchers.IO) и проверяйте isActive внутри циклов.

Обработка ошибок: CoroutineExceptionHandler перехватывает исключения, которые не были обработаны внутри корутины. Для viewModelScope исключения по умолчанию приводят к краху приложения, поэтому всегда обрабатывайте ошибки внутри корутины через try-catch или runCatching.

Пример кода

class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            try {
                val result = fetchData()
                _uiState.value = UiState.Success(result)
            } catch (e: CancellationException) {
                throw e // обязательно пробрасываем, чтобы не сломать отмену
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e)
            }
        }
    }
}

// Отмена с проверкой внутри цикла
fun processLargeList(items: List<Int>) {
    viewModelScope.launch {
        for (item in items) {
            ensureActive() // бросает CancellationException при отмене
            processItem(item)
        }
    }
}

// Привязка к lifecycle
class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    render(state)
                }
            }
        }
    }
}

Как отвечать на собеседовании

Начните с определения Job и CoroutineScope, затем объясните кооперативную природу отмены. Подчеркните важность структурированной конкурентности. Приведите пример из Android с viewModelScope. Обязательно упомяните CancellationException и почему его нельзя проглатывать. Если спросят про сложные сценарии - расскажите про SupervisorJob, NonCancellable и withTimeout.

Что проверяет интервьюер

Интервьюер оценивает:

  • Понимаете ли вы разницу между cancel() и join().
  • Знаете ли вы, что отмена кооперативная и как это работает.
  • Умеете ли вы обрабатывать отмену в реальном коде.
  • Понимаете ли вы, как скоупы связаны с жизненным циклом Android.
  • Знаете ли вы про CancellationException и его роль.

Типичные ошибки

  • Проглатывание CancellationException в catch - ломает отмену.
  • Создание собственного CoroutineScope в Activity без отмены в onDestroy.
  • Использование GlobalScope - антипаттерн.
  • Запуск корутин в Dispatchers.Main для тяжёлых операций.
  • Игнорирование ensureActive() в длительных циклах.
  • Ожидание, что cancel() мгновенно остановит выполнение - это не так.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью