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 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 otimização de apps e oferece dicas gerais para ajudar você a escolher.
Dicas gerais ao escolher bibliotecas
Use estas dicas para garantir que suas bibliotecas sejam compatíveis com a otimização de apps.
Prefira a geração de código à reflexão
Escolha bibliotecas que usam geração de código (codegen) em vez de reflexão. Com a geração de código, o otimizador pode determinar mais facilmente qual código é realmente usado durante a 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 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 fizer o seguinte:
- Usa classes ou métodos dos pacotes
kotlin.reflectoujava.lang.reflect. - Usa as funções
Class.forNameouclassLoader.getClass. - Lê anotações no ambiente de execução, por exemplo, se armazena um valor de anotação
usando
val value = myClass.getAnnotation()ouval value = myMethod.getAnnotation()e depois faz algo comvalue. 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 rastreador de problemas e as discussões on-line para verificar se há problemas relacionados à minificação ou à configuração da otimização do app. Se houver, tente procurar alternativas para essa biblioteca. Vale lembrar o seguinte:
- As bibliotecas do AndroidX e bibliotecas como o Hilt funcionam bem com a otimização de apps porque usam principalmente codegen em vez de reflexão. Quando usam a reflexão, eles fornecem regras de manutenção mínimas para manter apenas o código necessário.
- As bibliotecas de serialização costumam usar a 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 usam codegen para evitar esses problemas, por exemplo, usando Kotlin Serialization ou Moshi com codegen.
- Se possível, evite bibliotecas que incluem 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 gerais precisam ser refinadas para manter apenas o código necessário. Para mais informações, consulte Adotar otimizações de forma incremental.
- As bibliotecas não devem exigir que você copie e cole regras de manutenção da documentação em um arquivo no seu projeto, principalmente regras de manutenção em 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 com o tempo.
Ativar a otimização depois de adicionar uma nova biblioteca
Ao adicionar uma nova biblioteca, ative a otimização depois 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 essa biblioteca.
Filtrar regras de retenção inválidas (avançado)
As regras de retenção são cumulativas. Isso significa que algumas regras incluídas em 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 as otimizações de código, essa regra desativará as otimizações do projeto inteiro.
Evite bibliotecas com regras de retenção que mantêm códigos que deveriam ser removidos. Mas, 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")
}
}
}
Estudo de caso: por que o Gson falha com otimizações
Gson é uma biblioteca de serialização que costuma causar problemas com a otimização de apps
porque usa muito a reflexão. O snippet de código a seguir mostra como o Gson é
normalmente usado, o que pode causar falhas com facilidade durante a execução. Observe que, quando você
usa o Gson para receber uma lista de objetos User, não chama o construtor nem passa uma
fábrica para a função fromJson(). Construir ou consumir classes definidas pelo app sem uma das seguintes opções é um sinal de que uma biblioteca pode estar usando reflexão aberta:
- Classe de app que implementa uma biblioteca, uma interface padrão ou uma classe
- 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. Quando o R8 analisa esse código e não vê o UserList ou o User instanciado em nenhum lugar, ele pode renomear campos ou remover construtores que não parecem ser usados, causando falhas no app. Se você estiver usando outras bibliotecas de maneira semelhante, verifique se elas não vão interferir na otimização do app e, se isso acontecer, evite usá-las.
Para definir as classes de maneira compatível com as regras de consumidor do Gson, use o snippet a seguir como referência:
class User(@com.google.gson.annotations.SerializedName("name") val name: String)
class UserList(@com.google.gson.annotations.SerializedName("users") val users: List<User>)
O Room, o Hilt e o Moshi com geração de código criam tipos definidos pelo app, mas usam a geração de código para evitar a necessidade de reflexão.