Производительность в Jetpack Compose: стабильность и неизменяемость



Книга Производительность в Jetpack Compose: стабильность и неизменяемость



При изменении состояния 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.




  1. app_release-classes.txt: отчет о стабильности классов.

  2. app_release-composables.txt: отчет о перезапускаемых и пропускаемых composable-функциях.

  3. app_release-composables.csv: версия вышеуказанного текстового файла в формате csv.

  4. 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 может спокойно пропустить эту функцию.



200   0  

Comments

    Ничего не найдено.