Composition: същност, изграждане на UI дървото в Compose

Автор: IT Sectr Публикувано: 2026-06-27 Време за четене: 7 мин

Composition — е централният процес в Jetpack Compose, по време на който от описателни Composable функции се изгражда живо UI дърво, показвано на екрана. За разлика от View системата в Android, където оформлението се зареждаше от XML и се преобразуваше в неизменяеми обекти, Composition работи като динамична система: функциите се изпълняват, създават слотове в паметта, формират йерархия от възли и я свързват със състоянието. Според Google Android Developers, 2026, разбирането на Composition е критично за оптимизиране на производителността на Compose приложения.

Основни точки

  • Composition — изпълнение на Composable функции за изграждане на UI дърво
  • Слотове — клетки памет, съхраняващи параметрите и състоянието на всяка функция
  • Позиция в Compose (Positional Memorization) свързва състоянието с мястото в кода
  • Първо преминаване Composition създава началното UI дърво при стартиране на екрана
  • CompositionLocal предава данни през дървото без изрични параметри

Какво е Composition в Jetpack Compose

Composition — е процес на изпълнение на Composable функции, в резултат на който се формира вътрешно представяне на потребителския интерфейс под формата на дърво от възли. Всеки възел на това дърво съответства или на вграден компонент (Text, Button, Image), или на извикване на персонализирана Composable функция. Composition не създава директно Android View обекти — изгражда абстрактно описание, което след това се обработва от фазите Layout и Drawing.

Ключовата характеристика на Composition е неговата възможност за рестартиране (restartability). Всяка Composable функция в състава на композицията може да бъде рестартирана по всяко време, ако нейните входни параметри или прочетените от нея обекти на състоянието са се променили. Системата не рестартира цялото дърво — само онези функции, които реално зависят от променените данни.

Технически, Composition се управлява чрез Composer — вътрешен двигател, който Kotlin компилаторът вгражда във всяка Composable функция. Composer записва в слотове (групи позиции) информация за това кои функции са били извикани, с какви параметри и в какъв ред. При последващи извиквания Composer сравнява новите данни със запазените и взема решение за рестартиране.

Как се изгражда UI дървото в процеса на Composition

Процесът на изграждане на UI дървото започва с извикване на метода setContent вътре в Activity или Fragment. Този метод създава началния Composition и стартира изпълнението на кореновата Composable функция. След това всяка вложена Composable функция добавя своите възли към дървото, формирайки йерархия: Row съдържа Text и Button, Column съдържа Image и Card, и така нататък.

Всеки възел на дървото получава уникален ключ на позиция, базиран на положението му в изходния код. Този ключ се използва за идентификация на възела при повторни изпълнения. Ключът на позиция е причината, поради която редът на извикване на Composable функции не трябва да зависи от условия: ако в едно изпълнение са извикани A -> B, а в следващото B -> A, Compose няма да може да съпостави старите и новите възли.

kotlin
@Composable
fun AppScreen() {
    Column {                     // Column възел (позиция 1)
        HeaderSection()            // HeaderSection възел (позиция 2)
        ContentSection()           // ContentSection възел (позиция 3)
        FooterSection()            // FooterSection възел (позиция 4)
    }
}

@Composable
fun HeaderSection() {
    Row {                       // Row възел (позиция 2.1)
        Text("Заглавие")         // Text възел (позиция 2.2)
        Icon(...)                // Icon възел (позиция 2.3)
    }
}

В този пример всяко извикване получава позиция, базирана на реда в кода. Column (позиция 1) съдържа три дъщерни възела (позиции 2, 3, 4). HeaderSection добавя още два дъщерни възела (2.1, 2.2, 2.3). Ако в следващата рекомпозиция ContentSection бъде извикан преди HeaderSection, Composer няма да може правилно да съпостави възлите — оттук и правилото: редът на извикване на Composable функции трябва да бъде стабилен.

Управление на състоянието в Composition

Състоянието в Composition се управлява чрез обекти от тип State<T>. Когато Composable функция чете стойност от State чрез делегирано свойство (by), тя регистрира зависимост от това State. При промяна на стойността всички функции, които са прочели това State, се маркират за рестартиране в следващата фаза на композицията.

Механизмът за регистриране на зависимости се нарича snapshot система. Всеки път, когато State се промени, snapshot записва всички промени и уведомява Composer кои функции зависят от това State. Важно е да се разбере: четенето на State в не-Composable код (например в ламбда onClick) не регистрира зависимост — само четене вътре в Composable функция или в ламбди, изпълнени в контекста на композицията.

Snapshot системата работи транзакционно: няколко промени на State в рамките на едно събитие се обединяват в една транзакция, което предотвратява множество рекомпозиции. Това е особено важно при обработка на жестове: по време на едно движение се променят няколко State обекта, но Compose изпълнява само една рекомпозиция.

kotlin
@Composable
fun StateExample() {
    var text by remember { mutableStateOf("Hello") }
    var isVisible by remember { mutableStateOf(true) }

    Column {
        Text(text)  // регистрира зависимост от text

        if (isVisible) {  // регистрира зависимост от isVisible
            TextField(value = text, onValueChange = { text = it })
        }

        Button(onClick = { isVisible = !isVisible }) {
            Text(if (isVisible) "Скрий" else "Покажи")
        }
    }
}

