При изменении состояния Jetpack Compose может автоматически пропускать composable-функции, параметры которых остались без изменений. Это обеспечивает эффективный рендеринг и повышает производительность. Но в некоторых ситуациях такого не наблюдается, и Jetpack Compose вынужден проводить рекомпозицию composable
-функций.
В этой статье расскажем, почему эта операция не может быть выполнена и как повысить производительность приложения.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Stability_ImmutabilityTheme {
val (isChecked, onChecked) = remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
) {
Checkbox(
checked = isChecked,
onCheckedChange = onChecked
)
ContactList(
contactListState = ContactListState(
names = listOf("name1", "name2", "name3", "name4"),
)
)
}
}
}
}
}
@Composable
fun ContactList(
contactListState: ContactListState
) {
if (contactListState.isLoading) {
CircularProgressIndicator()
} else {
Text(text = contactListState.names.toString())
}
}
data class ContactListState(
val names: List<String>,
val isLoading: Boolean = false,
)
В этом примере у нас есть чекбокс и composable
-функция Contact List, которая отображает список контактов.
С помощью инструмента Layout Inspector мы можем наблюдать, какие элементы composables
перекомпонуются при изменении состояния. При этом перекомпонованные элементы подсвечиваются синим цветом. В то же время Layout Inspector показывает, сколько composable-функций было перекомпоновано и сколько было пропущено.
Инструмент Layout Inspector можно открыть в разделе Tools/Layout Inspector в Android Studio.
В нашем примере мы изменяем только состояние чекбокса, однако composable-функция Contact List
была перекомпонована четыре раза, хотя ни разу не менялись параметры Contact List
.
Перезапускаемые и пропускаемые функции в Jetpack Compose
Прежде чем рассказать о перезапускаемых и пропускаемых функциях, дадим определение рекомпозиции.
Рекомпозиция (перекомпоновка) — это процесс повторного вызова composable-функций при изменении входных данных. Это происходит, когда изменяются входные данные функции. Когда Compose выполняет рекомпозицию на основе новых входных данных, он вызывает только те функции и лямбды, которые могли измениться, а остальные пропускает (документация Android).
Компилятор Compose помечает функции как перезапускаемые (restartable
) и пропускаемые (skippable
), чтобы определить, является ли она пропускаемой или перезапускаемой.
Перезапускаемая функция (restartable)
Пометка функции как restartable
означает, что она может быть вызвана снова, если произойдут какие-либо изменения ее входных данных.
Пропускаемая функция (skippable)
Если функция помечена как пропускаемая (skippable
), можно пропустить ее вызов, если параметры не изменились с момента последнего вызова.
Как узнать, что функция помечена как перезапускаемая и/или пропускаемая?
Включите отчет о метриках компилятора Compose
subprojects {
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions.freeCompilerArgs.addAll(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
project.buildDir.absolutePath + "/compose_compiler",
)
compilerOptions.freeCompilerArgs.addAll(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
project.buildDir.absolutePath + "/compose_compiler",
)
}
}
Запишите приведенный выше скрипт в файл build.gradle.kts (Project).
Чтобы получить результат отчета, выполните следующую команду:
./gradlew assembleRelease -PcomposeCompilerReports=true
Эта задача сгенерирует четыре файла в каталоге build каждого модуля под файлом compose_compiler.
- app_release-classes.txt: отчет о стабильности классов.
- app_release-composables.txt: отчет о перезапускаемых и пропускаемых composable-функциях.
- app_release-composables.csv: версия вышеуказанного текстового файла в формате csv.
- app_release-module.json: содержит общую статистику.
После выполнения задачи откройте файл app_release-composables.txt — вы увидите все composable-функции. Каждая из них будет помечена либо как перезапускаемая (restartable), либо как пропускаемая (skippable).
restartable scheme("[androidx.compose.ui.UiComposable]") fun ContactList(
unstable contactListState: ContactListState
)
Composable-функция ContactList
помечена как restartable, а не как skippable. Почему?
Параметр ContactListState
помечен как нестабильный (unstable). Что это означает с точки зрения стабильности и как сделать его стабильным?
Стабильность
Стабильность параметров composable-функции имеет решающее значение для определения того, нужно ли проводить ее повторную оценку при рекомпозиции в компиляторе Compose.
Стабильные параметры
- Если параметры composable-функции стабильны и не изменились во время рекомпозиции, Compose пропускает ее.
- Так, стабильными параметрами являются типы
String
,Int
,Float
иImmutableList
.
Нестабильные параметры
- Если composable-функция имеет нестабильные параметры, Compose всегда проводит рекомпозицию, даже если параметры не изменились.
- Например, типы
List
,Map
иSet
являются нестабильными параметрами. Также нестабильными являются объявленияvar
вData class
.
val numbers: List<Int> = mutableListOf(1, 2, 3)
Поскольку стандартные классы Collection
определены в Kotlin как интерфейсы, их базовая реализация может оказаться нестабильной. Компилятор Compose не уверен в неизменности этого класса, поскольку видит только объявленный тип, и помечает его как нестабильный.
Посмотрим на класс данных ContactListState
. Чтобы увидеть отчет, откроем файл app_release-classes.
unstable class ContactListState {
unstable val names: List<String>
stable val isLoading: Boolean
<runtime stability> = Unstable
}
Как мы уже говорили, параметр List нестабилен. Если класс данных имеет нестабильные параметры, он также будет нестабильным.
Стабильные и неизменяемые аннотации
Неизменяемые аннотации. Как следует из названия, они содержат неизменяемые данные. Поскольку эти данные никогда не меняются, Compose может рассматривать их как стабильные.
@Immutable
data class ContactListState(
val names: List<String>,
val isLoading: Boolean = false,
)
Стабильные аннотации. В них хранятся данные, которые можно изменять, но при изменении необходимо уведомление на этапе Composition.
@Stable
data class ContactListState(
val names: List<String>,
val isLoading: Boolean = false,
)
Мы можем использовать любую из этих аннотаций. После внесения изменений повторно запустим метрики и посмотрим на результаты.
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun ContactList(
stable contactListState: ContactListState
)
Composable-функция ContactList
является перезапускаемой и пропускаемой. А параметры ContactList стабильны.
При изменении состояния только чекбокс подвергается рекомпозиции. Поскольку ни один из параметров ContactList не изменился и все они являются стабильными, Compose может спокойно пропустить эту функцию.
Читайте также:
- Реализация параллакс-карусели из SwiftUI в Jetpack Compose
- Jetpack Compose Canvas: 10 практических примеров
- Как оптимизировать навигацию в Jetpack Compose
Читайте нас в Telegram, VK и Дзен
Перевод статьи Tolga Pirim: Performance In Jetpack Compose — Stability & Immutability