Стабильность в Compose

Compose считает типы стабильными или нестабильными. Тип является стабильным, если он неизменяем или если Compose может определить, изменилось ли его значение между рекомпозициями. Тип нестабилен, если Compose не может определить, изменилось ли его значение между рекомпозициями.

Compose использует стабильность параметров composable-функции, чтобы определить, как сравнивать входные данные и решать, можно ли пропустить composable-функцию во время рекомпозиции (с включенным по умолчанию режимом строгого пропуска, начиная с Kotlin 2.0.20):

  • Стабильные параметры. Compose сравнивает стабильные параметры, используя структурное равенство (Object.equals()). Если стабильные параметры composable-функции равны предыдущим значениям, Compose пропускает ее.
  • Нестабильные параметры. Если включен режим строгого пропуска (по умолчанию в Kotlin 2.0.20 и более поздних версиях), Compose сравнивает нестабильные параметры, используя равенство экземпляров (===), и пропускает composable-функцию, если передаются те же экземпляры объектов. В версиях до Kotlin 2.0.20 (или когда режим строгого пропуска отключен) Compose всегда перекомпонует composable-функцию с нестабильными параметрами, когда перекомпонуется ее родительский элемент.

Если ваше приложение часто выделяет новые экземпляры нестабильных параметров или принудительно выполняет ресурсоемкие проверки .equals() для больших коллекций из-за чрезмерного аннотирования моделей, вы можете заметить ненужные рекомпозиции или накладные расходы на сравнение.

В этом документе подробно описано, как Compose определяет стабильность и как вы можете оптимизировать его для повышения производительности и общего удобства работы пользователей.

Неизменяемые объекты

В следующих фрагментах кода показаны общие принципы стабильности и рекомпозиции.

Класс Contact – это неизменяемый класс данных. Это связано с тем, что все его параметры являются примитивами, определенными с помощью ключевого слова val. После создания экземпляра Contact вы не сможете изменить значения свойств объекта. Если вы попытаетесь это сделать, будет создан новый объект.

data class Contact(val name: String, val number: String)

У composable-функции ContactRow есть параметр типа Contact.

@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
   var selected by remember { mutableStateOf(false) }

   Row(modifier) {
      ContactDetails(contact)
      ToggleButton(selected, onToggled = { selected = !selected })
   }
}

Рассмотрим, что происходит, когда пользователь нажимает переключатель и состояние selected меняется:

  1. Compose оценивает, нужно ли перекомпоновать код внутри ContactRow.
  2. Единственный аргумент для ContactDetails имеет тип Contact.
  3. Поскольку Contact – неизменяемый класс данных, Compose уверен, что ни один из аргументов для ContactDetails не изменился.
  4. Поэтому Compose пропускает ContactDetails и не перекомпоновывает его.
  5. С другой стороны, аргументы для ToggleButton изменились, и Compose перекомпонует этот компонент.

Изменяемые объекты

В предыдущем примере используется неизменяемый объект, но можно создать и изменяемый. Рассмотрим следующий фрагмент кода:

data class Contact(var name: String, var number: String)

Поскольку каждый параметр Contact теперь является var, класс больше не является неизменяемым. Если бы его свойства изменились, Compose не узнал бы об этом. Это связано с тем, что Compose отслеживает изменения только в объектах State.

Compose считает такой класс нестабильным. В режиме строгого пропуска (Kotlin 2.0.20+) Compose сравнивает Contact по равенству экземпляров (===). Изменение contact.name на месте не вызовет рекомпозицию, а передача нового экземпляра Contact с идентичными значениями приведет к рекомпозиции ContactDetails. Без режима строгого пропуска ContactDetails рекомпонуется при каждом изменении selected.

Реализация в Compose

Полезно, хотя и не критично, рассмотреть, как именно Compose определяет, какие функции следует пропустить во время рекомпозиции.

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

Функции

Compose может помечать функции как skippable или restartable. Обратите внимание, что функция может быть помечена как одна из них, обе или ни одна из них:

  • Пропускаемая. Если компилятор помечает composable-функцию как пропускаемую, Compose может пропустить ее во время рекомпозиции, если все ее аргументы равны своим предыдущим значениям. (В Kotlin 2.0.20+ с режимом строгого пропуска все перезапускаемые composable-функции автоматически становятся пропускаемыми.)
  • Перезапускаемая. Перезапускаемая composable-функция служит областью, в которой может начаться рекомпозиция. Другими словами, функция может быть точкой входа, с которой Compose может начать повторное выполнение кода для рекомпозиции после изменения состояния.

Типы

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

  • Неизменяемый. Compose помечает тип как неизменяемый, если значение его свойств никогда не может измениться и все методы ссылочно прозрачны.
    • Обратите внимание, что все примитивные типы помечены как неизменяемые. К ним относятся String, Int и Float.
  • Стабильный. Указывает на тип, свойства которого могут измениться после создания. Если эти свойства изменяются во время выполнения, Compose узнает об этих изменениях.

Стабильность отладки

Если приложение перекомпонует composable-функцию, параметры которой не изменились, сначала проверьте, выделяются ли новые экземпляры нестабильных типов при каждой перекомпоновке (или, если режим строгого пропуска отключен, есть ли у composable-функции параметры со свойствами var или val нестабильного типа).

Подробную информацию о том, как диагностировать сложные проблемы со стабильностью в Compose, можно найти в руководстве по отладке стабильности.

Устранение проблем со стабильностью

Подробнее о том, как устранить проблемы со стабильностью в Compose…

Сводка

В целом следует учитывать следующее:

  • Параметры. Compose определяет стабильность каждого параметра composable-функций, чтобы решить, сравнивать ли их с помощью структурного равенства (.equals()) или равенства экземпляров (===) во время рекомпозиции.
  • Быстрые исправления. Если вы заметили, что composable-функция не пропускается и это приводит к проблемам производительности, проверьте, создаются ли новые экземпляры нестабильных параметров при каждом проходе и используются ли свойства var вместо State.
  • Отчеты компилятора. Вы можете использовать отчеты компилятора, чтобы определить, какая стабильность выводится для ваших классов.
  • Коллекции. Compose считает стандартные интерфейсы коллекций (List, Set и Map) нестабильными, поскольку их базовые реализации могут быть изменяемыми. В режиме строгого пропуска (включен по умолчанию в Kotlin 2.0.20 и более поздних версиях) нестабильные коллекции по-прежнему пропускают рекомпозицию, используя равенство экземпляров (===), когда ссылка на коллекцию не меняется. Будьте осторожны при замене стандартных коллекций неизменяемыми коллекциями Kotlinx или аннотировании классов, содержащих коллекции, с помощью @Immutable или @Stable, поскольку сравнение каждого элемента с .equals() при каждом проходе рекомпозиции может быть более затратным, чем сама рекомпозиция.
  • Другие модули. Compose считает классы из модулей, в которых не запущен компилятор Compose, нестабильными (сравниваются с помощью === в режиме строгого пропуска). Если источник данных часто повторно создает экземпляры небольших плоских моделей из модулей, не относящихся к Compose, с идентичными значениями, вы можете настроить файл конфигурации стабильности или использовать @Stable или @Immutable, где .equals() не требует больших ресурсов.

Дополнительные ресурсы