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

Как создается объект в композиции

2.0 Middle🔥 112 комментариев
#Архитектура и паттерны

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

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

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

Отличный и очень важный вопрос, который затрагивает саму суть декларативного подхода UI-фреймворков, таких как Jetpack Compose.

В Compose объект (в традиционном ООП-понимании) не создается явно разработчиком для описания UI. Вместо этого UI-дерево (или граф композиции) создается и управляется Compose Runtime на основе вызовов @Composable функций, которые вы пишете. Давайте разберем этот процесс по шагам.

Основной принцип: Декларативная модель

Вы не создаете экземпляры View или ViewGroup, как в старом императивном View-системе Android. Вы описываете, как должен выглядеть ваш интерфейс в зависимости от текущего состояния (State). Когда состояние меняется, Compose Runtime заново выполняет (recomposes) только те @Composable функции, чьи входные данные изменились, и обновляет граф композиции, что в конечном итоге приводит к обновлению на экране.

Процесс "создания объекта": от Composable функции до узла в графе

Рассмотрим, что происходит при вызове простой Composable функции:

@Composable
fun Greeting(name: String) {
    // 1. "Вызов" композиции
    Text(
        text = "Hello, $name!",
        modifier = Modifier.padding(16.dp),
        color = Color.Blue
    )
}

Когда Greeting вызывается внутри другой @Composable функции (например, в setContent Activity), запускается следующий процесс:

  1. Начало Recompose Scope: Compose Runtime определяет "область рекомпозиции" – точку в графе, с которой нужно начать обход или обновление.
  2. Исполнение кода Composable функции: Выполняется код внутри Greeting. Это приводит к вызову другой @Composable функции – Text.
  3. Генерация или обновление "Applier"-ом узлов графа: Здесь ключевую роль играет система Slot Table и Applier. Во время исполнения:
    *   **Compose Compiler** генерирует специальный код для вашей `@Composable` функции. Этот код не создает UI напрямую, а описывает его структуру для рантайма.
    *   **Compose Runtime** использует класс, реализующий интерфейс `Applier`. Для UI Android это `AndroidComposeView.Applier`. Его задача – применять операции к дереву узлов.
    *   Внутри функции `Text` (и любой другой, создающей UI-элемент) будут вызваны `emit`-подобные методы, которые инструктируют `Applier`:
        *   **Создать новый узел** (`start`), если его еще нет в текущей позиции Slot Table.
        *   **Обновить свойства** существующего узла (например, изменить текст или цвет), если он уже существует и только его данные изменились.
        *   **Закончить узел** (`end`) после настройки его свойств и дочерних элементов.

    Узлами графа для UI являются, например, экземпляры внутренних классов, представляющих `LayoutNode`, `AbstractComposeView` и другие низкоуровневые строительные блоки.

  1. Связь с платформой: В конечном итоге, этот внутренний граф композиции (LayoutNode-дерево) отрисовывается на Canvas или мапится на существующие View через AndroidComposeView, который является наследником ViewGroup. Но эта абстракция скрыта от разработчика.

Ключевые механизмы, обеспечивающие процесс

  • Slot Table: Это внутренняя, персистентная структура данных Compose. Она хранит всю информацию, необходимую для описания UI-дерева: какие Composable были вызваны, с какими параметрами, и в каком порядке. Это "источник правды". При рекомпозиции Compose сравнивает новое описание (после изменения состояния) с данными в Slot Table, чтобы понять, что нужно добавить, удалить или обновить.
  • Intelligent Recompose (Умная рекомпозиция): Compose отслеживает, какие State объекты были "прочитаны" (read) внутри @Composable функции. Если изменяется только State A, то перезапускаются (recompose) только те функции, которые от него зависят. Функции, которые от State A не зависят, не выполняются, а их соответствующие узлы в графе не трогаются. Это фундаментальная оптимизация.
  • Positional Memoization (Позиционная мемоизация): Вызов @Composable функции в определенной позиции (порядке) исполнения кода не гарантирует, что будет создан один и тот же физический объект в памяти после рекомпозиции. Однако Compose использует концепцию мемоизации, чтобы повторно использовать ранее созданные узлы графа, если функция вызывается с теми же ключами (key {}) и в той же позиции, что и во время предыдущей композиции. Это предотвращает ненужное пересоздание дорогих ресурсов.

Пример с управляемым созданием

Иногда вам нужно явно контролировать жизненный цикл объекта, привязанный к жизненному циклу Composable функции. Для этого используются помощники:

@Composable
fun ExpensiveComponent(data: List<String>) {
    // Объект `LazyListState` будет создан ровно один раз для этой позиции в графе
    // и переиспользован при последующих рекомпозициях.
    val listState = remember { LazyListState() }

    // Объект `Bitmap` будет создан заново только если изменился `data`.
    // `remember(data)` сбрасывает значение при изменении ключа.
    val processedImage = remember(data) {
        HeavyImageProcessor.process(data)
    }

    LazyColumn(state = listState) {
        items(processedImage) { item ->
            // ...
        }
    }
}

Итог: В Compose вы не создаете UI-объекты вручную. Вы пишете @Composable функции, которые являются декларативными описаниями UI. Compose Runtime, используя Slot Table, Applier и механизмы позиционной мемоизации и интеллектуальной рекомпозиции, решает, когда создать новый узел в графе композиции (LayoutNode), когда обновить свойства существующего, а когда переместить или удалить узел. Это делает код более предсказуемым и эффективно обновляет только необходимые части интерфейса.