Modo forte para pular

A rejeição avançada é um modo disponível no compilador do Compose. Quando ativada, ela muda o comportamento do compilador de duas maneiras:

  • Os elementos combináveis com parâmetros instáveis se tornam puláveis.
  • Lambdas com capturas instáveis são lembradas.

Ativar o modo de rejeição avançada

A pulagem forte é ativada por padrão no Kotlin 2.0.20.

Para ativar a pulagem forte em um módulo do Gradle em uma versão anterior à 2.0.20, inclua a seguinte opção no bloco composeCompiler da configuração do Gradle:

android { ... }

composeCompiler {
   enableStrongSkippingMode = true
}

Capacidade de pular elementos combináveis

O modo de pulo forte flexibiliza algumas das regras de estabilidade normalmente aplicadas pelo compilador do Compose quando se trata de pulos e funções combináveis. Por padrão, o compilador do Compose marca uma função combinável como pulável se todos os argumentos dela tiverem valores estáveis. O modo de rejeição avançada muda isso.

Com a opção de pular forte ativada, todas as funções combináveis que podem ser reiniciadas se tornam puláveis. Isso se aplica mesmo que eles tenham parâmetros instáveis. As funções combináveis que não podem ser reiniciadas continuam não puláveis.

Quando pular

Para determinar se um elemento combinável deve ser ignorado durante a recomposição, o Compose compara o valor de cada parâmetro com os valores anteriores. O tipo de comparação depende da estabilidade do parâmetro.

  • Os parâmetros instáveis são comparados usando a igualdade de instância (===).
  • Os parâmetros estáveis são comparados usando a igualdade de objetos (Object.equals()).

Se todos os parâmetros atenderem a esses requisitos, o Compose vai pular o elemento combinável durante a recomposição.

Talvez você queira que um elemento combinável desative a pulagem forte. Ou seja, talvez você queira um elemento que possa ser reiniciado, mas não pulado. Nesse caso, use a anotação @NonSkippableComposable.

@NonSkippableComposable
@Composable
fun MyNonSkippableComposable {}

Anotar classes como estáveis

Se você quiser que um objeto seja comparado usando a igualdade de objeto (Object.equals()) em vez da igualdade de instância (===), continue anotando a classe em questão com @Stable ou @Immutable.

Lembre-se das seguintes compensações antes de adicionar @Stable ou @Immutable:

  • As classes com propriedades estáveis em módulos do Compose já são estáveis: uma classe com propriedades val de tipos estáveis em um módulo compilado com o compilador do Compose é inferida automaticamente como estável e usa .equals() sem precisar de @Immutable ou @Stable.
  • Anote apenas classes com verificações .equals() baratas: adicione @Stable ou @Immutable quando o compilador não puder inferir a estabilidade (como classes de módulos que não são do Compose) e novas instâncias com valores idênticos forem alocadas com frequência, desde que a verificação .equals() da classe seja barata.
  • Tenha cuidado ao anotar modelos com coleções: adicionar @Stable ou @Immutable a modelos que contêm coleções (ou substituir List por ImmutableList) faz com que o Compose compare cada elemento na coleção com .equals() em cada passagem de recomposição. Para grandes coleções ou modelos usados em layouts lentos (LazyColumn, LazyRow, LazyGrid), comparar cada elemento pode ser mais lento do que usar a igualdade de instâncias (===) ou recompor diretamente.

Memoização de lambda

O modo de rejeição avançada também permite mais memorização de lambdas em elementos combináveis. Com a rejeição forte ativada, cada lambda dentro de uma função combinável será lembrada automaticamente.

Exemplos

Para alcançar a memorização de lambdas dentro de elementos que podem ser compostos ao usar a omissão forte, o compilador envolve a lambda com uma chamada remember. Ela é codificada com as capturas da lambda.

Considere um caso em que você tem uma lambda como no exemplo a seguir:

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

Com a rejeição avançada ativada, o compilador memoriza a lambda ao envolvê-la em uma chamada remember:

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

As chaves seguem as mesmas regras de comparação das funções combináveis. O ambiente de execução compara chaves instáveis usando a igualdade de instâncias. Ele compara chaves estáveis usando a igualdade de objetos.

Memoização e recomposição

Essa otimização aumenta muito o número de elementos que o ambiente de execução pula durante a recomposição. Sem a memorização, é muito mais provável que o ambiente de execução aloque uma nova lambda para qualquer elemento que use um parâmetro lambda durante a recomposição. Como resultado, a nova lambda tem parâmetros que não são iguais aos da última composição. Isso resulta em recomposição.

Evitar a memorização

Se você tiver uma lambda que não quer memorizar, use a anotação @DontMemoize.

val lambda = @DontMemoize {
    ...
}

Tamanho do APK

Quando compilados, os elementos combináveis puláveis resultam em mais código gerado do que os que não são puláveis. Com a pulagem forte ativada, o compilador marca quase todos os elementos combináveis como puláveis e envolve todos os lambdas em um remember{...}. Por isso, ativar o modo de rejeição avançada tem um impacto muito pequeno no tamanho do APK do seu aplicativo.

Ativar a pulagem forte no Now In Android aumentou o tamanho do APK em 4 kB. A diferença de tamanho depende muito do número de elementos combináveis que não podiam ser pulados e que estavam presentes no app, mas que devem ser relativamente menores.