Каков жизненный цикл composable в Jetpack Compose?
Жизненный цикл composable: Enter (появление в дереве), Recompose (перерисовка при изменении State), Leave (удаление из дерева). Не зависит от Android lifecycle. DisposableEffect очищает ресурсы при Leave, LaunchedEffect отменяет корутины.
Жизненный цикл composable в Jetpack Compose
Жизненный цикл composable не совпадает с Android-lifecycle (Activity/Fragment). Он определяется присутствием composable в дереве composition и состоит из трёх этапов: Enter, Recompose, Leave.
Три фазы
- Enter (Initial Composition). Composable впервые появляется в дереве. Инициализируются все
remember-значения, запускаются эффекты сUnit-ключом или первым значением ключа. - Recomposition. Один или несколько читаемых
State-объектов изменились. Compose вызывает функцию повторно, обновляя только затронутые части дерева. При одинаковых входных данных composable может быть пропущен (skipped). - Leave (Disposal). Composable покидает дерево (условие
ifстало false, навигация назад, родитель удалён). Запускается cleanup всехDisposableEffect-блоков, отменяются всеLaunchedEffect-корутины.
Slot Table и идентичность
Compose хранит состояние в Slot Table, индексируя позиции по месту вызова в коде. Composable считается «тем же» между recomposition, если его позиция в дереве не изменилась. При использовании в списках уникальность гарантирует явный key(id) { ... }.
Пример с эффектами жизненного цикла
@Composable
fun LocationTracker(userId: String) {
val context = LocalContext.current
// Запускается при Enter, перезапускается при смене userId, отменяется при Leave
LaunchedEffect(userId) {
trackLocation(userId) // suspend-функция
}
// Подписка на Android LocationManager с cleanup при Leave
DisposableEffect(Unit) {
val listener = LocationListener { location ->
// обработка
}
val manager = context.getSystemService(LocationManager::class.java)
manager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L, 10f, listener
)
onDispose {
manager.removeUpdates(listener) // Cleanup при Leave
}
}
Text("Tracking $userId")
}
Связь с Android lifecycle
Composable и Activity/Fragment живут независимо, но их нужно явно связывать через LocalLifecycleOwner. Например, чтобы паузировать подписку при onStop:
@Composable
fun LifecycleAwareComponent() {
val lifecycle = LocalLifecycleOwner.current.lifecycle
DisposableEffect(lifecycle) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> startWork()
Lifecycle.Event.ON_PAUSE -> stopWork()
else -> Unit
}
}
lifecycle.addObserver(observer)
onDispose { lifecycle.removeObserver(observer) }
}
}
Подводные камни
- DisposableEffect без cleanup. Если зарегистрировать listener в
DisposableEffect, но не удалить его вonDispose { }, он переживёт composable и вызовет утечку памяти или краш. - LaunchedEffect с неправильным ключом.
LaunchedEffect(Unit)запустится один раз при Enter.LaunchedEffect(userId)— при каждом изменении userId. Если ключ — нестабильный объект, эффект будет перезапускаться при каждой recomposition. - Параллельная recomposition. В Compose 1.5+ recomposition может выполняться параллельно (если включена
reuseActivation). Код внутри composable должен быть thread-safe или работать только черезState. - remember не привязан к Navigation backstack.
remember-состояние живёт столько, сколько composable находится в composition. При навигации назад сpopBackStackэкран пересоздаётся и состояние сбрасывается, если не использоватьrememberSaveableили ViewModel. - Анимации удерживают composable в composition.
AnimatedVisibilityиAnimatedContentдержат уходящий composable в дереве до конца exit-анимации. DisposableEffect-cleanup отложен соответственно. - Порядок эффектов не гарантирован между composable. Если два composable запускают
LaunchedEffect, нет гарантии очерёдности их старта. Не полагайтесь на межкомпонентный порядок инициализации. - rememberCoroutineScope vs LaunchedEffect.
rememberCoroutineScope()возвращает scope, привязанный к composition. Корутины в нём не отменяются при recomposition, только при Leave — это может вызвать race condition при быстрых обновлениях state. - ViewModel не заменяет DisposableEffect. ViewModel переживает смену конфигурации, но не отменяет Android-подписки (BroadcastReceiver, LocationListener). Cleanup платформенных ресурсов всегда должен быть в composable через DisposableEffect.
Common mistakes
- Объяснять «жизненный цикл composable» только как синтаксис и не описывать поведение runtime/compiler.
- Игнорировать важный риск: Если хранить внешние listeners без DisposableEffect, они переживают composable и создают leak.
- Давать пример без edge case: отмены, null, recomposition, platform boundary или ошибки.
What the interviewer is testing
- Формулирует суть темы «жизненный цикл composable» своими словами и связывает ее с кодом.
- Называет механизм: Lifecycle определяется не Activity callbacks, а presence in composition, keys и state reads; effects получают cleanup при leaving.
- Видит production-последствие: Если хранить внешние listeners без DisposableEffect, они переживают composable и создают leak.