На этой странице вы узнаете о жизненном цикле компонуемого объекта и о том, как Compose определяет, нуждается ли компонуемый объект в перекомпозиции.
Обзор жизненного цикла
Как указано в документации по управлению состоянием , композиция описывает пользовательский интерфейс вашего приложения и создается путем выполнения компонуемых объектов. Композиция представляет собой древовидную структуру компонуемых объектов, описывающих ваш пользовательский интерфейс.
Когда Jetpack Compose впервые запускает ваши компонуемые объекты во время первоначального создания композиции , он отслеживает те объекты, которые вы вызываете для описания вашего пользовательского интерфейса в композиции. Затем, когда состояние вашего приложения изменяется, Jetpack Compose планирует перекомпозицию . Перекомпозиция — это процесс повторного выполнения компонуемых объектов, которые могли измениться в ответ на изменения состояния, и последующего обновления композиции, чтобы отразить все изменения.
Композиция может быть создана только путем первоначальной композиции и обновлена путем рекомпозиции. Единственный способ изменить композицию — это рекомпозиция.

Рекомпозиция обычно запускается изменением объекта State<T> . Compose отслеживает эти изменения и запускает все компонуемые объекты в композиции, которые считывают данный объект State<T> , и любые вызываемые ими компонуемые объекты не могут быть пропущены .
Если компонент вызывается несколько раз, в композицию помещается несколько его экземпляров. Каждый вызов имеет свой собственный жизненный цикл в композиции.
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }

