Diagnosticar problemas de estabilidade

Se você estiver enfrentando problemas de desempenho resultantes de recomposição desnecessária ou excessiva, depure a estabilidade do app. Este guia descreve vários métodos para fazer isso.

Layout Inspector

O Layout Inspector no Android Studio permite conferir quais elementos combináveis estão sendo recompostos no app. Ele exibe contagens de quantas vezes o Compose recompõe ou ignorou um componente.

Recomposição e contagens de ignorados no Layout Inspector

Relatórios do compilador do Compose

O compilador do Compose pode gerar os resultados da inferência de estabilidade para inspeção. Usando essa saída, é possível determinar quais dos elementos combináveis são puláveis e quais não são. As subseções a seguir resumem como usar esses relatórios. Para informações mais detalhadas, consulte a documentação técnica.

Configurar

Os relatórios do compilador do compilador não são ativados por padrão. Você pode ativá-los com uma flag do compilador. A configuração exata varia de acordo com o projeto. Porém, para a maioria deles, você pode colar o script abaixo no arquivo raiz build.gradle.

Groovy

subprojects {
  tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
        kotlinOptions {
            if (project.findProperty("composeCompilerReports") == "true") {
                freeCompilerArgs += [
                        "-P",
                        "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
                                project.buildDir.absolutePath + "/compose_compiler"
                ]
            }
            if (project.findProperty("composeCompilerMetrics") == "true") {
                freeCompilerArgs += [
                        "-P",
                        "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
                                project.buildDir.absolutePath + "/compose_compiler"
                ]
            }
        }
    }
}

Kotlin

subprojects {
    tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
        kotlinOptions {
            if (project.findProperty("composeCompilerReports") == "true") {
                freeCompilerArgs += listOf(
                    "-P",
                    "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${project.buildDir.absolutePath}/compose_compiler"
                )
            }
            if (project.findProperty("composeCompilerMetrics") == "true") {
                freeCompilerArgs += listOf(
                    "-P",
                    "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=${project.buildDir.absolutePath}/compose_compiler"
                )
            }
        }
    }
}

Executar a tarefa

Para depurar a estabilidade dos elementos combináveis, execute a tarefa desta forma:

./gradlew assembleRelease -PcomposeCompilerReports=true

Exemplo de saída

Essa tarefa gera três arquivos. Confira abaixo exemplos de saídas do JetSnack.

  • <modulename>-classes.txt:relatório sobre a estabilidade das classes neste módulo. Amostra.
  • <modulename>-composables.txt:um relatório sobre a capacidade de reiniciar e pulsar os elementos combináveis no módulo. Amostra.
  • <modulename>-composables.csv:uma versão CSV do relatório de elementos combináveis, que pode ser importada para uma planilha ou um processamento usando um script. Exemplo

Relatório de elementos combináveis

O arquivo composables.txt detalha cada função combinável do módulo especificado, incluindo a estabilidade dos parâmetros e se eles são reiniciáveis ou puláveis. Confira a seguir um exemplo hipotético do 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
)

O elemento combinável SnackCollection é completamente reiniciável, pulável e estável. Isso geralmente é preferível, embora certamente não seja obrigatório.

Por outro lado, vamos conferir outro exemplo.

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
)

O elemento combinável HighlightedSnacks não é pulável. O Compose nunca a pula durante a recomposição. Isso vai acontecer mesmo que nenhum dos parâmetros seja alterado. O motivo para isso é o parâmetro unstable, snacks.

Relatório de turmas

O arquivo classes.txt contém um relatório semelhante sobre as classes do módulo em questão. O snippet a seguir é a saída para a classe 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
}

Para referência, esta é a definição de Snack:

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

O compilador do Compose marcou Snack como instável. Isso ocorre porque o tipo de parâmetro tags é Set<String>. Esse é um tipo imutável, já que não é um MutableSet. No entanto, as classes de coleção padrão, como Set, List e Map, são interfaces. Assim, a implementação subjacente ainda pode ser mutável.

Por exemplo, é possível escrever val set: Set<String> = mutableSetOf("foo"). A variável é constante e o tipo declarado não é mutável, mas a implementação dela ainda é mutável. O compilador do Compose não tem certeza da imutabilidade dessa classe, já que só vê o tipo declarado. Portanto, ela marca tags como instável.