Como autor de uma biblioteca, você precisa garantir que os desenvolvedores de apps possam incorporar sua biblioteca ao app deles com facilidade, mantendo uma experiência de alta qualidade para o usuário final. Isso significa que sua biblioteca precisa ser compatível com a otimização do Android (R8) sem exigir configuração adicional do desenvolvedor ou documentar que a biblioteca pode ser inadequada para uso no Android. É fundamental que as bibliotecas destinadas ao uso no Android não impeçam otimizações importantes do app e sigam outros requisitos de otimização.
Esta documentação é destinada a desenvolvedores de bibliotecas publicadas, mas também pode ser útil para desenvolvedores de módulos de biblioteca interna em um app grande e modularizado.
Se você é desenvolvedor de apps e quer aprender mais sobre como otimizar seu app Android, consulte Ativar a otimização de apps. Para aprender mais sobre quais bibliotecas são apropriadas, consulte Escolher bibliotecas com sabedoria.
Entender os tipos de regras de retenção
Há dois tipos distintos de regras de retenção que você pode ter em bibliotecas:
- As regras de retenção para consumidores precisam especificar regras que mantenham o que a biblioteca
reflete. Se uma biblioteca usa reflexão ou JNI para chamar o código dela ou
o código definido por um app cliente, essas regras precisam descrever qual código precisa
ser mantido. As bibliotecas precisam empacotar as regras de retenção do consumidor, que usam o mesmo
formato das regras de retenção do app. Essas regras são agrupadas em artefatos de biblioteca
(AARs ou JARs) e são consumidas automaticamente durante a otimização do app Android
quando a biblioteca é usada. Essas regras são mantidas no
arquivo especificado com a propriedade
consumerProguardFilesno arquivobuild.gradle.kts(oubuild.gradle). Para saber mais, consulte Escrever regras de manutenção do consumidor. - As regras de manutenção de build da biblioteca são aplicadas quando a biblioteca é criada. Eles
são necessários apenas se você decidir otimizar parcialmente sua biblioteca no momento
da compilação. Eles precisam impedir que a API pública da biblioteca seja removida. Caso contrário, ela não vai estar presente na distribuição da biblioteca, o que significa que os desenvolvedores
de apps não poderão usá-la. Essas regras são mantidas no arquivo
especificado com a propriedade
proguardFilesno arquivobuild.gradle.kts(oubuild.gradle). Para saber mais, consulte Otimizar o build da biblioteca AAR.
Requisitos e diretrizes de otimização
A configuração do R8 em bibliotecas tem um impacto global no tamanho e no desempenho do binário final do aplicativo para o consumidor. Além das práticas recomendadas gerais para regras de manutenção, os autores de bibliotecas precisam aderir a requisitos específicos e considerar outras diretrizes.
Obedecer aos requisitos de otimização
A ineficiência nas bibliotecas é um dos principais fatores que contribuem para o aumento do tamanho do app, o desperdício de memória, as inicializações lentas e os erros de ANR (O app não está respondendo). As bibliotecas não podem violar os requisitos a seguir para evitar uma redução significativa na qualidade do app e na experiência do usuário.
Não use regras de retenção amplas ou em todo o pacote: sua biblioteca não pode incluir regras de retenção amplas que mantenham a maior parte do código na sua biblioteca ou em outra biblioteca. As regras de manutenção amplas podem resolver falhas no curto prazo, mas aumentam o tamanho de todos os apps que consomem sua biblioteca.
Não inclua regras de retenção em todo o pacote (como
-keep class com.mylibrary.** {*; }) para pacotes na sua biblioteca ou em outras bibliotecas referenciadas. Essas regras limitam a otimização desses pacotes em todos os apps que consomem sua biblioteca.Não inclua regras de retenção amplas para dependências de terceiros que sua biblioteca consome (por exemplo,
-keep public class com.google.android.gms.** { *; }). Muitas bibliotecas externas já incluem as próprias regras de retenção do consumidor. A introdução de uma regra em todo o pacote substitui essas regras e força o R8 a reter o código de dependência não referenciado em todo o aplicativo.Não use regras globais inadequadas: nunca use opções globais, como
-dontobfuscateou-allowaccessmodification.Uso de codegen em vez de reflexão sempre que possível: quando possível, use a geração de código (codegen) em vez da reflexão. A geração de código e a reflexão são abordagens comuns para evitar código boilerplate ao programar, mas a geração de código é mais compatível com um otimizador de apps como o R8.
Com a geração de código, o código é analisado e modificado durante o processo de build. Como não há modificações importantes após o tempo de compilação, o otimizador sabe qual código é necessário e o que pode ser removido com segurança.
Com a reflexão, o código é analisado e manipulado no ambiente de execução. Como o código não é realmente finalizado até ser executado, o otimizador não sabe qual código pode ser removido com segurança. Ele provavelmente vai remover o código usado dinamicamente por reflexão durante o tempo de execução, o que causa falhas no app para os usuários.
Muitas bibliotecas modernas usam codegen em vez de reflexão. Consulte KSP para um ponto de entrada comum, usado por Room, Hilt e muitos outros.
Suporte ao modo completo do R8: sua biblioteca não pode falhar quando o modo completo do R8 está ativado. O modo completo do R8 é o recomendado para usar o R8 e é o padrão desde o AGP 8.0, que se tornou estável em 2023. Se a biblioteca falhar no R8, a solução é identificar o ponto de entrada específico de reflexão ou JNI e adicionar uma regra segmentada, não manter o pacote inteiro. Para gerar regras segmentadas com base em anotações nesses pontos de entrada, use o Jetpack Keep.
Recomendações adicionais
Além dos requisitos de otimização, veja a seguir outras recomendações.
Não use
-repackageclassesno arquivo de regras de retenção do consumidor da biblioteca. As regras de manutenção do consumidor são mescladas na configuração do R8 de cada app que usa sua biblioteca. Portanto, essa opção global se aplica a todo o app e substitui as configurações de reempacotamento do desenvolvedor de apps. Por padrão, a partir do AGP 9.0, a publicação de uma biblioteca vai falhar se as regras de manutenção do consumidor contiverem opções globais. Para mais informações, consulte as notas da versão do AGP 9.0.Para otimizar o build da biblioteca, use
-repackageclassescom um nome de pacote interno, como<your.library.package>.internal, no arquivo de regras de manutenção do build da biblioteca. Isso pode melhorar a eficiência da biblioteca em apps não otimizados, mas geralmente não é necessário, porque os apps também precisam ser otimizados.Declare todos os atributos necessários para que a biblioteca funcione nos arquivos de regras keep dela, mesmo que haja uma sobreposição com os atributos definidos em
proguard-android-optimize.txt.Se você precisar dos seguintes atributos na distribuição da biblioteca, mantenha-os no arquivo de regras de manutenção do build da biblioteca, e não no arquivo de regras de manutenção do consumidor da biblioteca:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
Os autores de bibliotecas precisam manter o atributo
RuntimeVisibleAnnotationsnas regras de retenção para consumidores se as anotações forem usadas no momento da execução.Os autores de bibliotecas não podem usar as seguintes opções globais nas regras de manutenção do consumidor. Assim como
-repackageclasses, essas opções configuram todo o build do R8 do app, não apenas o código da biblioteca. Portanto, deixe-as para o desenvolvedor de apps:-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
Quando a reflexão é aceitável
Se você precisar usar a reflexão, faça isso apenas em um dos seguintes:
- Tipos de segmentação específicos (implementadores de interface ou subclasses específicos)
- Código que usa uma anotação de ambiente de execução específica
O uso da reflexão dessa maneira limita o custo de execução e permite escrever regras de retenção para consumidores segmentados.
Essa forma específica e direcionada de reflexão é um padrão que você pode ver tanto
no framework do Android (por exemplo, ao inflar atividades, visualizações e
drawables) quanto nas bibliotecas do AndroidX (por exemplo, ao criar WorkManager
ListenableWorkers ou RoomDatabases). Por outro lado, a reflexão aberta
do Gson não é adequada para uso em apps Android.
Equívocos comuns
Alguns equívocos comuns podem levar você a configurar o R8 incorretamente. Isso inclui o seguinte:
Entendimento incorreto das otimizações do R8: ao contrário do que muitos pensam, as otimizações do R8 não se limitam à ofuscação, mas também incluem redução de código e otimizações lógicas com técnicas de inlining de métodos e mesclagem de classes. Para mais informações, consulte a visão geral da otimização do R8.
Ignorar a otimização de bibliotecas ofuscadas: um erro comum é omitir uma biblioteca da otimização porque ela foi otimizada ou ofuscada quando foi compilada para um AAR (Android Archive) ou JAR (Java Archive). As otimizações durante o tempo de build da biblioteca são limitadas, e seu app não deve desativar a otimização da biblioteca incluindo-a em uma regra de manutenção. Para mais informações, consulte Otimizar o build da biblioteca AAR.
Entendimento incorreto da opção
-keep: a regra-keepimpede que o R8 execute qualquer uma das etapas de otimização. Para mais informações, consulte Escolher a opção de retenção certa.
Validar a qualidade da regra de retenção do consumidor
Para validar se a biblioteca não impede muita otimização de código quando incorporada a um app, use uma amostra de biblioteca ou um app de teste de integração. Ative o R8 e inspecione a qualidade geral da configuração do R8 do app com o R8 Configuration Analyzer.
Se as regras do consumidor limitarem a otimização em grandes partes do código da biblioteca, isso indica que elas precisam ser refinadas. Restrinja as regras do consumidor e teste todos os aspectos afetados da biblioteca em um app com o R8 ativado.
Configurar o empacotamento de regras
Para garantir que as regras keep do consumidor sejam aplicadas corretamente, empacote-as de acordo com o formato da biblioteca.
Se você usar o Jetpack Keep para declarar o uso de reflexão e JNI da biblioteca, o plug-in do Gradle poderá empacotar as regras geradas como regras de retenção do consumidor para você.
Bibliotecas AAR
Para adicionar regras de consumidor a uma biblioteca AAR, use a opção consumerProguardFiles
no script de build do módulo da biblioteca Android. Para mais informações, consulte nossas
orientações sobre como criar módulos de biblioteca.
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
Groovy
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
Bibliotecas JAR
Para agrupar regras com sua biblioteca Kotlin ou Java que é enviada como um JAR, coloque o
arquivo de regras no diretório META-INF/proguard/ do JAR final, com qualquer nome de arquivo.
Por exemplo, se o código estiver em <libraryroot>/src/main/kotlin, coloque um arquivo de regras
do consumidor em
<libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro,
e as regras serão agrupadas no local correto no JAR de saída.
Verifique se o JAR final agrupa as regras corretamente, conferindo se elas estão
no diretório META-INF/proguard.
Otimizar o build da biblioteca AAR (avançado)
Em geral, não é necessário otimizar diretamente um build de biblioteca, porque as possíveis otimizações no momento do build são muito limitadas. Como desenvolvedor de bibliotecas, você precisa pensar em vários estágios de otimização e manter o comportamento, tanto no momento da criação da biblioteca quanto do app, antes de otimizar essa biblioteca.
Se você ainda quiser otimizar sua biblioteca no momento da compilação, isso é compatível com o Plug-in do Android para Gradle.
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
Groovy
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
O comportamento de proguardFiles é muito diferente de
consumerProguardFiles:
proguardFilessão usadas no momento do build, geralmente comgetDefaultProguardFile("proguard-android-optimize.txt"), para definir qual parte da biblioteca precisa ser mantida durante o build. No mínimo, essa é sua API pública.- Por outro lado, os
consumerProguardFilessão empacotados na biblioteca para afetar as otimizações que vão acontecer mais tarde, durante o build de um app que consome sua biblioteca.
Por exemplo, se a biblioteca usar reflexão para criar classes internas, talvez seja
necessário definir as regras de manutenção em proguardFiles e
consumerProguardFiles.
Se você usar -repackageclasses no build da biblioteca, reempacote as classes em um
subpacote dentro do pacote da biblioteca. Por exemplo, use -repackageclasses
'com.example.mylibrary.internal' em vez de -repackageclasses 'internal'.
Suporte a diferentes versões do R8 (avançado)
É possível personalizar regras para segmentar versões específicas do R8. Isso permite que sua biblioteca funcione de maneira ideal em projetos que usam versões mais recentes do R8, enquanto permite que as regras atuais continuem sendo usadas em projetos com versões mais antigas do R8.
Para especificar regras segmentadas do R8, inclua-as no
diretório META-INF/com.android.tools dentro de classes.jar de um AAR ou no
diretório META-INF/com.android.tools de um JAR.
In an AAR library:
proguard.txt (legacy location, the file name must be "proguard.txt")
classes.jar
└── META-INF
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
In a JAR library:
META-INF
├── proguard/<ProGuard-rule-files> (legacy location)
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
No diretório META-INF/com.android.tools, pode haver vários
subdiretórios com nomes no formato r8-from-<X>-upto-<Y> para indicar
as versões do R8 para as quais as regras foram escritas. Cada subdiretório pode ter um ou
mais arquivos com as regras do R8, com qualquer nome e extensão.
As partes -from-<X> e -upto-<Y> são opcionais, a versão <Y> é exclusiva e os intervalos de versões geralmente são contínuos, mas também podem se sobrepor.
Por exemplo, r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 e
r8-from-8.2.0 são nomes de diretórios que representam um conjunto de regras do R8 segmentadas. As
regras no diretório r8 podem ser usadas por qualquer versão do R8. As regras no
diretório r8-from-8.0.0-upto-8.2.0 podem ser usadas pelo R8 da versão
8.0.0 até, mas não incluindo, a versão 8.2.0.
O Plug-in do Android para Gradle usa essas informações para selecionar todas as regras que podem
ser usadas pela versão atual do R8. Se uma biblioteca não especificar regras de R8
direcionadas, o Plug-in do Android para Gradle vai selecionar as regras dos locais legados
(proguard.txt para um AAR ou META-INF/proguard/<ProGuard-rule-files> para um
JAR).