MyComposable в композиции. Если компонент вызывается несколько раз, в композицию помещается несколько экземпляров. Элемент, имеющий другой цвет, указывает на то, что это отдельный экземпляр.Анатомия составного элемента в композиции
Экземпляр компонуемого объекта в Composition идентифицируется по месту его вызова . Компилятор Compose рассматривает каждое место вызова как отдельное. Вызов компонуемых объектов из нескольких мест вызова создаст несколько экземпляров этого компонуемого объекта в Composition.
Если во время рекомпозиции компонуемый объект вызывает другие компонуемые объекты, чем во время предыдущей композиции, Compose определит, какие компонуемые объекты были вызваны, а какие нет , и для тех компонуемых объектов, которые были вызваны в обеих композициях, Compose избежит их повторной композиции, если их входные данные не изменились .
Сохранение идентичности имеет решающее значение для сопоставления побочных эффектов с их составляющими, чтобы они могли успешно завершиться, а не перезапускаться при каждой перекомпозиции.
Рассмотрим следующий пример:
@Composable fun LoginScreen(showError: Boolean) { if (showError) { LoginError() } LoginInput() // This call site affects where LoginInput is placed in Composition } @Composable fun LoginInput() { /* ... */ } @Composable fun LoginError() { /* ... */ }
В приведенном выше фрагменте кода LoginScreen будет вызывать компонуемый объект LoginError при определенных условиях и всегда будет вызывать компонуемый объект LoginInput . Каждый вызов имеет уникальное место вызова и позицию источника, которые компилятор будет использовать для его однозначной идентификации.

LoginScreen в систему в композиции при изменении состояния и перекомпозиции. Один и тот же цвет означает, что перекомпозиция не производилась. Несмотря на то, что вызов LoginInput переместился с первого на второй, экземпляр LoginInput будет сохранен при повторной композиции. Кроме того, поскольку LoginInput нет параметров, которые изменились бы при повторной композиции, вызов LoginInput будет пропущен Compose.
Добавьте дополнительную информацию, чтобы помочь в интеллектуальном пересоставлении текста.
Многократный вызов составного объекта также приведет к его многократному добавлению в Composition. При многократном вызове составного объекта из одного и того же места вызова Compose не располагает информацией для уникальной идентификации каждого вызова этого составного объекта, поэтому для обеспечения различимости экземпляров используется порядок выполнения в дополнение к месту вызова. Иногда этого поведения достаточно, но в некоторых случаях оно может привести к нежелательным последствиям.
@Composable fun MoviesScreen(movies: List<Movie>) { Column { for (movie in movies) { // MovieOverview composables are placed in Composition given its // index position in the for loop MovieOverview(movie) } } }
В приведенном выше примере Compose использует порядок выполнения в дополнение к месту вызова, чтобы каждый экземпляр в композиции оставался уникальным. Если в конец списка добавляется новый movie , Compose может повторно использовать уже имеющиеся в композиции экземпляры, поскольку их местоположение в списке не изменилось, и, следовательно, входные данные movie остаются теми же для этих экземпляров.

MoviesScreen в композиции при добавлении нового элемента в конец списка. Элементы MovieOverview в композиции могут быть использованы повторно. Одинаковый цвет в MovieOverview означает, что элемент не был перекомпонован. Однако, если список movies изменяется путем добавления элементов в начало или середину списка, удаления или изменения порядка элементов, это вызовет перекомпозицию во всех вызовах MovieOverview , входной параметр которых изменил свою позицию в списке. Это крайне важно, если, например, MovieOverview получает изображение фильма с помощью побочного эффекта. Если перекомпозиция происходит во время выполнения эффекта, она будет отменена и начнётся заново.
@Composable fun MovieOverview(movie: Movie) { Column { // Side effect explained later in the docs. If MovieOverview // recomposes, while fetching the image is in progress, // it is cancelled and restarted. val image = loadNetworkImage(movie.url) MovieHeader(image) /* ... */ } }

MoviesScreen в композиции при добавлении нового элемента в список. Компоненты MovieOverview нельзя использовать повторно, и все побочные эффекты будут перезапущены. Изменение цвета в MovieOverview означает, что компонент был перекомпонован. В идеале, идентификатор экземпляра MovieOverview должен быть связан с идентификатором movie , который ему передается. Если мы изменим порядок списка фильмов, в идеале мы должны аналогично изменить порядок экземпляров в дереве композиции, вместо того чтобы перекомпоновывать каждый компонент MovieOverview с другим экземпляром фильма. Compose предоставляет способ указать среде выполнения, какие значения вы хотите использовать для идентификации определенной части дерева: key компонент.
Обернув блок кода вызовом составного элемента с одним или несколькими переданными значениями, эти значения будут объединены для идентификации данного экземпляра в композиции. Значение key не обязательно должно быть уникальным во всем мире , оно должно быть уникальным только среди вызовов составных элементов в месте вызова. Таким образом, в этом примере каждый movie должен иметь key , уникальный среди всех movies ; допустимо, если этот key используется и в других составных элементах приложения.
@Composable fun MoviesScreenWithKey(movies: List<Movie>) { Column { for (movie in movies) { key(movie.id) { // Unique ID for this movie MovieOverview(movie) } } } }
Благодаря вышеизложенному, даже если элементы в списке изменятся, Compose распознает отдельные вызовы MovieOverview и может использовать их повторно.

MoviesScreen в композиции при добавлении нового элемента в список. Поскольку компонуемые объекты MovieOverview имеют уникальные ключи, Compose распознает, какие экземпляры MovieOverview не изменились, и может повторно использовать их; их побочные эффекты будут продолжать выполняться. Некоторые компонуемые объекты имеют встроенную поддержку компонуемого key . Например, LazyColumn позволяет указывать пользовательский key в DSL items .
@Composable fun MoviesScreenLazy(movies: List<Movie>) { LazyColumn { items(movies, key = { movie -> movie.id }) { movie -> MovieOverview(movie) } } }
Пропускается, если входные данные не изменились.
В процессе рекомпозиции выполнение некоторых подходящих композиционных функций может быть полностью пропущено, если их входные данные не изменились по сравнению с предыдущей композицией.
Компонуемая функция может быть пропущена, за исключением следующих случаев :
- Функция возвращает значение не типа
Unit. - Функция аннотирована с помощью
@NonRestartableComposableили@NonSkippableComposable - Обязательный параметр имеет нестабильный тип.
Существует экспериментальный режим компиляции, Strong Skipping , который ослабляет последнее требование.
Для того чтобы тип считался стабильным, он должен соответствовать следующему контракту:
- Результат вычисления
equalsдля двух случаев всегда будет одинаковым для этих двух случаев. - В случае изменения общедоступного свойства данного типа, Composition будет уведомлен.
- Все виды государственной собственности также стабильны.
В рамках этого контракта существует ряд важных распространенных типов, которые компилятор Compose будет рассматривать как стабильные, даже если они явно не помечены как стабильные с помощью аннотации @Stable :
- Все примитивные типы данных:
Boolean,Int,Long,Float,Charи т. д. - Строки
- Все типы функций (лямбда-выражения)
Все эти типы способны следовать контракту стабильности, поскольку они неизменяемы. Поскольку неизменяемые типы никогда не меняются, им никогда не нужно уведомлять Композицию об изменении, поэтому следовать этому контракту гораздо проще.
Одним из примечательных типов, который является стабильным, но при этом изменяемым, является тип MutableState в Compose. Если значение хранится в MutableState , объект состояния в целом считается стабильным, поскольку Compose будет уведомлен о любых изменениях свойства .value объекта State .
Когда все типы, переданные в качестве параметров компонуемому объекту, являются стабильными, значения параметров сравниваются на предмет равенства в зависимости от положения компонуемого объекта в дереве пользовательского интерфейса. Перекомпозиция пропускается, если все значения не изменились с момента предыдущего вызова.
Compose считает тип стабильным только в том случае, если может это доказать. Например, интерфейс обычно считается нестабильным, и типы с изменяемыми открытыми свойствами, реализация которых может быть неизменяемой, также нестабильны.
Если Compose не может определить, является ли тип стабильным, но вы хотите заставить Compose рассматривать его как стабильный, пометьте его аннотацией @Stable .
// Marking the type as stable to favor skipping and smart recompositions. @Stable interface UiState<T : Result<T>> { val value: T? val exception: Throwable? val hasError: Boolean get() = exception != null }
В приведенном выше фрагменте кода, поскольку UiState является интерфейсом, Compose обычно мог бы считать этот тип нестабильным. Добавив аннотацию @Stable , вы сообщаете Compose, что этот тип стабилен, что позволяет Compose отдавать предпочтение интеллектуальным перекомпозициям. Это также означает, что Compose будет рассматривать все свои реализации как стабильные, если в качестве типа параметра используется интерфейс.
Рекомендуем вам
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- State and Jetpack Compose
- Побочные эффекты в Compose
- Сохранение состояния пользовательского интерфейса в Compose