← Назад к вопросам

Можно ли в run-time понять что произошла рекомпозиция?

2.0 Middle🔥 162 комментариев
#UI и вёрстка#Производительность и оптимизация

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Можно ли во время выполнения (run-time) понять, что произошла рекомпозиция?

Короткий ответ: да, можно, но не напрямую через публичные API Compose, и обычно это не рекомендуется для production-кода. Jetpack Compose намеренно инкапсулирует механизм рекомпозиции, чтобы гарантировать предсказуемость и производительность. Прямого метода типа onRecomposed() или флага isRecomposing в API для разработчиков нет. Однако существует несколько подходов для отслеживания, отладки или реакции на факт рекомпозиции, каждый со своей областью применения.

Основные подходы для обнаружения рекомпозиции

1. Использование побочных эффектов и состояний для логирования

Самый простой способ — использовать LaunchedEffect или DisposableEffect с ключом, который меняется при рекомпозиции. Вы можете логировать или увеличивать счетчик.

@Composable
fun MyComposable() {
    var recompositionCount by remember { mutableStateOf(0) }

    // Эффект, который срабатывает при каждой рекомпозиции
    DisposableEffect(Unit) {
        recompositionCount++
        println("Recomposed! Count: $recompositionCount")
        onDispose { }
    }

    // Или с LaunchedEffect, если нужно реагировать на изменения ключа
    LaunchedEffect(recompositionCount) {
        // Этот блок будет выполняться при каждом изменении recompositionCount
    }
}

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

@Composable
fun MyComposable() {
    var logTrigger by remember { mutableStateOf(0) }

    SideEffect {
        println("Recomposition completed for MyComposable")
        // Здесь можно, например, отправлять аналитику
    }

    Button(onClick = { logTrigger++ }) {
        Text("Trigger")
    }
}

2. Инструменты отладки и инспекции в Android Studio

Для отладки и профилирования, а не для run-time логики в приложении, используйте встроенные инструменты:

  • Layout Inspector с поддержкой Compose показывает, какие компоненты были рекомпозированы.
  • Профайлер Compose в Android Studio имеет трассировки рекомпозиции, где можно увидеть, какие @Composable функции выполнялись и сколько раз.
  • Модификатор debugInspectorInfo или кастомные InspectorInfo для более детальной инспекции в инструментах разработчика.

3. Мониторинг через Recomposer и состояние композиции (продвинутый, для отладки)

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

import androidx.compose.runtime.*
import kotlinx.coroutines.flow.collect

@Composable
fun TrackRecompositions() {
    val recomposer = currentComposer.recomposer
    // Далее можно наблюдать за состоянием recomposer, но это внутренний API
    // и может измениться в любой версии.
}

Важно: Этот метод опирается на внутренние API Compose (currentComposer), которые не являются частью публичного контракта и могут сломаться в будущих версиях. Используйте только для отладки и глубокого анализа.

4. Кастомный CompositionLocal или обертка для отладочных целей

Можно создать механизм для ручного отслеживания рекомпозиций в определенных частях UI, передавая callback через CompositionLocal.

val LocalRecompositionTracker = staticCompositionLocalOf<(() -> Unit)?> { null }

@Composable
fun RecompositionAwareBox(content: @Composable () -> Unit) {
    val trackRecomposition = LocalRecompositionTracker.current
    SideEffect {
        trackRecomposition?.invoke()
    }
    content()
}

// Использование:
@Composable
fun ParentScreen() {
    var count by remember { mutableStateOf(0) }
    val tracker = remember {
        {
            println("Custom tracking: Recomposition detected at ${System.currentTimeMillis()}")
        }
    }
    CompositionLocalProvider(LocalRecompositionTracker provides tracker) {
        RecompositionAwareBox {
            Text("Count: $count")
            Button(onClick = { count++ }) { Text("Increment") }
        }
    }
}

Когда это действительно нужно?

  • Отладка избыточных рекомпозиций: Для поиска узких мест производительности.
  • Аналитика взаимодействия: Чтобы понять, как часто обновляется определенная часть интерфейса (хотя обычно лучше отслеживать изменения данных, а не сам факт рекомпозиции).
  • Тестирование: В UI-тестах можно проверять, что определенные события приводят к ожидаемым рекомпозициям.

Выводы и рекомендации

  1. Избегайте логики, зависящей от факта рекомпозиции, в production. Compose — это декларативный фреймворк, и ваши компоненты должны описывать UI для любого состояния данных, а не реагировать на процесс его обновления.
  2. Для отладки используйте инструменты Android Studio, а не кастомный run-time код.
  3. Если вам необходимо реагировать на изменения данных (что является причиной рекомпозиции), используйте соответствующие механизмы: LaunchedEffect(key), DerivedStateOf, SnapshotStateList/Map и колбэки onClick/onValueChange.
  4. Помните об инкапсуляции: Jetpack Compose не предоставляет публичного API для детектирования рекомпозиции, потому что это нарушило бы его декларативную модель и могло бы привести к неочевидным побочным эффектам.

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