Промяната на text води до рекомпозиция само на Column, Text и TextField. Button и условието isVisible остават непроменени. Такова изолиране на рекомпозицията е ключово предимство на Compose пред системите, които прерисуват целия екран. Всяка Composable функция следи само онези State обекти, които чете директно.

Composition vs Recomposition: ключови разлики

Composition (композиция) и Recomposition (рекомпозиция) — са два различни режима на изпълнение на Composable функции. Composition се случва веднъж при създаване на екрана: системата изпълнява всички Composable функции с начални стойности и изгражда първоначалното UI дърво. Recomposition се случва многократно при промяна на данни: системата рестартира само онези функции, които зависят от промененото състояние.

Режимът Composition активира всички възли на дървото, разпределя слотове за всяка функция, регистрира всички наследници. Recomposition работи селективно: Compose сравнява новите и старите стойности на параметрите на всяка функция и ако не са се променили — функцията не се изпълнява (skipping).

Composition и Recomposition се различават по цена. Първият Composition е по-скъп, тъй като изисква пълно изграждане на дървото и разпределяне на слотове. Recomposition е по-евтин, особено ако повечето функции са стабилни — техните параметри се сравняват чрез equals и Compose пропуска тяхното извикване. За максимална производителност трябва да се стремим възможно най-малко функции да бъдат засегнати от рекомпозиция.

ХарактеристикаCompositionRecomposition
Кога се случваВеднъж, при първото показванеМногократно, при промяна на данни
ОбхватЦялото дървоСамо променените функции
Сравнение на параметриНе се изпълняваИзпълнява се за skipping
Създаване на слотовеДа, всички слотове се създаватСамо за нови възли

CompositionLocal: предаване на данни през дървото

CompositionLocal — механизъм за имплицитно предаване на данни през дървото на композицията. Решава проблема, когато параметър трябва да бъде предаден през десетки вложени Composable функции, които не го използват директно. Вместо изрична верига от параметри, данните се задават на горното ниво и се четат във всяка вложена функция чрез CompositionLocal.current.

Темата MaterialTheme — най-известният пример за CompositionLocal. Всички компоненти на Compose четат цветове, типография и форми чрез MaterialTheme.colorScheme, MaterialTheme.typography, MaterialTheme.shapes, без да ги получават чрез параметри. Разработчикът може да създава свои CompositionLocal за данни като текущ потребител, настройки за локализация или конфигурация на екрана.

Важно ограничение: CompositionLocal не трябва да се използва за често променящи се данни (позиция на превъртане, текст в поле за въвеждане). Компонентът, който чете CompositionLocal, се рестартира при всяка промяна на стойността, поради което за динамични данни е по-добре да се използват изрични параметри или State. CompositionLocal е оптимален за конфигурационни данни, които се променят рядко или изобщо.

kotlin
val LocalUser = compositionLocalOf<User?> { null }

@Composable
fun AppRoot(user: User, content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalUser.provides(user)) {
        content()
    }
}

@Composable
fun UserAvatar() {
    val user = LocalUser.current  // четене без изричен параметър
    AsyncImage(model = user?.avatarUrl, contentDescription = "Avatar")
}

CompositionLocalProvider създава област на видимост, вътре в която LocalUser.current връща зададената стойност. UserAvatar чете потребителя без изрично предаване на параметър чрез междинни функции. Това е особено ценно в дълбоки йерархии, където данните са необходими само в няколко листни възела.

Често задавани въпроси

Какво ще се случи, ако State се промени по време на Composition?

Промяната на State по време на Composition планира нова рекомпозиция, която ще се изпълни след приключване на текущата. Не възниква зацикляне: Compose гарантира, че всяка рекомпозиция се изпълнява в отделна транзакция на snapshot системата.

Колко време отнема Composition на сложен екран?

На съвременни устройства Composition на екран с 50–100 Composable функции отнема 1–5 ms. Google препоръчва да се остане в рамките на 16 ms за кадър при 60fps. Ако Composition надвишава този лимит, използвайте LazyColumn или разделете екрана на по-малки функции.

Може ли Composition да се стартира ръчно?

Директно ръчно стартиране на Composition не е възможно — той се управлява автоматично от Composer. Въпреки това може принудително да се планира рекомпозиция чрез промяна на State или извикване на invalidate() на кореновия композит, ако има достъп до CompositionContext.

Как се различава Composition от View йерархията в класическия Android?

View йерархията — е неизменимо дърво от Java обекти, което се създава веднъж. Composition — виртуално дърво, което се преизгражда при всяка промяна на данни. View съхранява своето състояние в инстанционни променливи, Composition — в слотове, свързани с позицията на извикване на функцията.

Как Composition обработва премахването на възли?

Ако Composable функция престане да бъде извиквана (например условието if стане false), Composition премахва нейния възел и извиква почистване на DisposableEffect. При повторно появяване (if отново стане true) се създава нов възел — старият не се възстановява.

Обобщение

  • Composition — процес на изпълнение на Composable функции за изграждане на UI дърво, свързано със състояние
  • Composer управлява слотове, записва извиквания на функции и сравнява параметри при рекомпозиция
  • Snapshot система регистрира зависимостите на функциите от State и обединява промените в транзакции
  • Composition се изпълнява веднъж при стартиране, Recomposition — при промяна на данни
  • CompositionLocal предава конфигурационни данни през дървото без изрична верига от параметри
  • Позицията на извикване на функция служи като неин уникален идентификатор в дървото на композицията
  • Препоръка: правете малки Composable функции с неизменими параметри за ефективно skipping

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също