Столкнувшись с нестабильным классом , вызывающим проблемы с производительностью, вам следует сделать его стабильным. В этом документе описываются несколько методов, которые вы можете использовать для этого.
Включить сильный пропуск
Сначала вам следует попробовать включить режим сильного пропуска. Режим строгого пропуска позволяет пропускать составные объекты с нестабильными параметрами и является самым простым способом устранения проблем с производительностью, вызванных стабильностью.
Дополнительную информацию см. в разделе «Сильный пропуск» .
Сделать класс неизменяемым
Вы также можете попытаться сделать нестабильный класс полностью неизменяемым.
- Неизменяемый : указывает тип, в котором значение каких-либо свойств никогда не может измениться после создания экземпляра этого типа, а все методы являются ссылочно прозрачными.
- Убедитесь, что все свойства класса имеют значение
val
, а неvar
, и имеют неизменяемые типы. - Примитивные типы, такие как
String, Int
иFloat
всегда неизменяемы. - Если это невозможно, вы должны использовать состояние Compose для любых изменяемых свойств.
- Убедитесь, что все свойства класса имеют значение
- Стабильный : указывает изменяемый тип. Среда выполнения Compose не узнает, если и когда какие-либо общедоступные свойства или поведение метода типа приведут к результатам, отличным от предыдущего вызова.
Неизменяемые коллекции
Распространенной причиной, по которой Compose считает класс нестабильным, являются коллекции. Как отмечено на странице Диагностика проблем стабильности , компилятор Compose не может быть полностью уверен, что такие коллекции, как List, Map
и Set
, действительно неизменяемы, и поэтому помечает их как нестабильные.
Чтобы решить эту проблему, вы можете использовать неизменяемые коллекции. Компилятор Compose включает поддержку Kotlinx Immutable Collections . Эти коллекции гарантированно будут неизменяемыми, и компилятор Compose рассматривает их как таковые. Эта библиотека все еще находится в стадии альфа-версии, поэтому ожидайте возможных изменений в ее API.
Рассмотрим еще раз этот нестабильный класс из руководства «Диагностика проблем стабильности» :
unstable class Snack {
…
unstable val tags: Set<String>
…
}
Вы можете сделать tags
стабильными, используя неизменяемую коллекцию. В классе измените тип tags
на ImmutableSet<String>
:
data class Snack{
…
val tags: ImmutableSet<String> = persistentSetOf()
…
}
После этого все параметры класса становятся неизменяемыми, а компилятор Compose помечает класс как стабильный.
Аннотировать с помощью Stable
или Immutable
Возможный путь решения проблем со стабильностью — аннотировать нестабильные классы с помощью @Stable
или @Immutable
.
Аннотирование класса переопределяет то, что в противном случае компилятор мог бы сделать вывод о вашем классе. Это похоже на !!
оператор в Котлине . Вы должны быть очень осторожны с использованием этих аннотаций. Переопределение поведения компилятора может привести к непредвиденным ошибкам, например к тому, что компоновка не будет перекомпонована, когда вы этого ожидаете.
Если возможно сделать ваш класс стабильным без аннотации, вам следует стремиться добиться стабильности таким образом.
Следующий фрагмент представляет собой минимальный пример класса данных, помеченного как неизменяемый:
@Immutable
data class Snack(
…
)
Независимо от того, используете ли вы аннотацию @Immutable
или @Stable
, компилятор Compose помечает класс Snack
как стабильный.
Аннотированные классы в коллекциях
Рассмотрим компонуемый объект, включающий параметр типа List<Snack>
:
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
Даже если вы аннотируете Snack
с помощью @Immutable
, компилятор Compose по-прежнему помечает параметр snacks
в HighlightedSnacks
как нестабильный.
Когда дело доходит до типов коллекций, параметры сталкиваются с той же проблемой, что и классы : компилятор Compose всегда помечает параметр типа List
как нестабильный , даже если он представляет собой коллекцию стабильных типов.
Вы не можете пометить отдельный параметр как стабильный или аннотировать составной элемент, чтобы его всегда можно было пропустить. Есть несколько путей вперед.
Есть несколько способов обойти проблему нестабильных коллекций. В следующих подразделах описаны эти различные подходы.
Конфигурационный файл
Если вы согласны соблюдать контракт стабильности в своей кодовой базе, вы можете согласиться считать коллекции Kotlin стабильными, добавив kotlin.collections.*
в свой файл конфигурации стабильности .
Неизменяемая коллекция
Для обеспечения безопасности неизменяемости во время компиляции вы можете использовать неизменяемую коллекцию Kotlinx вместо List
.
@Composable
private fun HighlightedSnacks(
…
snacks: ImmutableList<Snack>,
…
)
обертка
Если вы не можете использовать неизменяемую коллекцию, вы можете создать свою собственную. Для этого оберните List
в аннотированный стабильный класс. Общая оболочка, вероятно, будет лучшим выбором для этого, в зависимости от ваших требований.
@Immutable
data class SnackCollection(
val snacks: List<Snack>
)
Затем вы можете использовать это как тип параметра в вашем компонуемом объекте.
@Composable
private fun HighlightedSnacks(
index: Int,
snacks: SnackCollection,
onSnackClick: (Long) -> Unit,
modifier: Modifier = Modifier
)
Решение
После применения любого из этих подходов компилятор Compose теперь помечает HighlightedSnacks
Composable как skippable
и restartable
.
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
stable index: Int
stable snacks: ImmutableList<Snack>
stable onSnackClick: Function1<Long, Unit>
stable modifier: Modifier? = @static Companion
)
Во время рекомпозиции Compose теперь может пропускать HighlightedSnacks
, если ни один из его входных данных не изменился.
Файл конфигурации стабильности
Начиная с Compose Compiler 1.5.5, файл конфигурации классов, которые считаются стабильными, может быть предоставлен во время компиляции. Это позволяет считать классы, которые вы не контролируете, такие как классы стандартной библиотеки, такие как LocalDateTime
, стабильными.
Файл конфигурации представляет собой обычный текстовый файл с одним классом в строке. Поддерживаются комментарии, одинарные и двойные подстановочные знаки. Пример конфигурации:
// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider my datalayer stable
com.datalayer.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>
Чтобы включить эту функцию, передайте путь к файлу конфигурации в блок параметров composeCompiler
конфигурации плагина Gradle компилятора Compose .
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
Поскольку компилятор Compose работает с каждым модулем вашего проекта отдельно, вы можете при необходимости предоставить разные конфигурации для разных модулей. Альтернативно, создайте одну конфигурацию на корневом уровне вашего проекта и передайте этот путь каждому модулю.
Несколько модулей
Другая распространенная проблема связана с многомодульной архитектурой. Компилятор Compose может сделать вывод о том, является ли класс стабильным, только если все непримитивные типы, на которые он ссылается, либо явно помечены как стабильные, либо находятся в модуле, который также был создан с помощью компилятора Compose.
Если ваш уровень данных находится в отдельном модуле для вашего уровня пользовательского интерфейса (что является рекомендуемым подходом), это может стать проблемой, с которой вы можете столкнуться.
Решение
Для решения этой проблемы можно воспользоваться одним из следующих подходов:
- Добавьте классы в файл конфигурации компилятора .
- Включите компилятор Compose в модулях уровня данных или пометьте свои классы тегами
@Stable
или@Immutable
, где это необходимо.- Это предполагает добавление зависимости Compose к вашему уровню данных. Однако это всего лишь зависимость для среды выполнения Compose, а не для
Compose-UI
.
- Это предполагает добавление зависимости Compose к вашему уровню данных. Однако это всего лишь зависимость для среды выполнения Compose, а не для
- В модуле пользовательского интерфейса оберните классы уровня данных в классы-оболочки, специфичные для пользовательского интерфейса.
Та же проблема возникает и при использовании внешних библиотек, если они не используют компилятор Compose.
Не все составные элементы можно пропустить
При работе над исправлением проблем со стабильностью не следует пытаться сделать так, чтобы все составные элементы можно было пропустить. Попытка сделать это может привести к преждевременной оптимизации, которая создаст больше проблем, чем исправит.
Существует множество ситуаций, когда возможность пропуска не приносит никакой реальной пользы и может привести к сложности сопровождения кода. Например:
- Составной объект, который не перекомпонуется часто или вообще не перекомпоновывается.
- Компонуемый объект, который сам по себе просто вызывает компонуемые объекты с возможностью пропуска.
- Составной элемент с большим количеством параметров и дорогими реализациями равенства. В этом случае стоимость проверки того, изменился ли какой-либо параметр, может перевесить стоимость дешевой рекомпозиции.
Когда составной элемент можно пропустить, это добавляет небольшие накладные расходы, которые могут того не стоить. Вы даже можете аннотировать свой составной объект как неперезапускаемый в тех случаях, когда вы решите, что возможность перезапуска требует больше накладных расходов, чем оно того стоит.