← Назад к вопросам

Как часто происходит рекомпозиция в Jetpack Compose

2.0 Middle🔥 151 комментариев
#UI и вёрстка#Производительность и оптимизация

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Рекомпозиция в Jetpack Compose: частота и механизм

Рекомпозиция — это фундаментальный процесс в Jetpack Compose, при котором фреймворк повторно вызывает composable-функции для обновления UI в ответ на изменение состояния (State). Частота рекомпозиции напрямую зависит от того, как часто и какие части состояния изменяются. Она не происходит "по расписанию", а является реактивной и целевой.

Когда и как часто происходит рекомпозиция?

  1. При изменении наблюдаемого состояния (State, MutableState):
    Это основный триггер. Любое изменение значения, "читаемого" внутри composable-функции (например, через `mutableStateOf()`), приводит к рекомпозиции этой функции и всех функций, которые вызываются внутри её тела на текущем уровне. Однако Compose старается минимизировать область рекомпозиции.

```kotlin
@Composable
fun MyCounter() {
    // count — наблюдаемое состояние
    val count by remember { mutableStateOf(0) }

    // Эта функция будет рекомпозироваться каждый раз при изменении count
    Text(text = "Count: $count")

    Button(onClick = { count++ }) {
        // Эта функция также будет рекомпозироваться при изменении count,
        // так как она вызывается внутри MyCounter и может зависеть от состояния
        Text("Increment")
    }
}
```

2. Строго для читающих это состояние функций:

    Compose использует систему **positional memoization** (запоминание через вызовы и позиции в дереве). Если родительская функция рекомпозируется, но дочерняя функция не читает изменённое состояние и её входные параметры (`@Composable` lambda) остались прежними, она может быть **скипована (skip)**. Это ключевая оптимизация для предотвращения чрезмерной рекомпозиции.

```kotlin
@Composable
fun ParentScreen(isLoading: Boolean, data: List<String>) {
    // Эта часть рекомпозируется только при изменении isLoading
    if (isLoading) {
        LoadingIndicator() // Рекомпозируется только когда isLoading true/false
    }

    // LazyColumn рекомпозируется при изменении data
    LazyColumn {
        items(data) { item ->
            // ItemComposable рекомпозируется только для изменённых элементов
            ItemComposable(item)
        }
    }

    // Статический заголовок, не зависящий от состояния, может не рекомпозироваться
    StaticHeader()
}
```

3. При изменении входных параметров composable-функции:

    Если аргументы функции изменяются (например, новый текст для `Text()`), функция будет рекомпозирована для отражения новых данных.

  1. "Часто" — это субъективно:
    *   В анимации или непрерывном потоке данных рекомпозиция может происходить много раз в секунду (например, каждые 16 мс для 60 FPS), но только для узлов, зависящих от анимируемого значения.
    *   В обычном UI (клики, форма ввода) рекомпозиция происходит эпизодически — только при действиях пользователя или новых данных из сети.
    *   **Ключевой принцип**: рекомпозиция затрагивает **минимально необходимую часть дерева UI**. Не весь экран пересчитывается при каждом изменении одного поля.

Важные механизмы контроля частоты

  • remember и ключи: remember сохраняет вычисление или объект между рекомпозициями. С ключом (key = someState) значение будет пересчитано только при изменении ключа, что позволяет управлять частотой дорогих вычислений.

    @Composable
    fun ExpensiveComponent(selectedId: Int) {
        // Фильтрация списка будет пересчитана только при изменении selectedId,
        // а не при любой рекомпозиции родителя
        val filteredList = remember(selectedId) {
            originalList.filter { it.id == selectedId }
        }
        // Использование filteredList
    }
    
  • Стабильные типы и @Stable: Compose анализирует типы данных состояния. Если тип объявлен как стабильный (не изменяет свои свойства хаотично между рекомпозициями), Compose может лучше оптимизировать и избегать лишних рекомпозиций.

  • Derived state (derivedStateOf): Позволяет создать производное состояние, которое изменяется только при определённых условиях, уменьшая триггеры рекомпозиции.

    @Composable
    fun ListWithScrollIndicator(items: List<Item>) {
        val listState = rememberLazyListState()
        // isScrolled вычисляется часто, но изменяется только при пересечении порога
        val isScrolled = derivedStateOf {
            listState.firstVisibleItemIndex > 5
        }
        // Индикатор рекомпозируется только когда isScrolled меняет значение,
        // а не при каждом скролле
        if (isScrolled.value) {
            ScrollIndicator()
        }
        LazyColumn(state = listState) { ... }
    }
    

Практические рекомендации

  • Разделяйте логику и UI: Размещайте изменяемое состояние как можно ближе к composable, который его непосредственно использует, чтобы ограничить область рекомпозиции.
  • Избегайте выполнения побочных эффектов или чтения состояния внутри самой composable-функции без использования LaunchedEffect или remember: это может привести к неожиданным или чрезмерным рекомпозициям.
  • Профилируйте с Layout Inspector: Инструмент Compose Layout Inspector в Android Studio показывает, какие узлы рекомпозировались, перерисовались или были пропущены, помогая оптимизировать частоту.

Итог: Рекомпозиция в Compose происходит реактивно и локально — только при изменении состояния и только для функций, которые непосредственно зависят от этого изменения. Частота варьируется от очень высокой (в анимациях) до единичных случаев (действия пользователя). Задача разработчика — правильно структурировать состояние и использовать оптимизации (remember, derivedStateOf, стабильные типы), чтобы рекомпозиция была минимальной и эффективной, обеспечивая высокую производительность UI.

Как часто происходит рекомпозиция в Jetpack Compose | PrepBro