Composition — е централният процес в Jetpack Compose, по време на който от описателни Composable функции се изгражда живо UI дърво, показвано на екрана. За разлика от View системата в Android, където оформлението се зареждаше от XML и се преобразуваше в неизменяеми обекти, Composition работи като динамична система: функциите се изпълняват, създават слотове в паметта, формират йерархия от възли и я свързват със състоянието. Според Google Android Developers, 2026, разбирането на Composition е критично за оптимизиране на производителността на Compose приложения.
Основни точки
Composition — е процес на изпълнение на Composable функции, в резултат на който се формира вътрешно представяне на потребителския интерфейс под формата на дърво от възли. Всеки възел на това дърво съответства или на вграден компонент (Text, Button, Image), или на извикване на персонализирана Composable функция. Composition не създава директно Android View обекти — изгражда абстрактно описание, което след това се обработва от фазите Layout и Drawing.
Ключовата характеристика на Composition е неговата възможност за рестартиране (restartability). Всяка Composable функция в състава на композицията може да бъде рестартирана по всяко време, ако нейните входни параметри или прочетените от нея обекти на състоянието са се променили. Системата не рестартира цялото дърво — само онези функции, които реално зависят от променените данни.
Технически, Composition се управлява чрез Composer — вътрешен двигател, който Kotlin компилаторът вгражда във всяка Composable функция. Composer записва в слотове (групи позиции) информация за това кои функции са били извикани, с какви параметри и в какъв ред. При последващи извиквания Composer сравнява новите данни със запазените и взема решение за рестартиране.
Процесът на изграждане на UI дървото започва с извикване на метода setContent вътре в Activity или Fragment. Този метод създава началния Composition и стартира изпълнението на кореновата Composable функция. След това всяка вложена Composable функция добавя своите възли към дървото, формирайки йерархия: Row съдържа Text и Button, Column съдържа Image и Card, и така нататък.
Всеки възел на дървото получава уникален ключ на позиция, базиран на положението му в изходния код. Този ключ се използва за идентификация на възела при повторни изпълнения. Ключът на позиция е причината, поради която редът на извикване на Composable функции не трябва да зависи от условия: ако в едно изпълнение са извикани A -> B, а в следващото B -> A, Compose няма да може да съпостави старите и новите възли.
@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 се управлява чрез обекти от тип State<T>. Когато Composable функция чете стойност от State чрез делегирано свойство (by), тя регистрира зависимост от това State. При промяна на стойността всички функции, които са прочели това State, се маркират за рестартиране в следващата фаза на композицията.
Механизмът за регистриране на зависимости се нарича snapshot система. Всеки път, когато State се промени, snapshot записва всички промени и уведомява Composer кои функции зависят от това State. Важно е да се разбере: четенето на State в не-Composable код (например в ламбда onClick) не регистрира зависимост — само четене вътре в Composable функция или в ламбди, изпълнени в контекста на композицията.
Snapshot системата работи транзакционно: няколко промени на State в рамките на едно събитие се обединяват в една транзакция, което предотвратява множество рекомпозиции. Това е особено важно при обработка на жестове: по време на едно движение се променят няколко State обекта, но Compose изпълнява само една рекомпозиция.
@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 (композиция) и Recomposition (рекомпозиция) — са два различни режима на изпълнение на Composable функции. Composition се случва веднъж при създаване на екрана: системата изпълнява всички Composable функции с начални стойности и изгражда първоначалното UI дърво. Recomposition се случва многократно при промяна на данни: системата рестартира само онези функции, които зависят от промененото състояние.
Режимът Composition активира всички възли на дървото, разпределя слотове за всяка функция, регистрира всички наследници. Recomposition работи селективно: Compose сравнява новите и старите стойности на параметрите на всяка функция и ако не са се променили — функцията не се изпълнява (skipping).
Composition и Recomposition се различават по цена. Първият Composition е по-скъп, тъй като изисква пълно изграждане на дървото и разпределяне на слотове. Recomposition е по-евтин, особено ако повечето функции са стабилни — техните параметри се сравняват чрез equals и Compose пропуска тяхното извикване. За максимална производителност трябва да се стремим възможно най-малко функции да бъдат засегнати от рекомпозиция.
| Характеристика | Composition | Recomposition |
|---|---|---|
| Кога се случва | Веднъж, при първото показване | Многократно, при промяна на данни |
| Обхват | Цялото дърво | Само променените функции |
| Сравнение на параметри | Не се изпълнява | Изпълнява се за skipping |
| Създаване на слотове | Да, всички слотове се създават | Само за нови възли |
CompositionLocal — механизъм за имплицитно предаване на данни през дървото на композицията. Решава проблема, когато параметър трябва да бъде предаден през десетки вложени Composable функции, които не го използват директно. Вместо изрична верига от параметри, данните се задават на горното ниво и се четат във всяка вложена функция чрез CompositionLocal.current.
Темата MaterialTheme — най-известният пример за CompositionLocal. Всички компоненти на Compose четат цветове, типография и форми чрез MaterialTheme.colorScheme, MaterialTheme.typography, MaterialTheme.shapes, без да ги получават чрез параметри. Разработчикът може да създава свои CompositionLocal за данни като текущ потребител, настройки за локализация или конфигурация на екрана.
Важно ограничение: CompositionLocal не трябва да се използва за често променящи се данни (позиция на превъртане, текст в поле за въвеждане). Компонентът, който чете CompositionLocal, се рестартира при всяка промяна на стойността, поради което за динамични данни е по-добре да се използват изрични параметри или State. CompositionLocal е оптимален за конфигурационни данни, които се променят рядко или изобщо.
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 планира нова рекомпозиция, която ще се изпълни след приключване на текущата. Не възниква зацикляне: Compose гарантира, че всяка рекомпозиция се изпълнява в отделна транзакция на snapshot системата.
На съвременни устройства Composition на екран с 50–100 Composable функции отнема 1–5 ms. Google препоръчва да се остане в рамките на 16 ms за кадър при 60fps. Ако Composition надвишава този лимит, използвайте LazyColumn или разделете екрана на по-малки функции.
Директно ръчно стартиране на Composition не е възможно — той се управлява автоматично от Composer. Въпреки това може принудително да се планира рекомпозиция чрез промяна на State или извикване на invalidate() на кореновия композит, ако има достъп до CompositionContext.
View йерархията — е неизменимо дърво от Java обекти, което се създава веднъж. Composition — виртуално дърво, което се преизгражда при всяка промяна на данни. View съхранява своето състояние в инстанционни променливи, Composition — в слотове, свързани с позицията на извикване на функцията.
Ако Composable функция престане да бъде извиквана (например условието if стане false), Composition премахва нейния възел и извиква почистване на DisposableEffect. При повторно появяване (if отново стане true) се създава нов възел — старият не се възстановява.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също