Escolha as bibliotecas com cuidado

Para ativar a otimização de apps, use bibliotecas compatíveis com a otimização do Android. Se uma biblioteca não estiver configurada para otimização do Android, por exemplo, se ela usar reflexão sem agrupar as regras de manutenção associadas, talvez ela não seja adequada para um app Android. Esta página explica por que algumas bibliotecas são mais adequadas para a otimização de apps e oferece dicas gerais para ajudar você a escolher.

Dicas gerais para escolher bibliotecas

Use estas dicas para garantir que suas bibliotecas sejam compatíveis com a otimização de apps.

Preferir codegen em vez de reflexão

Escolha bibliotecas que usem geração de código (codegen) em vez de reflexão. Com a geração de código, o otimizador pode determinar qual código é realmente usado no momento da execução e qual pode ser removido. Pode ser difícil saber se uma biblioteca usa codegen ou reflexão, mas há alguns sinais. Consulte as dicas para receber ajuda.

Para mais informações sobre codegen x reflexão, consulte Otimização para autores de bibliotecas.

Verificar o uso de reflexão (avançado)

Para saber se uma biblioteca usa reflexão, inspecione o código dela. Se a biblioteca usar reflexão, verifique se ela fornece regras de manutenção associadas. Uma biblioteca provavelmente usa reflexão se faz o seguinte:

  • Usa classes ou métodos dos pacotes kotlin.reflect ou java.lang.reflect.
  • Usa as funções Class.forName ou classLoader.getClass.
  • Lê anotações no ambiente de execução, por exemplo, se armazena um valor de anotação usando val value = myClass.getAnnotation() ou val value = myMethod.getAnnotation() e, em seguida, faz algo com value.
  • Chama métodos usando o nome do método como uma string, como no exemplo a seguir:

    // Calls the private `processData` API with reflection
    myObject.javaClass.getMethod("processData", DataType::class.java)
    ?.invoke(myObject, data)
    

Verificar problemas de otimização

Ao considerar uma nova biblioteca, consulte o Issue Tracker e as discussões on-line dela para verificar se há problemas relacionados à minificação ou à configuração da otimização do app. Se houver, procure alternativas para essa biblioteca. Lembre-se do seguinte:

  • As bibliotecas do AndroidX e outras, como o Hilt, funcionam bem com a otimização de apps porque usam principalmente a geração de código em vez da reflexão. Quando usam reflexão, elas fornecem regras de manutenção mínimas para manter apenas o código necessário.
  • As bibliotecas de serialização costumam usar reflexão para evitar código boilerplate ao instanciar ou serializar objetos. Em vez de abordagens baseadas em reflexão, como Gson para JSON, procure bibliotecas que usem codegen para evitar esses problemas, por exemplo, usando Kotlin Serialization ou Moshi com codegen.
  • Se possível, evite bibliotecas que incluam regras de retenção em todo o pacote. As regras de retenção em todo o pacote podem ajudar a resolver erros, mas as regras de retenção amplas precisam ser refinadas para manter apenas o código necessário. Para mais informações, consulte Adotar otimizações de forma incremental.
  • Antes de publicar um app que usa uma biblioteca de terceiros, use o Analisador de configuração do R8 para auditar as regras de manutenção fornecidas. Ao analisar o relatório, é possível verificar se as regras de manutenção da biblioteca são muito amplas, impedindo que o R8 realize otimizações críticas na base de código. Essa verificação garante que as bibliotecas selecionadas estejam alinhadas às metas de performance do app e não introduzam um excesso de configuração desnecessário.
  • As bibliotecas não devem exigir que você copie e cole regras de retenção da documentação em um arquivo do projeto, especialmente as que se aplicam a todo o pacote. Essas regras se tornam um fardo de manutenção para o desenvolvedor de apps a longo prazo e são difíceis de otimizar e mudar ao longo do tempo.

Ativar a otimização depois de adicionar uma nova biblioteca

Ao adicionar uma nova biblioteca, ative a otimização em seguida e verifique se há erros. Se houver erros, procure alternativas para essa biblioteca ou escreva regras de manutenção. Se uma biblioteca não for compatível com a otimização, registre um bug com ela.

Filtrar regras de manutenção ruins (avançado)

