O sistema de build da plataforma Android (Soong) executa o compilador R8 em
destinos que compilam bytecode DEX, incluindo apps Android (android_app), testes
(android_test) e bibliotecas Java instaláveis com compile_dex: true (como
services.jar), para reduzir, otimizar e remover códigos e recursos não utilizados. Em
bibliotecas estáticas (android_library e java_library estático), o Soong não
executa o R8 diretamente, mas usa o bloco de propriedades optimize para anexar e propagar
regras de manutenção do consumidor (export_proguard_flags_files: true) para destinos DEX
downstream que as vinculam estaticamente.
Para engenheiros de plataforma que criam imagens do sistema ou pacotes de fornecedores, eliminar regras de manutenção excessivamente amplas oferece benefícios diretos à integridade do sistema:
- Menor consumo de espaço da partição do sistema:o R8 remove classes, métodos
e recursos inativos antes de empacotar APKs e JARs em
/system,/system_ext,/producte/vendor. - Artefatos compilados menores:menos métodos DEX significam arquivos
.odexe.vdexmenores gerados pelodex2oatdurante o tempo de build ou a compilação no dispositivo. - Menor pressão de memória no ambiente de execução:como o Android mapeia
.odex,.vdexe tabelas de recursos de APK na memória do processo usando chamadasmmapde paginação por demanda, binários menores reduzem as falhas de página principais durante a inicialização do app e diminuem o código residente e a memória de recursos em todos os processos. Para mais informações sobre como o código do app compilado afeta a memória do dispositivo, consulte O código do app é memória. - Otimização mais eficaz de todo o programa:as restrições de manutenção estreitas permitem que o R8 use métodos inline, desvirtualize chamadas virtuais e de interface, remova campos não utilizados e propague constantes entre classes.
Configurar a otimização do R8 no Soong
Nos arquivos Android.bp, configure a otimização do R8 usando o bloco de propriedades optimize
em destinos android_app e java_library instaláveis (ou em
módulos android_library e java_library estáticos para exportar regras de manutenção
do consumidor):
android_app {
name: "MySystemApp",
srcs: ["src/**/*.java"],
optimize: {
obfuscate: true,
shrink_resources: true,
},
}
A tabela a seguir resume as propriedades optimize mais comuns no Soong
(definidas em build/soong/java/dex.go):
| Propriedade | Descrição |
|---|---|
enabled |
Controla se o R8 é executado no destino. O padrão é true
para todos os destinos android_app.
|
shrink |
Controla a remoção de classes, campos e métodos inacessíveis. O padrão é true para destinos android_app (false para java_library independente e
módulos de teste, que transmitem -dontshrink, a menos que sejam definidos
explicitamente).
|
optimize |
Controla as otimizações de bytecode, como inlining de métodos, mesclagem de classes, propagação de constantes e remoção de ramificações mortas. O padrão é
true para destinos android_app (controlados pela
flag de build de lançamento RELEASE_R8_OPTIMIZE_BY_DEFAULT).
|
obfuscate |
Controla a minificação e a renomeação de identificadores. O padrão é
false para compatibilidade histórica, mas defina como
true sempre que possível para reduzir o tamanho do DEX e desbloquear
otimizações mais profundas (consulte
Ativar a ofuscação sempre que possível).
|
shrink_resources |
Remove recursos não utilizados (entradas res/) do APK
empacotado após a redução de código. O valor padrão é false.
|
optimized_shrink_resources |
Executa o pipeline integrado de redução de código e recursos do R8 para que ele rastreie
as referências de código e recursos de uma só vez. Quando
shrink_resources: true é definido, o padrão é
RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT
(true em builds de plataforma padrão).
|
proguard_flags_files |
Lista arquivos .flags ou .pro específicos do módulo
que contêm regras de retenção personalizadas. Evite adicionar arquivos personalizados quando as anotações
padrão ou os padrões forem suficientes.
|
export_proguard_flags_files |
Propaga o proguard_flags_files dessa biblioteca para
módulos downstream que dependem dela de forma estática.
|
trace_references_from |
Lista destinos dependentes de compilação DEX direta (conectados por
instrumentation_for ou libs) cujas referências de bytecode
nesse destino são rastreadas e retidas automaticamente pelo R8 (também
propagadas em variantes override_android_app e
override_android_test e compatíveis com
bibliotecas Java complementares não dependentes quando pareadas com uma
divisão .impl).
|
allow_traced_references |
Permite que destinos dependentes de compilação DEX direta (conectados por
instrumentation_for ou libs, como
testes de fornecedores ou entre repositórios que não podem ser listados em
trace_references_from) ativem o rastreamento das referências de bytecode
nesse destino definindo
trace_references: true.
|
trace_references |
Quando definido como true em um dependente de compilação DEX direta de um
destino com allow_traced_references: true, executa o R8
tracereferences nesse módulo e transmite as regras de manutenção
geradas para o módulo de destino.
|
protect_api_surface |
Transmite --protect-api-surface para o R8. Quando definido em um
java_sdk_library, também gera e
anexa automaticamente regras de manutenção da API Metalava em todos os escopos de API ativados (público,
sistema, module-lib, system-server e teste) à biblioteca de
implementação.
|
As bibliotecas estáticas (android_library, java_library e importações de AAR) também podem
definir required_downstream_optimization: { enabled: true, optimize: true } em
Android.bp para exigir que qualquer destino de compilação DEX downstream que as vincule
estaticamente (direta ou transitivamente) ative a redução do R8 e a otimização
de bytecode. O Soong ativa isso automaticamente no
androidx.compose.runtime_runtime para que as metas que vinculam o Jetpack Compose sejam
otimizadas com o R8.
Ativar a ofuscação sempre que possível
No Soong, obfuscate usa false como padrão para compatibilidade histórica, mas é
recomendável definir explicitamente obfuscate: true em android_app e destinos DEX
independentes sempre que possível:
- Menor consumo de DEX e
.vdex:renomear pacotes, classes, campos e métodos para identificadores curtos (a,b) reduz o pool de strings DEX (string_idsestring_data_item) e os descritores de tipo. Como o Android mapeia arquivos.vdexe.odexna memória do processo, tabelas de símbolos menores reduzem diretamente o tamanho da partição do sistema e o consumo de memória do ambiente de execução. - Otimizações mais profundas de todo o programa:permitir que o R8 renomeie identificadores desbloqueia a mesclagem de classes, o nivelamento de pacotes e a eliminação de duplicação de classes sintéticas em pacotes que o R8 precisaria pular para evitar colisões de nomes.
- Simbolização de stack trace completo:ativar a ofuscação no Soong não
reduz a capacidade de depuração. Para cada destino compilado pelo R8, o Soong emite um
arquivo de mapeamento
proguard_dictionaryno diretório intermediário do módulo, agrupa todos os dicionários de módulo no artefatoproguard-dict.zipdo build, incorpora um hash de mapeamento (--map-id-template) ao cabeçalho DEX e reescreve os atributosSourceFileda classe (--source-file-template) para que oretracepossa simbolizar stack traces automaticamente.
Mantenha obfuscate: false (ou preserve explicitamente os nomes de APIs públicas) apenas para:
- Bibliotecas compartilhadas no bootclasspath ou no classpath
system_server:os módulos (java_libraryoujava_sdk_library) que expõem uma superfície de API externa vinculada dinamicamente por outros módulos no momento da execução (como destinosframework.jar,services.jarou<uses-library>) precisam manterobfuscate: falseou preservar explicitamente a superfície da API usando regrasprotect_api_surface: true,@KeepForApiou-keeppara que os autores de chamadas compilados em relação aos stubs possam resolver nomes de classes e membros no momento da execução. Nos módulosjava_sdk_library, a configuraçãoprotect_api_surface: truegera e anexa automaticamente as regras de manutenção do Metalava ProGuard em todos os escopos de API ativados (público, sistema, module-lib, system-server e teste) à biblioteca de implementação. Quando a otimização generalizada do R8 é ativada em destinos de bootclasspath não atualizáveis (framework-minus-apex,exteframework-location), eles também incluemframework-hiddenapi-proguard-rules(gerado porhiddenapi_to_proguard) para reter os membros listados em arquivosboot/hiddenapi/*.txtativos (omitindohiddenapi-max-target-o.txt).
Antes de ativar o obfuscate: true em um aplicativo, verifique os seguintes
pré-requisitos de migração:
- Dependências externas do APK de teste:se um APK externo
android_testdefinirinstrumentation_for: "MyApp"e invocar diretamente classes ou métodos internos deMyApp, a renomeação desses símbolos vai causarNoSuchMethodErrorouNoClassDefFoundErrorno momento da execução do teste. Configuretrace_references_from(ouallow_traced_referencescomtrace_references: true), estruture o teste como um APK de autoinstrumentação que vinculaMyApp.implde forma estática ou anote os hooks de teste com@VisibleForTesting(consulte a Etapa 2: migrar regras de manutenção orientadas por testes) para que os símbolos internos necessários para os testes sejam preservados enquanto o restante do app é ofuscado. - Reflexão baseada em string e JNI:se um módulo procurar classes, métodos
ou campos por nomes de string literais (
Class.forName,getDeclaredMethodou JNIFindClasseGetMethodID) sem regras ou anotações de manutenção, a ofuscação vai renomear esses destinos e interromper as pesquisas de ambiente de execução. Observe que a reflexão não anotada também falha emshrink: trueeoptimize: true. Anote esses pontos de entrada com o Guia para manter anotações (@UsesReflection,@UsedByReflectionou@UsedByNative) para que o R8 preserve os nomes e ofusque o restante do módulo.
Siga o princípio de zero flags personalizadas
O módulo ideal da plataforma Android não tem arquivos proguard.flags ou keep.xml
personalizados. Na build da plataforma Android, a grande maioria das regras de manutenção personalizadas é
redundante:
- Linhas de base da plataforma e AAPT2:a maioria dos pontos de entrada já é retida
automaticamente por linhas de base globais da plataforma (como
@Keep, métodos JNInativee@VisibleForTesting) ou gerada pelo AAPT2 deAndroidManifest.xmle recursos de layout. Consulte Noções básicas sobre as regras de manutenção padrão da plataforma. - Alternativas direcionadas:quando os pontos de entrada precisam ser preservados, prefira
anotações de site de declaração (
keepanno,@VisibleForTesting), regras de biblioteca exportadas (export_proguard_flags_files: true) ou vinculação de teste estático em vez de arquivos.flagsseparados (consulte Auditar e migrar regras de manutenção atuais).
Antes de adicionar ou manter um arquivo proguard.flags ou keep.xml personalizado, verifique
se a regra já é processada por linhas de base padrão ou se pode ser migrada para
anotações de código.
Entender as regras de retenção padrão da plataforma
O Soong transmite automaticamente as seguintes regras de referência globais para cada invocação
do R8 em destinos de compilação DEX (módulos android_app, android_test e
java_library instaláveis), configurados em build/soong/java/dex.go:
build/make/core/proguard.flags:preserva classes e membros anotados com@com.android.internal.annotations.VisibleForTestingem todos os pacotes e com@VisibleForTesting(androidx.annotation.VisibleForTestingoucom.google.common.annotations.VisibleForTesting) nos pacotesandroid.**,com.android.**ecom.google.android.**. Também preserva@TestApi,@Keep(androidx.annotation,android.support.annotation,com.android.internal.annotations),@KeepForWeakReference,@WeaklyReferencedCallbacke@dalvik.annotation.optimization.**.build/make/core/proguard_basic_keeps.flags:preserva os atributosSourceFilepara stack traces, anotações de visibilidade do ambiente de execução (RuntimeVisible*Annotations), atributosExceptionseAnnotationDefault, métodosnative, membrosSerializable, métodos@JavascriptInterface, construtoresThrowable(String), camposParcelable$CREATORe camposMessageLitedo protobuf.build/make/core/proguard/kotlin.flags:silencia avisos inofensivos para metanotações específicas do Kotlin (kotlin.Metadataekotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) e remove as anotaçõesDebugMetadatado Kotlin em builds de lançamento.build/make/core/proguard/checknotnull.flags:substitui chamadas auxiliares comuns de verificação de nulo (com.google.common.base.Preconditions.checkNotNulledagger.internal.Preconditions.checkNotNull*) por verificações de nulo de bytecode concisas. Este arquivo omite intencionalmenteObjects.requireNonNullpara preservar mensagens de exceção explícitas nos limites da API do framework.build/make/core/proguard/enumvalues.flags:retém os métodosvaluesevalueOfem tiposenum, a menos que sejam desativados pela configuração do build.- Regras geradas automaticamente pelo AAPT2:o AAPT2 inspeciona arquivos
AndroidManifest.xml, XML de layout e XML de preferência mesclados para gerar regras de manutenção exatas para cadaActivity,Service,BroadcastReceiver,ContentProvider,BackupAgent,Application, subclasseViewpersonalizada, subclassePreferencee método XMLandroid:onClickregistrados.
Auditar e migrar regras de retenção atuais
Ao auditar arquivos proguard.flags ou keep.xml em um repositório de plataforma, avalie cada regra em ordem em relação à seguinte hierarquia de quatro etapas:
Etapa 1: excluir regras redundantes ou obsoletas
Exclua as regras que já estão cobertas por referências globais, regras de layout e manifesto do AAPT2 ou padrões do R8, bem como regras que fazem referência a classes ou pacotes que não existem mais.
Se todas as regras em um arquivo .flags forem redundantes, exclua o arquivo e remova
proguard_flags_files de Android.bp. Se o bloco optimize restante apenas
reafirmar as configurações padrão, remova o bloco redundante e formate o arquivo de build
com bpfmt -w Android.bp. Ao limpar um pacote, verifique se os módulos complementares
em subdiretórios (como variantes de destino do Kotlin) fazem referência ao mesmo
arquivo de flags e atualize-os juntos.
Etapa 2: migrar regras de manutenção baseadas em testes
Mova as regras de manutenção orientadas por testes para fora dos arquivos .flags de produção personalizados usando um dos
seguintes padrões:
Rastrear referências de destinos de teste ou complementares (
trace_references_fromouallow_traced_references): quando o destino e um módulo dependente direto compilam o próprio DEX (android_app,android_testou umjava_librarydexado) e estão conectados porinstrumentation_foroulibs, o Soong pode executar o R8tracereferencesno módulo dependente e alimentar as regras de manutenção geradas na compilação do R8 do destino sem criar um ciclo de dependência ou exigir uma divisão manual da biblioteca.impl:Testes no mesmo repositório (
trace_references_from): liste os dependentes diretosinstrumentation_foroulibsno módulo de destino. Isso também se propaga automaticamente pelas variantesoverride_android_appeoverride_android_teste continua oferecendo suporte a bibliotecas Java complementares não dependentes quando pareadas com uma divisão.impl:android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { obfuscate: true, shrink_resources: true, trace_references_from: ["MyAppTests"], }, } android_test { name: "MyAppTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], }Testes entre repositórios ou de fornecedores (
allow_traced_referencesetrace_references): quando o módulo de destino não pode listar o dependente diretamente emtrace_references_from(como testes em outro repositório ou árvore de fornecedores), definaallow_traced_references: trueno módulo de destino e ative cada dependente comtrace_references: true:android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { allow_traced_references: true, }, } android_test { name: "MyAppVendorTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], optimize: { trace_references: true, }, }Reflexão baseada em string em testes:o
tracereferencesinspeciona referências de pool de constantes de bytecode e não pode detectar reflexão baseada em string em testes (Class.forName,getDeclaredField,getDeclaredMethodou auxiliares de injeção de campo reflexiva). Adicione anotações aos campos e métodos de produção acessados de forma reflexiva com@VisibleForTesting(ou@UsedByReflection).
Vincule a biblioteca de implementação aos testes (
static_libs): se você quiser que nenhum ponto de entrada somente para teste seja retido no APK de produção e não quiser que as mudanças de teste acionem novamente o R8 no destino de produção, coloque os arquivos de origem do app em umandroid_library(MyApp.impl) e vincule estaticamenteMyApp.implaMyAppeMyAppTests:android_library { name: "MyApp.impl", srcs: ["src/**/*.java"], manifest: "AndroidManifest.xml", } android_app { name: "MyApp", static_libs: ["MyApp.impl"], optimize: { obfuscate: true, shrink_resources: true, }, } android_test { name: "MyAppTests", srcs: ["tests/**/*.java"], static_libs: ["MyApp.impl"], }Esse padrão de três módulos permite que
MyAppTestsseja executado como um teste de autoinstrumentação com acesso total a classes internas, enquanto permite queMyAppseja reduzido e ofuscado apenas em relação aos pontos de entrada de produção.Anote os hooks de teste com
@VisibleForTesting:se um APK de teste externo chamar apenas alguns métodos ou construtores internos em um destinoandroid_app, você poderá anotar essas declarações com@VisibleForTesting. A referência globalbuild/make/core/proguard.flagsretém automaticamente os itens@VisibleForTestingnos pacotesandroid.**,com.android.**ecom.google.android.**sem arquivos.flagspersonalizados. Para módulos de fornecedor fora desses namespaces, usetrace_references_from,@UsedByReflectionou regras de biblioteca exportadas. Ao fazer anotações manuais com@VisibleForTestingem vez de usartrace_references_fromoutrace_references, também anote as classes transmitidas para o Mockitomock(...)ouspy(...)(não apenas os métodos de stub), porque omockito-target(dexmaker-mockito) no dispositivo gera subclasses de execução, e o R8 pode marcar classes sem subclasses comofinal(MockitoException: Can't mock/spy class ... - final class).Evite ou reempacote bibliotecas estáticas sobrepostas (
jarjar_rules): quando umandroid_appe o APK de testeinstrumentation_forvinculam estaticamente a mesma biblioteca (diretamente, comoguava, ou transitivamente), otracereferencesvê as classes definidas no teste e não emite regras de manutenção para o app. No momento da execução, o carregador de classes pai do app oculta a cópia do teste com as classes reduzidas pelo R8, causandoIllegalAccessErrorouNoSuchMethodError. Se o teste não precisar da própria cópia empacotada, remova a duplicata dostatic_libsdo teste (ou vincule-a usandolibs) para evitar a colisão. Caso contrário, reempacote os pacotes de biblioteca estática sobrepostos do app em um prefixo específico do módulo usandojarjar_rules.
Etapa 3: exportar regras da biblioteca proprietária
Se um java_library ou android_library compartilhado exigir regras de manutenção para a própria
reflexão interna ou callbacks JNI, defina as regras no destino da biblioteca e
defina export_proguard_flags_files: true:
java_library {
name: "my-shared-library",
srcs: ["src/**/*.java"],
optimize: {
proguard_flags_files: ["proguard.flags"],
export_proguard_flags_files: true,
},
}
Todos os destinos de biblioteca e android_app downstream que vinculam estaticamente
my-shared-library herdam essas regras automaticamente. Assim, os apps downstream não
precisam duplicá-las. Quando vários módulos em diretórios diferentes precisam
compartilhar um conjunto de regras que não está vinculado a uma única biblioteca de código, publique o arquivo .flags
usando um filegroup explícito em Android.bp para que os módulos possam referenciar
:my-shared-flags de forma limpa nos limites do pacote. Para orientações gerais sobre
como criar regras de manutenção para consumidores de bibliotecas sem restringir a otimização de apps
downstream, consulte Otimização para autores de bibliotecas.
Etapa 4: anotar declarações no código-fonte
Quando uma classe, um método, um construtor ou um campo é acessado por reflexão ou
JNI e não é coberto pelo AAPT2 ou por referências globais, substitua as regras -keep
separadas em arquivos .flags por anotações de origem do
Guia para manter anotações (consulte a
referência do Javadoc keepanno):
@UsesReflection:use essa anotação quando você controlar o código que executa a reflexão. Coloque-a no site de chamada de reflexão para declarar quais classes, métodos ou campos de destino são acessados dinamicamente. Como@UsesReflectioncodifica automaticamente uma condição prévia de que o próprio site de chamada anotado é acessível, o R8 remove o autor da chamada e o destino reflexivo se o site de chamada não for usado.@UsedByReflection:coloque em classes, métodos, campos ou construtores que são instanciados ou invocados de forma reflexiva por códigos ou bibliotecas externos, como classes carregadas comClass.forNamede chavesBundleouSettings, ou classes de plug-in carregadas em carregadores de classe dinâmicos. Especifiquekind(comoKeepItemKind.CLASS_AND_METHODS),preconditions(@KeepCondition) e restrições de parâmetro para que o R8 mantenha apenas o contrato reflexivo exato e ainda possa otimizar ou remover membros não utilizados da classe. Não adicione@UsedByReflectiona componentes registrados no manifesto, comoJobServiceouBroadcastReceiver, porque o AAPT2 já os mantém.@UsedByNative:coloque em métodos ou campos acessados pelo código JNI em C ou C++ usandoGetMethodID,GetStaticMethodIDouGetFieldID.@KeepForApi:coloque em classes ou membros da API de biblioteca que precisam permanecer intactos quando uma biblioteca é reduzida antes da distribuição.@Keep(androidx.annotation.Keep): use como substituto quandokeepannonão for aplicável. Aplicar@Keepa uma classe retém a classe e todos os membros dela incondicionalmente. Aplicar@Keepa um método ou campo funciona como um ponto de entrada incondicional que retém o membro e a classe que o contém, mesmo que a classe nunca seja instanciada, enquantokeepannoexpressa a acessibilidade condicional.
Para usar as anotações keepanno do R8 em um módulo do Soong:
Adicione
"keepanno-annotations"alibsemAndroid.bp:libs: [ "keepanno-annotations", ],Importe
com.android.tools.r8.keepanno.annotations.*e anote a declaração ou o site de chamada no código-fonte Java ou Kotlin:import com.android.tools.r8.keepanno.annotations.KeepItemKind; import com.android.tools.r8.keepanno.annotations.UsedByReflection; public final class CustomPluginController { @UsedByReflection( description = "Instantiated via Class.forName from plugin config", kind = KeepItemKind.CLASS_AND_METHODS) public CustomPluginController(Context context) { // ... } }O R8 traduz as anotações
keepannodiretamente para o modelo interno de regras keep e as remove da saída DEX final, adicionando zero sobrecarga de bytecode de ambiente de execução.
Evite armadilhas comuns
As seções a seguir descrevem as armadilhas frequentes de proguard.flags e keep.xml
no código da plataforma e como corrigi-las.
Regras de retenção de componentes amplas
# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
- Por que isso prejudica o desempenho:essa regra é completamente redundante com o AAPT2.
Como ele usa um caractere curinga sem verificar se o componente está
registrado no
AndroidManifest.xmlmesclado final, ele força o R8 a reter todas as subclassesActivity,ServiceouBroadcastReceiverencontradas em qualquer lugar no caminho de classe, incluindo componentes de biblioteca não utilizados e atividades de depuração desativadas. - Correção recomendada:exclua a regra. O AAPT2 inspeciona o manifesto integrado e
gera regras
-keepexatas para componentes registrados.
Regras de manutenção do gerenciador de cliques XML amplo
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- Por que isso prejudica o desempenho:reter todos os métodos
public void *(View)em todas as classes do módulo impede que o R8 remova ou alinhe qualquer método que aceite um único parâmetroView. - Correção recomendada:exclua a regra. O AAPT2 verifica os arquivos XML de layout e
gera regras de manutenção direcionadas para métodos referenciados por atributos
android:onClick.
Regras de manutenção de getter e setter de visualização ampla
# Don't do this:
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
public *** get*();
}
- Por que isso prejudica o desempenho:essa regra força o R8 a reter todos os getters e
setters em cada subclasse
Viewno app e em todas as bibliotecas vinculadas (incluindo as bibliotecas AndroidX e Material), bloqueando a remoção de métodos inativos e o inline no código da interface. - Correção recomendada:exclua a regra. O AAPT2 já preserva construtores
para classes
Viewpersonalizadas infladas de arquivos XML de layout. Se o código anima uma propriedade de visualização usando nomes de string reflexivos, comoObjectAnimator.ofFloat(view, "translationZ", ...), substitua o nome da string por uma referência de propriedade tipada (View.TRANSLATION_Zou uma implementação personalizada deFloatPropertyouIntProperty) para evitar totalmente a reflexão. Se o acesso à propriedade reflexiva não puder ser evitado, anote o getter ou setter específico com@UsedByReflection.
Desativação geral de otimização ou ofuscação
# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
- Por que isso prejudica o desempenho:colocar
-dontoptimizeou-dontshrinkem um arquivo.flagssubstitui silenciosamente as configurações deAndroid.bpdo módulo e desativa as passagens de otimização em todo o destino. Pior ainda, se uma biblioteca exportar um arquivo.flagsque contenha-dontoptimize, ela vai desativar a otimização do R8 para cadaandroid_appdownstream que vincular a biblioteca. - Correção recomendada:remova
-dontoptimize,-dontshrinke-dontobfuscatedos arquivos.flags. Controle o comportamento de otimização explicitamente emAndroid.bpusando o blocooptimize(shrink,optimize,obfuscate) no destino de folha.
Sinalizações de diagnóstico em regras confirmadas
# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
- Por que isso prejudica o build:as flags de diagnóstico inundam os registros de build ou tentam gravar arquivos de saída em caminhos locais durante builds do Soong em sandbox.
- Correção recomendada:exclua essas flags dos arquivos
.flagsconfirmados. O Soong grava automaticamente as saídas de mapeamento e uso do R8, comoproguard_dictionaryeproguard_usage.zip, no diretório intermediário do módulo emout/soong/.intermediates/.
Regras de manutenção de produção para código de teste
- Por que isso prejudica a performance:adicionar regras personalizadas do
-keepapenas para acesso de teste força esse código a permanecer não ofuscado e retido em builds de produção. Embora a configuração detrace_references_from(ouallow_traced_referencescomtrace_references: true) ou a anotação de métodos com@VisibleForTestingevite manter regras manuais do-keepe retenha apenas o que os testes referenciam, esses símbolos ainda são enviados no binário de produção. - Correção recomendada:elimine as regras de teste personalizadas
-keepativandotrace_references_from(ouallow_traced_referencesetrace_references: true) ou estruture o app em uma biblioteca.implvinculada a um APK de teste de autoinstrumentação usandostatic_libspara manter os pontos de entrada somente de teste completamente fora do APK de produção, conforme descrito em Etapa 2: migrar regras de manutenção orientadas por testes.
Curingas de recursos gerais em keep.xml
<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/*,@drawable/*,@string/*" />
- Por que isso prejudica o desempenho:os caracteres curinga gerais retêm todos os recursos no
tipo correspondente em todo o módulo e nas dependências dele, o que impede a redução de recursos (
shrink_resources: true) e infla o APK e a tabelaresources.arscmapeada. - Correção recomendada:
- Dê preferência a referências de recursos estáticos em vez de
keep.xml:evite usar o métodoResources.getIdentifierpara procurar um conjunto limitado de recursos dinamicamente, como strings de experimentos numeradas ou drawables temáticos. Em vez disso, use uma instruçãoswitchde tempo de compilação ou mapeie constantes estáticasR.id,R.stringouR.drawable. As referências estáticas permitem que o R8 e o AAPT2 rastreiem os recursos ativos exatos, eliminem a sobrecarga de pesquisa de strings no ambiente de execução e removam completamente a necessidade dekeep.xml. - Listar nomes de recursos específicos:se a pesquisa dinâmica for necessária, como
por um leitor de licenças de terceiros, liste os identificadores de recursos exatos em
tools:keep(por exemplo,tools:keep="@raw/third_party_licenses"). - Excluir arquivos
keep.xmlredundantes:se os recursos listados já forem referenciados estaticamente no código (R.raw.foo) ou no XML (@raw/foo), o redutor os reterá automaticamente, para que você possa excluirres/raw/keep.xml.
- Dê preferência a referências de recursos estáticos em vez de
Verificar e auditar mudanças nas regras
Sempre que você remover ou restringir regras de manutenção em proguard.flags ou keep.xml,
verifique se o módulo é criado sem problemas, se ele passa nos testes de unidade e de instrumentação
e se mantém todos os pontos de entrada necessários.
Criar e testar módulos
Crie o módulo de forma limpa para verificar se o R8 e o AAPT2 são concluídos sem avisos de referência ausentes:
m <MODULE_NAME>Execute testes de unidade e de instrumentação para o módulo usando
atest:atest <TEST_MODULE_NAME>Para apps do sistema, serviços privilegiados ou módulos dependentes de hardware, execute testes de instrumentação e de interface em dispositivos físicos de destino ou no laboratório de testes de dispositivos para exercitar caminhos de reflexão de ambiente de execução, vinculações de IPC e inflação de recursos.
Inspecionar diferenças de DEX e de recursos
Compare o APK ou JAR compilado antes e depois das mudanças de regra para confirmar que o R8 remove o código e os recursos inativos sem remover os pontos de entrada esperados:
Use
apkanalyzeroudexdumppara inspecionar classes, métodos e campos retidos nos arquivos APK ou DEX de saída:apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkAo modificar
keep.xmlou ativarshrink_resources, useaapt2 dump resourcespara verificar se o build remove recursos não utilizados e mantém os necessários:aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkAo executar
prebuilts/r8/r8.jardiretamente noclasses.jarpré-R8 de um módulo para verificar as mudanças de flag off-line sem um build de plataforma completo, transmita os JARs de caminho de classe--libdo módulo (android.jar,framework.jarelibscompartilhado) e as regrasproguard.optionsgeradas pelo AAPT2 deAndroidManifest.xmle XMLs de layout. Sem essas entradas, o R8 não pode resolver supertipos de framework (Activity,Service,Phone,Handler) e não retém métodos de hierarquia suficientes.
Analisar o raio de retenção e a subsunção de regras com o R8
O compilador R8 de código aberto inclui um analisador Keep Radius
(também exposto no Android Studio e no Gradle como o
R8 Configuration Analyzer) que mede o impacto exato
de cada regra de manutenção durante a compilação. O Soong integra esse analisador
diretamente ao build da plataforma Android (configurado em
build/soong/java/dex.go).
Para analisar as regras de manutenção de um módulo de plataforma ou de um build inteiro:
Execute o build com a variável de ambiente
R8_DUMP_KEEP_RADIUS=true:R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>Quando
R8_DUMP_KEEP_RADIUS=trueé definido, o Soong instrui o R8 a registrar as métricas de regras de manutenção em um arquivor8keepradius.pbintermediário para cada módulo compilado emout/soong/.intermediates/. Para cada regra de manutenção e anotaçãokeepanno, o R8 registra:- Raio de retenção imediata:as classes, os campos e os métodos exatos retidos pela regra, além das restrições específicas que ela aplica contra redução, otimização ou ofuscação.
- Subsunção de regras:quais outras regras ou anotações de retenção já retêm os mesmos itens. Se uma regra personalizada for completamente subsumida por uma regra do AAPT2 ou uma regra de referência global, ela poderá ser excluída com segurança.
- Regras globais e para todo o pacote:regras que usam caracteres curinga amplos para todo o pacote ou aplicam diretivas de configuração globais.
Converta a saída de
r8keepradius.pbem um relatório HTML interativo usandoKeepRadiusHtmlReportGenerator(incluído emprebuilts/r8/r8.jar):# Generate an HTML report for a single module INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME> java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ $INTERMEDIATES/android_common/r8keepradius.pb \ keep_radius_report.html # Scan all built modules under out/soong/.intermediates and generate # per-module HTML reports plus an aggregate keepradius.html summary java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ out/soong/.intermediates \ out/keep_radius_reportsQuando recebe um diretório, o
KeepRadiusHtmlReportGeneratorpercorre todos os arquivos*keepradius*.pb, gera um relatório HTML para cada módulo e criaout/keep_radius_reports/keepradius.htmlresumindo as contagens de itens ativos, as contagens de itens mantidos e as regras de manutenção de maior raio em todo o build. Para mais informações sobre como interpretar as pontuações de redução, otimização e ofuscação no relatório gerado, consulte Usar o analisador de configuração do R8.
O Soong também é compatível com outras variáveis de ambiente de diagnóstico e loop interno do R8
em build/soong/java/dex.go:
R8_DUMP_INPUT=true: gravar8inputs.zipno diretório intermediário do módulo, que contém todos os JARs de entrada, JARs de biblioteca e configurações mescladas do ProGuard para reprodução independente do R8.R8_DUMP_PERFETTO_TRACE=true: gravar8trace.ptraceno diretório intermediário do módulo para inspecionar as passagens de compilação do R8 no Perfetto.SOONG_SKIP_TRACE_REFERENCES=true: pula a execução de regrastracereferencesconfiguradas portrace_references_fromoutrace_referencespara acelerar as versões de loop interno local ao iterar no código de teste.
Validar regras do consumidor da biblioteca
Quando um java_library ou android_library exporta regras de manutenção para consumidores downstream (export_proguard_flags_files: true), essas regras não podem incluir flags globais que alterem ou desativem a otimização para o aplicativo para o consumidor.
O R8 oferece um analisador de regras de manutenção de código aberto
(ProcessKeepRules), exposto no build da plataforma Android
como a ferramenta de host process-keep-rules (definida em
prebuilts/r8/Android.bp):
# Build the R8 keep rules validator host binary
m process-keep-rules
# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>
A ferramenta process-keep-rules analisa cada arquivo de configuração e falha com diagnósticos de arquivo
e linha se encontrar diretivas não permitidas nas regras do consumidor
da biblioteca. As diretivas não permitidas incluem otimização global, redução
ou desativação de ofuscação (como -dontoptimize ou -dontshrink), sinalizações de reempacotamento
de pacotes e modificação de acesso, sinalizações de diagnóstico ou mapeamento e
-keepattributes no nível do app.
Auditar árvores de origem com pgaudit.py
Para auditar diretórios de origem não criados junto com os analisadores de tempo de build do R8, a
árvore da plataforma Android inclui o script pgaudit.py em
build/make/core/proguard/tools/. Essa ferramenta de análise estática verifica arquivos Android.bp,
proguard.flags e keep.xml em um repositório para sinalizar regras já
cobertas pelo AAPT2 ou por referências globais da plataforma, caracteres curinga amplos, substituições de -dontoptimize
e regras de manutenção somente para teste:
# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/
# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json
As equipes de plataforma e OEM podem combinar pgaudit.py para uma triagem rápida da árvore de origem,
process-keep-rules para validar as regras de biblioteca exportadas e
R8_DUMP_KEEP_RADIUS=true com KeepRadiusHtmlReportGenerator para medir o
raio exato de retenção de classe, campo e método de cada regra em um build.