Пост

Jetpack Compose. Рекомпозиция

В предыдущей статье мы рассмотрели подход к отрисовке в Compose и обозначили флоу рендеринга и важные компоненты технологии.

В данной статьей рассмотрим процесс рекомпозиций: какие компоненты участвуют и какие общие способы минимизации рекомпозиций мы можем использовать.

Введение

Рекомпозиция в Jetpack Compose - это процесс, при котором Compose перестраивает UI в ответ на изменения в состояниях. Это автоматический процесс, но он может быть неэффективным, если состояние изменяется чаще, чем нужно. По опыту, чаще всего сказывается недостаток экспертизы при работе с Compose ввиду того, что технология достаточно молодая. И изучение технологии чаще начинается с непосредственно создания UI, с создания различных списков и других прикладных вещей, которые можно сразу применять.

Я решил начать с фундамента технологии. С изучения концепций, ее философии. С исходного кода, в котором, кстати, можно найти многие интересные вещи, применимые на проектах.

Как управляется рекомпозиция

Чтобы управлять рекомпозицией эффективно, следует понимать, как Compose отслеживает изменения. Система использует механику подписок на изменения состояний. Каждый компонент “подписывается” на изменения в State переменных, которые он использует, и перестраивается при их изменении.

Jetpack Compose использует несколько ключевых компонентов для управления процессом рекомпозиций:

1. Composer

Composer - это центр управления процессом создания, обновления и уничтожения UI. В Jetpack Compose, Composer сохраняет состояние композиции и исполняет функции, ответственные за построение UI. Эти функции, называемые Composable функциями, реактивно отзываются на изменения в данных и находятся в выделенном блоке памяти, который Composer использует для отслеживания структуры и состояния UI.

2. Snapshot

Snapshot представляет собой систему управления изменениями состояния, которая позволяет компонентам UI реагировать на изменения в данных. Когда состояние, управляемое через Snapshot систему, изменяется, Snapshot уведомляет систему о необходимости пересмотреть и потенциально рекомпоновать затронутые части UI. Так, изменения в данных приводят к рекомпозициям в определенных частях интерфейса.

3. SlotTable

Это структура данных, которая хранит текущее дерево UI в Jetpack Compose. В SlotTable каждый Composable элемент имеет свое место, или “slot”. Composer использует SlotTable для определения, какие комопненты должны быть обновлены или пересозданы в результате изменений в данных. Это позволяет системе эффективно манипулировать деревом компонентов при изменении состояния. Мы касались этой структуры в предыдущей статьи, там же можно увидеть ссылку на исходники;

4. Recomposer

Recomposer работает как менеджер, который координирует рекомпозиции, отслеживая изменения в данных и определяя, когда и какие части UI нуждаются в обновлении. Он отслеживает активные Composable функции и инициирует их вызов при обнаружении изменений в состоянии, которые они отслеживают.

5. Applier

В роли этого компонента выступает мост между Composer и фактическим Android View системом. Applier отвечает за применение изменений, сгенерированных Composer, к реальной структуре View. Это включает в себя внесение изменений в свойства View и управление жизненным циклом UI-элементов.

6. CompositionLocal

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

7. MutableState / State

Эти интерфейсы представляют возможность отслеживать изменения в данных. Когда используется MutableState, любые изменения в данных, которые этот стейт отслеживает, могут триггерить рекомпозиции соответствующих Composable функций, что делает UI реактивным. Примеры реализаций: mutableStateIntOf, mutableStateOf и так далее.

8. Composition

Composition - это контекст, в котором выполнена текущая композиция или рекомпозиция. Он содержит состояние композиций и загружает необходимые ресурсы для работы с данными и UI элементами.

9. MovingGroup

Хотя это не классический компонент, но это концепция в компоузе, которая означает группировку связанных изменений для оптимизации процесса. Это подразумевает группировку операций обновления таким образом, чтобы минимизировать количество рекомпозиций, необходимых для обновления UI.