As regras do Keep são cumulativas. Isso significa que determinadas regras incluídas por uma dependência de biblioteca não podem ser removidas e podem afetar a compilação de outras partes do app. Por exemplo, se uma biblioteca incluir uma regra para desativar otimizações de código, essa regra vai desativar as otimizações de todo o projeto.

Evite bibliotecas com regras de retenção que mantêm códigos que deveriam ser removidos. No entanto, se você precisar usá-las, filtre as regras conforme mostrado no código a seguir:

// If you're using AGP 8.4 and higher
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreFrom("com.somelibrary:somelibrary")
        }
    }
}

// If you're using AGP 7.3-8.3
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreExternalDependencies("com.somelibrary:somelibrary")
        }
    }
}

Fluxo de trabalho recomendado para regras de biblioteca amplas

Se o R8 Configuration Analyzer revelar que uma biblioteca agrupa regras de manutenção excessivamente amplas, filtre essas regras apenas em builds locais, não em builds de produção. Para testar o quanto as regras afetam seu app, siga estas etapas:

  1. Atualize a biblioteca: verifique se você está usando a versão mais recente da biblioteca sinalizada pelo R8 Configuration Analyzer, porque as versões mais recentes podem incluir regras de manutenção mais restritas. Para encontrar a versão mais recente, consulte a documentação da biblioteca ou o repositório Maven dela. O Android Studio também sinaliza versões mais recentes de muitas dependências. Depois de atualizar, execute o analisador de configuração do R8 novamente para verificar se as regras de manutenção amplas permanecem.
  2. Verifique a possível melhoria localmente: copie as regras de consumidor agrupadas da biblioteca para o arquivo de regras de manutenção local, filtre as regras agrupadas, conforme mostrado em Filtrar regras de manutenção ruins, e restrinja as regras copiadas. Execute o analisador de configuração do R8 antes e depois de restringir as regras para comparar as pontuações de redução, otimização e ofuscação do app.
  3. Relate as descobertas: registre um relatório de bug com o mantenedor da biblioteca contendo os dados empíricos do relatório do analisador de configuração do R8 (por exemplo, demonstrando o número específico de classes ou métodos não referenciados preservados pela regra de caracteres curinga).

Estudo de caso: por que o Gson falha com otimizações

O Gson é uma biblioteca de serialização que geralmente causa problemas com a otimização de apps, porque usa muito a reflexão. O snippet de código abaixo mostra como o Gson é normalmente usado, o que pode causar falhas no momento da execução. Ao usar o Gson para receber uma lista de objetos User, você não chama o construtor nem transmite uma fábrica para a função fromJson(). A criação ou o consumo de classes definidas pelo app sem um dos itens a seguir é um sinal de que uma biblioteca pode estar usando reflexão aberta:

  • Classe de app que implementa uma biblioteca, uma interface ou uma classe padrão
  • Plug-in de geração de código, como o KSP.
class User(val name: String)
class UserList(val users: List<User>)

// This code runs in debug mode, but crashes when optimizations are enabled
Gson().fromJson("""[{"name":"myname"}]""", User::class.java).toString()

Para entender como o R8 funciona no Gson, consulte as regras do consumidor do Gson (link em inglês). Quando o R8 analisa esse código e não encontra UserList ou User instanciados em nenhum lugar, ele pode renomear campos ou remover construtores que não parecem ser usados, o que faz com que o app falhe. Se você estiver usando outras bibliotecas de maneira semelhante, verifique se elas não vão interferir na otimização do app. Se interferirem, evite-as.

Usar @SerializedName com versões mais recentes do Gson

Para definir seus modelos de dados de maneira compatível com as regras de consumidor do Gson, anote os campos com @SerializedName, conforme mostrado no snippet a seguir:

import com.google.gson.annotations.SerializedName

class User(@SerializedName("name") val name: String)
class UserList(@SerializedName("users") val users: List<User>)

Ao usar a anotação @SerializedName, você permite que o R8 corresponda às classes de modelo com as regras de manutenção incluídas no Gson versão 2.11.0 e mais recentes. O R8 preserva automaticamente os campos anotados e os construtores necessários, permitindo que você use as regras incluídas na biblioteca sem precisar manter configurações manuais do ProGuard no seu projeto.

Observe que Room, Hilt e Moshi com codegen constroem tipos definidos pelo app, mas usam codegen para evitar a necessidade de reflexão.