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 mostra quantas vezes o Compose recompôs ou ignorou um componente.

Contagens de recomposição e de elementos 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. Com essa saída, é possível determinar quais elementos combináveis são puláveis e quais não são. As subseções a seguir resumem como usar esses relatórios, mas para informações mais detalhadas, consulte a documentação técnica.

Configuração

Os relatórios do compilador do Compose não são ativados por padrão. É possível ativá-los com uma flag do compilador. A configuração exata varia de acordo com o projeto, mas para projetos que usam o plug-in do Gradle do compilador do Compose, é possível adicionar o seguinte no arquivo build.gradle de cada módulo.

  android { ... }

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

Os relatórios do compilador do Compose agora serão gerados ao criar seu projeto.

Exemplo de saída

O reportsDestination gera três arquivos. Confira abaixo exemplos de saídas do JetSnack.

  • <modulename>-classes.txt:um relatório sobre a estabilidade das classes neste módulo. Exemplo.
  • <modulename>-composables.txt:um relatório sobre como os elementos que podem ser compostos podem ser reiniciados e pulados no módulo. Exemplo.
  • <modulename>-composables.csv:uma versão CSV do relatório de elementos que podem ser compostos que pode ser importada para uma planilha ou processada usando um script. Amostra

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 elas são reiniciáveis ou puláveis. Veja 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
)

Neste exemplo, cada parâmetro de SnackCollection é marcado como stable. Portanto, o Compose compara cada parâmetro usando a igualdade estrutural (Object.equals()). Embora os parâmetros stable sejam ideais para valores primitivos e classes de dados pequenas e simples com verificações .equals() baratas, forçar modelos que contêm coleções (como um SnackCollection que envolve um List<Snack>) a serem stable faz com que o Compose chame .equals() em cada item em cada recomposição. Com o modo de rejeição avançada ativado, é preferível manter os modelos que contêm coleções unstable para que o Compose os compare usando a igualdade de instâncias (===).

Como alternativa, considere outro exemplo gerado com o modo de rejeição avançada desativado:

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
)

Quando o modo de rejeição avançada está desativado (antes do Kotlin 2.0.20), o combinável HighlightedSnacks não pode ser ignorado. O Compose nunca o ignora durante a recomposição, mesmo que nenhum dos parâmetros tenha mudado, devido ao parâmetro unstable snacks.

Com o modo de rejeição avançada ativado (o padrão no Kotlin 2.0.20 e versões mais recentes), HighlightedSnacks é marcado como skippable, e o Compose compara o parâmetro unstable snacks usando a igualdade de instância (===). Como resultado, o Compose ignora HighlightedSnacks se a mesma referência List é transmitida e só a recompõe se uma instância List recém-alocada é transmitida.

Relatório de turmas

O arquivo classes.txt contém um relatório semelhante sobre as classes no módulo especificado. O snippet a seguir é a saída da 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, veja 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 do 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. Portanto, a implementação subjacente ainda pode ser mutável.

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