На этой странице рассказывается о жизненном цикле функции, созданной с помощью Compose, и о том, как Compose определяет, нужно ли ее перекомпоновать.
Обзор жизненного цикла
Как указано в документации по управлению состоянием, композиция описывает интерфейс приложения и создается при запуске функций, поддерживающих композицию. Композиция – это древовидная структура композиций, описывающих ваш интерфейс.
Когда Jetpack Compose впервые запускает ваши функции, во время первоначальной композиции он отслеживает функции, которые вы вызываете для описания интерфейса в композиции. Когда состояние приложения меняется, Jetpack Compose планирует повторную композицию. Повторная композиция – это процесс, при котором Jetpack Compose повторно выполняет компонуемые функции, которые могли измениться в ответ на изменения состояния, а затем обновляет композицию, чтобы отразить эти изменения.
Композицию можно создать только на основе исходной композиции и обновить с помощью повторной композиции. Изменить композицию можно только путем ее повторного создания.
Перекомпоновка обычно запускается при изменении объекта State<T>. Compose отслеживает эти данные и запускает все компонуемые функции в композиции, которые считывают определенное значение State<T>, а также все компонуемые функции, которые они вызывают и которые нельзя пропустить.
Если компонуемый элемент вызывается несколько раз, в композицию добавляется несколько его экземпляров. У каждого вызова в композиции свой жизненный цикл.
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }
MyComposable в композиции. Если composable-функция вызывается несколько раз, в композицию добавляется несколько ее экземпляров. Элемент другого цвета – это отдельный экземпляр.Анатомия composable-функции в композиции
Экземпляр композиции в Composition определяется по месту вызова. Компилятор Compose рассматривает каждый сайт вызова как отдельный. При вызове composable-функций из нескольких мест вызова в композиции будет создано несколько экземпляров composable-функции.
Если во время рекомпозиции composable-функция вызывает другие composable-функции, чем во время предыдущей композиции, Compose определяет, какие composable-функции были вызваны, а какие нет. Для composable-функций, которые были вызваны в обеих композициях, Compose избегает рекомпозиции, если их входные данные не изменились.
Сохранение идентичности необходимо, чтобы связывать побочные эффекты с их composable-функцией, чтобы они могли успешно завершаться, а не перезапускаться при каждой рекомпозиции.
Пример:
@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 условно вызывает composable-функцию LoginError и всегда вызывает composable-функцию LoginInput. У каждого вызова есть уникальное место вызова и позиция источника, которые компилятор использует для его идентификации.
LoginScreen в композиции при изменении состояния и повторной композиции. Одинаковый цвет означает, что изображение не было перекомпоновано.Несмотря на то что LoginInput теперь вызывается вторым,
его LoginInput экземпляр будет сохранен при повторной композиции. Кроме того, поскольку у функции LoginInput нет параметров, которые изменились бы при повторной композиции, вызов LoginInput будет пропущен Compose.
Как добавить дополнительную информацию для интеллектуальной перекомпоновки
Если вызвать composable-функцию несколько раз, она будет добавлена в композицию несколько раз. Когда вызываемый компонуемый элемент вызывается несколько раз из одного и того же места вызова, у 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 composables в
Composition можно использовать повторно. Одинаковый цвет в MovieOverview означает, что composable-функция не была рекомпонована.Однако если список 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 означает, что composable-функция была перекомпонована.В идеале идентификатор экземпляра MovieOverview должен быть связан с идентификатором movie, который ему передается. Если мы изменим порядок фильмов в списке, то в идеале нужно будет изменить порядок экземпляров в дереве композиции, а не перекомпоновывать каждый компонуемый элемент MovieOverview с другим экземпляром фильма. Compose позволяет указать, какие значения нужно использовать для идентификации определенной части дерева: composable-функции key.
Если обернуть блок кода вызовом composable-функции key с одним или несколькими переданными значениями, эти значения будут объединены и использованы для идентификации экземпляра в композиции. Значение для key не должно быть глобально уникальным, оно должно быть уникальным только среди вызовов композиций в месте вызова. В этом примере каждый элемент movie должен иметь элемент key, который уникален среди элементов movies. При этом элемент key может быть общим с другими composable-функциями в приложении.
@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 не изменились, и может использовать их повторно. Их побочные эффекты будут выполняться и дальше.Некоторые компоненты composable поддерживают компонент key. Например, LazyColumn позволяет указать специальный key в языке описания items.
@Composable fun MoviesScreenLazy(movies: List<Movie>) { LazyColumn { items(movies, key = { movie -> movie.id }) { movie -> MovieOverview(movie) } } }
Пропуск, если входные данные не изменились
Во время повторной композиции выполнение некоторых подходящих функций может быть полностью пропущено, если их входные данные не изменились с момента предыдущей композиции.
Компонуемая функция может быть пропущена, если:
- Функция имеет тип возвращаемого значения, отличный от
Unit - Функция аннотирована с помощью
@NonRestartableComposableили@NonSkippableComposable. - Обязательный параметр имеет нестабильный тип
В режиме компилятора Пропуск с проверкой последнее требование не действует.
Чтобы тип считался стабильным, он должен соответствовать следующим требованиям:
- Значение
equalsдля двух экземпляров всегда будет одинаковым. - Если изменится общедоступное свойство типа, Composition получит уведомление.
- Все типы общедоступных свойств также стабильны.
Существуют важные распространенные типы, которые относятся к этому контракту и которые компилятор Compose будет считать стабильными, даже если они не помечены аннотацией @Stable:
- Все типы примитивных значений:
Boolean,Int,Long,Float,Charи т. д. - Струны
- Все типы функций (лямбда-выражения)
Все эти типы могут следовать контракту стабильности, поскольку они неизменяемы. Поскольку неизменяемые типы никогда не меняются, им не нужно уведомлять о составе изменений, поэтому следовать этому контракту гораздо проще.
Один из примечательных типов, который является стабильным, но изменяемым, – это тип MutableState в Compose. Если значение хранится в MutableState, объект состояния в целом считается стабильным, поскольку Compose будет получать уведомления о любых изменениях свойства .value объекта State.
Когда все типы, переданные в качестве параметров в composable-функцию, стабильны, значения параметров сравниваются на равенство на основе позиции composable-функции в дереве интерфейса. Если все значения остались прежними с момента предыдущего вызова, перекомпоновка пропускается.
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 отключен.
- Состояние и Jetpack Compose
- Побочные эффекты в Compose
- Сохранение состояния интерфейса в Compose