Если вы столкнулись с нестабильным классом, вызывающим проблемы с производительностью, вам следует сделать его стабильным. В этом документе описаны несколько методов, которые можно использовать для этого.
Включить строгий пропуск
В первую очередь следует попробовать включить режим строгого пропуска. Этот режим позволяет пропускать компонуемые объекты с нестабильными параметрами и является самым простым способом устранения проблем с производительностью, вызванных нестабильностью.
См. раздел «Сильные прыжки через скакалку» для получения более подробной информации.
Сделайте класс неизменяемым.
Вы также можете попытаться сделать нестабильный класс полностью неизменяемым.
- Неизменяемый : указывает на тип, в котором значение любых свойств никогда не может измениться после создания экземпляра этого типа, и все методы являются ссылочно прозрачными.
- Убедитесь, что все свойства класса имеют тип
val, а неvar, и являются неизменяемыми. - Примитивные типы, такие как
String, IntиFloatвсегда неизменяемы. - Если это невозможно, то для всех изменяемых свойств необходимо использовать состояние Compose.
- Убедитесь, что все свойства класса имеют тип
- Stable : Указывает на изменяемый тип. Среда выполнения Compose не знает, будут ли какие-либо из открытых свойств типа или поведение методов приводить к результатам, отличным от предыдущего вызова.
Неизменяемые коллекции
Одной из распространенных причин, по которой Compose считает класс нестабильным, являются коллекции. Как отмечено на странице «Диагностика проблем стабильности» , компилятор Compose не может быть полностью уверен в том, что такие коллекции, как List, Map и Set действительно неизменяемы, и поэтому помечает их как нестабильные.
Для решения этой проблемы можно использовать неизменяемые коллекции. Компилятор Compose поддерживает неизменяемые коллекции Kotlinx . Гарантируется, что эти коллекции являются неизменяемыми, и компилятор 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 .
Аннотирование класса означает переопределение того, что компилятор в противном случае определил бы о вашем классе. Это похоже на оператор !! в Kotlin . Следует быть очень осторожным при использовании этих аннотаций. Переопределение поведения компилятора может привести к непредвиденным ошибкам, например, к тому, что ваш компонуемый объект не будет перекомпонован, когда вы этого ожидаете.
Если стабильность вашего класса можно обеспечить без использования аннотаций, следует стремиться к её достижению именно таким способом.
Приведённый ниже фрагмент кода представляет собой минимальный пример класса данных, помеченного как неизменяемый:
@Immutable
data class Snack(
…
)
Независимо от того, используете ли вы аннотацию @Immutable или @Stable , компилятор Compose помечает класс Snack как стабильный.
Аннотированные классы в коллекциях
Рассмотрим составной объект, включающий параметр типа List<Snack> :
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
…
unstable snacks: List<Snack>
…
)
Даже если вы добавите аннотацию @Immutable к классу ` Snack , компилятор 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.
Не каждый составной элемент можно пропустить.
При работе над устранением проблем со стабильностью не следует пытаться сделать каждый компонуемый компонент пропускаемым. Попытка сделать это может привести к преждевременной оптимизации, которая создаст больше проблем, чем решит.
Существует множество ситуаций, когда возможность пропуска не приносит реальной пользы и может привести к трудноподдерживаемому коду. Например:
- Составляемый объект, который редко или вообще не пересоставляется.
- Композитный объект, который сам по себе просто вызывает пропускаемые композитные объекты.
- Компонуемый объект с большим количеством параметров и дорогостоящими реализациями оператора равенства. В этом случае затраты на проверку изменения какого-либо параметра могут превысить затраты на дешевую рекомпозицию.
Если составной объект допускает пропуск, это добавляет небольшие накладные расходы, которые могут оказаться неоправданными. Вы даже можете пометить свой составной объект как неперезапускаемый в тех случаях, когда вы считаете, что возможность перезапуска создает больше накладных расходов, чем это того стоит.