Como autor de uma biblioteca, você precisa garantir que os desenvolvedores de apps possam incorporar sua biblioteca ao app 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 de apps 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 internos em um app grande e modularizado.
Se você é um desenvolvedor de apps e quer saber como otimizar seu app Android, consulte Ativar a otimização de apps. Para saber quais bibliotecas são adequadas para uso, consulte Escolher bibliotecas com sabedoria.
Entender os tipos de regras keep
Há dois tipos distintos de regras keep que podem ser usadas em bibliotecas:
- As regras keep do consumidor precisam especificar regras que mantenham o que a biblioteca reflete. Se uma biblioteca usa reflexão ou JNI para chamar o código ou o código definido por um app cliente, essas regras precisam descrever qual código precisa ser mantido. As bibliotecas precisam empacotar regras keep do consumidor, que usam o mesmo formato das regras keep do app. Essas regras são agrupadas em artefatos de biblioteca (AARs ou JARs) e consumidas automaticamente durante a otimização do app Android quando a biblioteca é usada. Essas regras são mantidas no
arquivo especificado com a
consumerProguardFilespropriedade no seubuild.gradle.kts(oubuild.gradle) arquivo. Para saber mais, consulte Recomendações adicionais. - As regras keep de build de biblioteca são aplicadas quando a biblioteca é criada. Elas só são necessárias se você decidir otimizar parcialmente a biblioteca no momento do build. Elas precisam impedir que a API pública da biblioteca seja removida. Caso contrário, a API pública não estará presente na distribuição da biblioteca, o que significa que os desenvolvedores de apps não poderão usar a biblioteca. Essas regras são mantidas no arquivo
especificado com a
proguardFilespropriedade no seubuild.gradle.kts(oubuild.gradle) arquivo. Para saber mais, consulte Otimizar o build da biblioteca AAR.
Requisitos e diretrizes de otimização
A configuração do R8 nas bibliotecas tem um impacto global no tamanho e na performance do binário final do app consumidor. Além das práticas recomendadas gerais de regras keep, os autores de bibliotecas precisam obedecer 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 inchaço do app, o desperdício de memória, a inicialização lenta e os erros ANR (o app não está respondendo). As bibliotecas precisam evitar violar os requisitos a seguir para não reduzir significativamente a qualidade do app e a experiência do usuário.
Não há regras keep amplas ou em todo o pacote:sua biblioteca não pode incluir regras keep amplas que mantenham a maior parte do código na biblioteca ou em outra biblioteca. As regras keep amplas podem resolver falhas no curto prazo, mas aumentam o tamanho do app de todos os apps que consomem sua biblioteca.
Não inclua regras keep em todo o pacote (como
-keep class com.mylibrary.** {*; }) para pacotes na biblioteca ou em outras bibliotecas referenciadas. Essas regras limitam a otimização desses pacotes em todos os apps que consomem sua biblioteca.Não há regras globais inadequadas: nunca use opções globais como
-dontobfuscateou-allowaccessmodification.Usar codegen em vez de reflexão sempre que possível:quando possível, use a geração de código (codegen) em vez de reflexão. O codegen e a reflexão são abordagens comuns para evitar código boilerplate ao programar, mas o codegen é mais compatível com um otimizador de apps como o R8.
Com o codegen, o código é analisado e modificado durante o processo de build. Como não há grandes modificações após o tempo de compilação, o otimizador sabe qual código é necessário e qual 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. É provável que ele remova o código usado dinamicamente por reflexão durante o ambiente de execução, o que causa falhas no app para os usuários.
Muitas bibliotecas modernas usam codegen em vez de reflexão. Consulte a KSP para conferir um ponto de entrada comum, usado pelo Room, Hilt e muitos outros.
Suporte ao modo completo do R8: sua biblioteca não deve falhar quando o modo completo do R8 estiver ativado. O modo completo do R8 é o modo recomendado para usar o R8 e é o padrão desde o AGP 8.0, que foi lançado 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.
Recomendações adicionais
Além dos requisitos de otimização, confira outras recomendações.
- Não use
-repackageclassesno arquivo de regras keep do consumidor da biblioteca. No entanto, para otimizar o build da biblioteca, você pode usar-repackageclassescom um nome de pacote interno, como<your.library.package>.internal, no arquivo de regras keep do build da biblioteca. Isso pode melhorar a eficiência da biblioteca em apps não otimizados. No entanto, 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 da biblioteca, mesmo que haja uma sobreposição com os atributos definidos em
proguard-android-optimize.txt. - Se você precisar dos atributos a seguir na distribuição da biblioteca, mantenha-os no arquivo de regras keep do build da biblioteca e não no arquivo de regras keep do consumidor da biblioteca:
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- Os autores de bibliotecas precisam manter o atributo
RuntimeVisibleAnnotationsnas regras keep do consumidor se as anotações forem usadas no ambiente de execução. - Os autores de bibliotecas não devem usar as seguintes opções globais nas regras keep do consumidor:
-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, ela só poderá ser usada em um dos seguintes:
- Tipos segmentados específicos (implementadores ou subclasses de interface específicas)
- Código que usa uma anotação de ambiente de execução específica
O uso da reflexão dessa maneira limita o custo do ambiente de execução e permite a criação de regras keep do consumidor segmentadas.
Essa forma específica e segmentada de reflexão é um padrão que pode ser visto no
framework Android (por exemplo, durante o carregamento de recursos do sistema)
e nas bibliotecas AndroidX (por exemplo, ao construir 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. Eles incluem o seguinte:
Entendimento incorreto das otimizações do R8: ao contrário do que se pensa, as otimizações do R8 não se limitam apenas à ofuscação, mas também incluem a 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 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 em um AAR (Android Archive) ou JAR (Java Archive). As otimizações durante o tempo de build da biblioteca são limitadas, e o app não deve desativar a otimização da biblioteca incluindo-a em uma regra keep. Para mais informações, consulte Visão geral da otimização do R8.
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 keep certa.
Validar a qualidade da regra keep do consumidor
Para validar se a biblioteca não impede muita otimização de código quando incorporada a um app, use um exemplo 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 Analisador de configuração do R8.
Se as regras do consumidor limitarem a otimização em grandes partes do código da biblioteca, isso indica que as regras do consumidor precisam ser refinadas. Reduza as regras do consumidor e teste todos os aspectos afetados da biblioteca em um app com o R8 ativado.
Configurar o pacote de regras
Para garantir que as regras keep do consumidor sejam aplicadas corretamente, você precisa empacotá-las adequadamente, dependendo do formato da biblioteca.
Bibliotecas AAR
Para adicionar regras do 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 a biblioteca Kotlin ou Java 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
. As regras serão agrupadas no local correto no JAR de saída.
Verifique se as regras do pacote JAR final estão corretas conferindo se as regras estão no diretório META-INF/proguard.
Otimizar o build da biblioteca AAR (avançado)
Geralmente, não é necessário otimizar um build de biblioteca diretamente, porque as otimizações possíveis no tempo de build da biblioteca são muito limitadas. Como desenvolvedor de biblioteca, você precisa considerar várias etapas de otimização e manter o comportamento, tanto no tempo de build da biblioteca quanto do app, antes de otimizar essa biblioteca.
Se você ainda quiser otimizar a biblioteca no tempo de build, isso será 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 usados no tempo de build, geralmente comgetDefaultProguardFile("proguard-android-optimize.txt"), para definir qual parte da biblioteca precisa ser mantida durante o tempo de build da biblioteca. No mínimo, essa é sua API pública.consumerProguardFiles, por outro lado, são empacotados na biblioteca para afetar as otimizações que acontecem mais tarde, durante o build de um app que consome sua biblioteca.
Por exemplo, se a biblioteca usar a reflexão para construir classes internas, talvez seja necessário definir as regras keep 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'.
Otimizar a performance da interface do Compose
Se a biblioteca incluir componentes da interface do Compose, otimize a redução binária com o R8 (conforme descrito anteriormente) e a eficiência da recomposição:
- Projete elementos combináveis estáveis: para permitir que o compilador pule com segurança seus elementos combináveis quando as entradas não mudarem, estruture seus modelos de dados e configurações de biblioteca para seguir as regras de estabilidade. Para mais informações, consulte Estabilidade no Compose.
- Analise as métricas do compilador: use as métricas do compilador do Compose para verificar se os pontos de entrada da biblioteca estão marcados como reinicializáveis e ignoráveis. Isso minimiza o overhead de recomposição para apps de consumo. Para mais informações, consulte Diagnosticar problemas de estabilidade.
Suporte a diferentes versões do R8 (avançado)
Você pode personalizar regras para segmentar versões específicas do R8. Isso permite que a biblioteca funcione de maneira ideal em projetos que usam versões mais recentes do R8, permitindo 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
para quais versões do R8 as regras são escritas. Cada subdiretório pode ter um ou mais arquivos contendo as regras do R8, com qualquer nome de arquivo e extensão.
Observe que as partes -from-<X> e -upto-<Y> são opcionais, a versão <Y>
é exclusiva e os intervalos de versão 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ório que representam um conjunto de regras segmentadas do R8. 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é a versão 8.2.0, 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 segmentadas do R8, 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).