Режим строгого пропуска

Strong Skipping – это режим, доступный в компиляторе Compose. Если он включен, поведение компилятора меняется двумя способами:

Включить режим строгого пропуска

В Kotlin 2.0.20 строгий пропуск включен по умолчанию.

Чтобы включить режим строгого пропуска для модуля Gradle в выпуске до 2.0.20, добавьте в блок composeCompiler конфигурации Gradle следующий параметр:

android { ... }

composeCompiler {
   enableStrongSkippingMode = true
}

Возможность пропуска для составных объявлений

Режим строгого пропуска смягчает некоторые правила стабильности, которые обычно применяются компилятором Compose, когда речь идет о пропуске и composable-функциях. По умолчанию компилятор Compose помечает composable-функцию как пропускаемую, если все ее аргументы имеют стабильные значения. Режим строгого пропуска меняет это поведение.

Если включить строгий пропуск, все перезапускаемые composable-функции станут пропускаемыми. Это относится и к тем, у кого нет нестабильных параметров. Неперезапускаемые composable-функции остаются непропускаемыми.

Когда пропускать

Чтобы определить, следует ли пропустить composable-функцию во время рекомпозиции, Compose сравнивает значение каждого параметра с его предыдущим значением. Тип сравнения зависит от стабильности параметра.

  • Нестабильные параметры сравниваются с помощью равенства экземпляров (===).
  • Стабильные параметры сравниваются с помощью равенства объектов (Object.equals()).

Если все параметры соответствуют этим требованиям, Compose пропускает composable-функцию во время рекомпозиции.

Вы можете отключить строгий пропуск для composable-функции. То есть вам может понадобиться перезапускаемая composable-функция без возможности пропуска. В этом случае используйте аннотацию @NonSkippableComposable.

@NonSkippableComposable
@Composable
fun MyNonSkippableComposable {}

Как помечать классы как стабильные

Если вы хотите, чтобы объект сравнивался с использованием равенства объектов (Object.equals()), а не равенства экземпляров (===), продолжайте аннотировать данный класс с помощью @Stable или @Immutable.

Прежде чем добавлять @Stable или @Immutable, учтите следующее:

  • Классы со стабильными свойствами в модулях Compose уже стабильны. Класс со свойствами val стабильных типов в модуле, скомпилированном с помощью компилятора Compose, автоматически определяется как стабильный и использует .equals() без необходимости в @Immutable или @Stable.
  • Добавляйте аннотации только к классам с быстрыми проверками .equals(). Добавляйте @Stable или @Immutable, когда компилятор не может определить стабильность (например, для классов из модулей, не относящихся к Compose) и часто выделяются новые экземпляры с идентичными значениями, при условии, что проверка .equals() класса выполняется быстро.
  • Будьте осторожны, добавляя аннотации к моделям с коллекциями. Если вы добавите @Stable или @Immutable к моделям, содержащим коллекции (или замените List на ImmutableList), Compose будет сравнивать каждый элемент коллекции с .equals() при каждом проходе рекомпозиции. Для больших коллекций или моделей, используемых в ленивых макетах (LazyColumn, LazyRow, LazyGrid), сравнение каждого элемента может быть медленнее, чем использование равенства экземпляров (===) или прямая рекомпозиция.

Мемоизация лямбда-функций

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

Примеры

Чтобы добиться мемоизации лямбда-выражений внутри composable-функций при использовании строгого пропуска, компилятор оборачивает лямбда-выражение вызовом remember. Она связана с захватами лямбда-выражения.

Рассмотрим случай, когда у вас есть лямбда-выражение, как в следующем примере:

@Composable
fun MyComposable(unstableObject: Unstable, stableObject: Stable) {
    val lambda = {
        use(unstableObject)
        use(stableObject)
    }
}

Если включен режим строгого пропуска, компилятор запоминает лямбда-выражение, упаковывая его в вызов remember:

@Composable
fun MyComposable(unstableObject: Unstable, stableObject: Stable) {
    val lambda = remember(unstableObject, stableObject) {
        {
            use(unstableObject)
            use(stableObject)
        }
    }
}

Ключи следуют тем же правилам сравнения, что и составные функции. Среда выполнения сравнивает нестабильные ключи, используя равенство экземпляров. Он сравнивает стабильные ключи, используя равенство объектов.

Мемоизация и рекомпозиция

Эта оптимизация значительно увеличивает количество composable-функций, которые среда выполнения пропускает во время рекомпозиции. Без мемоизации среда выполнения с гораздо большей вероятностью выделит новую лямбду для любой composable-функции, которая принимает лямбда-параметр во время рекомпозиции. В результате у новой лямбда-функции будут параметры, не равные последней композиции. Это приводит к рекомпозиции.

Избегайте мемоизации

Если у вас есть лямбда-выражение, которое вы не хотите запоминать, используйте аннотацию @DontMemoize.

val lambda = @DontMemoize {
    ...
}

Размер APK-файла

При компиляции пропускаемых composable-функций генерируется больше кода, чем при компиляции непропускаемых. Если включить строгий пропуск, компилятор пометит почти все composable-функции как пропускаемые и обернет все лямбда-выражения в remember{...}. Поэтому включение режима сильного пропуска очень мало влияет на размер APK-файла вашего приложения.

Включение строгого пропуска в Now In Android увеличило размер APK на 4 кБ. Разница в размере в основном зависит от количества ранее непропускаемых composable-функций, которые присутствовали в данном приложении, но должна быть относительно незначительной.