Способы минимизации рекомпозиций

Использование ключей. Использование ключей является важной техникой оптимизации, особенно когда дело касается эффективного управления списками данных, которые могут динамически изменяться. Этот механизм напрямую влияет на работу SlotTable, структуры данных, которая хранит состояние и структуру UI в Compose.

Рассмотрим, как ключи помогают SlotTable оптимизировать работу с состоянием композиции.

Зачем нужны ключи

Ключи используются для пометки отдельных элементов в коллекциях, таких как списковые элементы, созданные внутри LazyColumn или LazyRow. Ключи позволяют Compose точно идентифицировать, какие элементы изменились, были добавлены или удалены, что существенно оптимизирует процесс рекомпозиции.

Польза ключей для SlotTable

  1. Стабильность элементов: Ключи помогают SlotTable эффективно отслеживать изменения в коллекциях. Каждый элементу назначается уникальный ключ, благодаря которому при изменении порядка элементов или их содержимого распознавание одних и тех же элементов происходит корректно, даже если их позиция в коллекции изменяется.

  2. Повторное использование состояния и UI: Ключи позволяют системе композиции сохранять и повторно использовать состояние и UI элементов между рекомпозициями. Если ключ элемента остается неизменным, Compose может быстро восстановить его состояние, не инициализируя его заново. Это снижает нагрузку на систему и ускоряет рендеринг.

  3. Минимизация работы: Без использования ключей, любые перемены в списке могли бы привести к полной перерисовке всех элементов, что неэффективно, особенно на больших списках. Неправильное использование или отсутствие ключей может привести к избыточным пересозданиям и рекомпозициям.

Пример использования ключей в LazyColumn

1
2
3
4
5
LazyColumn {
    items(items = listData, key = { item -> item.id }) {
        Text(text = "Item: ${it.name}")
    }
}

В данном примере каждому элементу списка присваивается уникальный ключ на основе item.id. Это позволяет Compose корректно управлять каждым элементом даже если, например, порядок данных изменится или сами элементы будут изменены.

В заключение этого пункта, ключи играют критически важную роль в оптимизации производительности фреймворка Jetpack Compose, позволяя SlotTable более эффективно управлять элементами интерфейса, сохранять состояния и минимизировать ненужные рекомпозиции.

Практики для минимизации рекомпозиций

Чтобы свести к минимуму необходимость рекомпозиций и улучшить производительность приложений, следует придерживаться некоторых практик:

  1. Оптимальное использование состояний: Используйте remember и изменяемые стейты (типа mutableStateOf) для управления состоянием, которое влияет на UI;
  2. Изоляция часто меняющихся состояний: Разделяйте UI компоненты таким образом, чтобы те Composable, которые редко меняются, не перерисовывались с часто изменяемыми. Это сократит количество ненужных рекомпозиций;
  3. Использование LaunchedEffect и DisposableEffect для управления побочными эффектами: Эффекты должны быть аккуратно организованы и отделены от основной UI логики. Подробнее об этом будет в следующих статьях по Compose;
  4. Использование ключей: чтобы исключить лишние рекомпозиции, следует использовать ключи. Тогда рекомпозиция выполнится только в случае изменения ключа;
  5. Мемоизация сложных вычислений: Используйте derivedStateOf для мемоизации сложных производных состояний, чье вычисление требует значительных ресурсов.

И в заключение, рекомендую воспринимать Composable в своем лексическом значении. На русский можно перевести как “Составляемый”, то есть по-сути это единица строительного блока. И UI в Jetpack Compose строится из этих строительных блоков. Так, следует находить из сложного, сплошного UI эти самые строительные блоки и выделять их как отдельные функции, как отдельные Composable элементы. SlotTable оперирует этими самыми Composable функциями, и когда изменяется только часть Composable блоков, то рекомпозиция будет осуществляться только для них. Что позволит избежать перерисовки всего UI и изменить только затронутые элементы.

Авторский пост защищен лицензией CC BY 4.0 .

Популярные теги