Давайте разложим это без магии: у любой @Composable по сути три состояния жизни:
- Вошла в композицию (initial composition)
- Живёт и время от времени пересоздаётся (recomposition)
- Вышла из композиции (disposed)
Compose-рантайм не даёт onCreate/onDestroy, но даёт правила, по которым вызывается код и срабатывают эффекты.
1. Initial composition — первый заход функции в композицию
Когда происходит:
setContent { MyScreen() }впервые- Внутри другой composable впервые появился вызов
Child() - Поменялся
if/when/navigationтак, что участок дерева стал новым
@Composable
fun Screen() {
if (showDetails) {
Details() // <= впервые вызвали для этого места в дереве
}
}
Что реально происходит при initial composition:
- Compose идёт сверху вниз по дереву
@Composableвызовов. - Для каждого вызова:
- Создаёт записи в slot table (структура, где хранится дерево UI + remember-слоты и т.д.)
- Выполняет
remember { ... }=> кладёт значения в слоты - Запускает сайд-эффекты:
LaunchedEffect,DisposableEffect,SideEffectи т.п. - Создаёт/меняет нужные
LayoutNode/DrawNode(то, что реально рисуется).
- Пример:
@Composable
fun Counter() {
println("Counter composed") // <= будет напечатано на initial composition и на каждой рекомпозиции
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count = $count")
}
}
- На initial composition:
remember { mutableStateOf(0) }выполняется => в slot table кладётсяMutableState(0)Button/ Textсоздаются в дереве
2. Recomposition — повторный вызов функции при изменении состояния
Когда происходит:
- Ты изменил обсервабельное состояние, прочитанное в composable:
mutableStateOf,derivedStateOfcollectAsState(),remember { mutableStateOf(...) },rememberSaveable
- Внешняя система поменяла
State<T>, который читает этот composable.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
// любое изменение count => запрос на рекомпозицию этой части дерева
Button(onClick = { count++ }) {
Text("Count = $count")
}
}
Когда count++:
MutableStateпомечает «чтение этого стейта => была такая-то композиционная группа«- При записи:
state.value = new=> Compose инвалидирует соответствующую группу. - Планировщик вызывает recomposition:
- Повторно вызывает
Counter() - Переиспользует уже имеющиеся слоты из slot table
- Сравнивает параметры, может скипнуть части, где ничего не поменялось
- Повторно вызывает
Важно:
- Рекомпозируется не весь экран, а только поддеревья, которые читали изменившийся стейт.
- Рекомпозиция может батчиться и откладываться, нет гарантии «каждый change => отдельный проход».
- Если composable не читает ни одного
State, он сам по себе никогда не станет причиной рекомпозиции.
Пример с локальной изоляцией:
@Composable
fun Parent() {
val color by remember { mutableStateOf(Color.Red) }
Column {
Child1(color) // читает color => будет рекомпозиться
Child2() // не читает color => можно скипнуть
}
}
Compose знает, какие группы зависят от каких State, и дергает только нужные.
3. Leaving composition — когда composable «умирает»
Composable выходит из композиции, когда:
- Родитель перестал его вызывать (ветка
if/whenстала другой). - Он исчез из списка (
LazyColumnс ключами,key {}и т.п.). - Навигация ушла на другой экран /
setContentпоказал совсем другое дерево.
@Composable
fun Screen(showDetails: Boolean) {
if (showDetails) {
Details()
}
}
Когда showDetails сменился с true на false:
- Ветка с
Details()больше не вызывается. - Для группы
Detailsрантайм:- удаляет её из slot table
- вызывает
onDisposeу всехDisposableEffect - отменяет все
LaunchedEffectвнутри неё - выбрасывает
remember-состояние (если это неrememberSaveableс внешним стором)
Пример:
@Composable
fun Details() {
val state = remember { mutableStateOf(0) }
DisposableEffect(Unit) {
println("Details ENTER composition")
onDispose {
println("Details LEAVE composition")
}
}
}
Вывод будет:
- при первом появлении:
Details ENTER composition - при скрытии
Details:Details LEAVE composition - между ними — только рекомпозиции из-за стейта, без повторного ENTER.
Связка с remember / эффектами
remember
- На initial composition: блок выполняется, результат кладётся в слот.
- На recomposition: значение берётся из слота, блок не выполняется.
- На leaving composition: слот удаляется, значение может быть освобождено GC.
rememberSaveable
- При leaving composition может сохранить значение через
SavedStateRegistryи восстановить при возвращении (напр. смена конфигурации / recreate activity).
LaunchedEffect
- На initial composition: стартует корутина.
- При изменении
key: старая корутина отменяется, создаётся новая. - При leaving composition: корутина отменяется.
DisposableEffect
- На initial composition: выполняется
effectблок, возвращаетсяonDispose. - При recomposition:
- если key не меняется →
effectне перезапускается - если key меняется → старый
onDisposeвызывается,effectзапускается заново.
- если key не меняется →
- При leaving composition: вызывается последний
onDispose.