Диагностика проблем со стабильностью

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

Инспектор макетов

Инспектор макетов в Android Studio позволяет увидеть, какие composable-функции рекомпонуются в приложении. Он показывает, сколько раз Compose рекомпоновал или пропускал компонент.

Количество рекомпозиций и пропусков в инспекторе макетов

Отчеты компилятора Compose

Компилятор Compose может выводить результаты вывода стабильности для проверки. Используя эти выходные данные, вы можете определить, какие из ваших компонуемых объектов можно пропустить, а какие нет. В следующих подразделах кратко описано, как использовать эти отчеты. Более подробную информацию можно найти в технической документации.

Настроить

Отчеты компилятора Compose по умолчанию отключены. Вы можете активировать их с помощью флага компилятора. Точная настройка зависит от проекта, но для проектов, использующих плагин gradle компилятора Compose, вы можете добавить в файл build.gradle каждого модуля следующее:

  android { ... }

  composeCompiler {
    reportsDestination = layout.buildDirectory.dir("compose_compiler")
    metricsDestination = layout.buildDirectory.dir("compose_compiler")
  }

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

Пример выходных данных

reportsDestination создает три файла. Ниже приведены примеры выходных данных из JetSnack.

  • <modulename>-classes.txt. Отчет о стабильности классов в этом модуле. Пример.
  • <modulename>-composables.txt. Отчет о том, какие composable-функции в модуле можно перезапускать и пропускать. Пример.
  • <modulename>-composables.csv: версия отчета о composable-функциях в формате CSV, которую можно импортировать в таблицу или обработать с помощью скрипта. Пример

Отчет о composable-функциях

В файле composables.txt содержится подробная информация о каждой composable-функции для заданного модуля, включая стабильность их параметров, а также возможность перезапуска или пропуска. Ниже приведен гипотетический пример из JetSnack.

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun SnackCollection(
  stable snackCollection: SnackCollection
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
  stable index: Int = @static 0
  stable highlight: Boolean = @static true
)

В этом примере каждый параметр SnackCollection помечен как stable, поэтому Compose сравнивает каждый параметр, используя структурное равенство (Object.equals()). Хотя параметры stable идеально подходят для примитивных значений и небольших плоских классов данных с дешевыми проверками .equals(), принудительное использование моделей, содержащих коллекции (например, SnackCollection, который оборачивает List<Snack>), в качестве stable заставляет Compose вызывать .equals() для каждого элемента при каждой рекомпозиции. Если включен режим строгого пропуска, рекомендуется сохранять модели, содержащие коллекции unstable, чтобы Compose сравнивал их, используя равенство экземпляров (===).

Рассмотрим другой пример, когда режим строгого пропуска отключен:

restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  stable index: Int
  unstable snacks: List<Snack>
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
)

Если режим строгого пропуска отключен (до Kotlin 2.0.20), composable-функция HighlightedSnacks не пропускается. Compose никогда не пропускает ее во время рекомпозиции, даже если ни один из ее параметров не изменился, из-за параметра unstable snacks.

Если включен режим строгого пропуска (по умолчанию в Kotlin 2.0.20 и более поздних версиях), HighlightedSnacks помечается как skippable, а Compose сравнивает параметр unstable snacks, используя равенство экземпляров (===). В результате Compose пропускает HighlightedSnacks, если передается та же ссылка List, и перекомпонует ее, только если передается вновь выделенный экземпляр List.

Отчет о курсах

Файл classes.txt содержит аналогичный отчет о классах в указанном модуле. Ниже приведен фрагмент кода, который возвращается для класса Snack:

unstable class Snack {
  stable val id: Long
  stable val name: String
  stable val imageUrl: String
  stable val price: Long
  stable val tagline: String
  unstable val tags: Set<String>
  <runtime stability> = Unstable
}

Ниже приведено определение термина "Snack".

data class Snack(
    val id: Long,
    val name: String,
    val imageUrl: String,
    val price: Long,
    val tagline: String = "",
    val tags: Set<String> = emptySet()
)

Компилятор Compose пометил Snack как нестабильный. Это связано с тем, что параметр tags имеет тип Set<String>. Это неизменяемый тип, поскольку он не является MutableSet. Однако стандартные классы коллекций, такие как Set, List и Map, в конечном счете являются интерфейсами. Таким образом, базовая реализация все равно может быть изменяемой.

Например, вы можете написать val set: Set<String> = mutableSetOf("foo"). Переменная является константой, а ее объявленный тип неизменяем, но ее реализация все ещё изменяема. Компилятор Compose не может быть уверен в неизменяемости этого класса, поскольку он видит только объявленный тип. Поэтому он помечает tags как нестабильный.