Otimizar o código e os recursos da plataforma com o R8

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, /product e /vendor.
  • Artefatos compilados menores:menos métodos DEX significam arquivos .odex e .vdex menores gerados pelo dex2oat durante 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, .vdex e tabelas de recursos de APK na memória do processo usando chamadas mmap de 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_ids e string_data_item) e os descritores de tipo. Como o Android mapeia arquivos .vdex e .odex na 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_dictionary no diretório intermediário do módulo, agrupa todos os dicionários de módulo no artefato proguard-dict.zip do build, incorpora um hash de mapeamento (--map-id-template) ao cabeçalho DEX e reescreve os atributos SourceFile da classe (--source-file-template) para que o retrace possa 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_library ou java_sdk_library) que expõem uma superfície de API externa vinculada dinamicamente por outros módulos no momento da execução (como destinos framework.jar, services.jar ou <uses-library>) precisam manter obfuscate: false ou preservar explicitamente a superfície da API usando regras protect_api_surface: true, @KeepForApi ou -keep para 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ódulos java_sdk_library, a configuração protect_api_surface: true gera 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, ext e framework-location), eles também incluem framework-hiddenapi-proguard-rules (gerado por hiddenapi_to_proguard) para reter os membros listados em arquivos boot/hiddenapi/*.txt ativos (omitindo hiddenapi-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_test definir instrumentation_for: "MyApp" e invocar diretamente classes ou métodos internos de MyApp, a renomeação desses símbolos vai causar NoSuchMethodError ou NoClassDefFoundError no momento da execução do teste. Configure trace_references_from (ou allow_traced_references com trace_references: true), estruture o teste como um APK de autoinstrumentação que vincula MyApp.impl de 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, getDeclaredMethod ou JNI FindClass e GetMethodID) 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 em shrink: true e optimize: true. Anote esses pontos de entrada com o Guia para manter anotações (@UsesReflection, @UsedByReflection ou @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 JNI native e @VisibleForTesting) ou gerada pelo AAPT2 de AndroidManifest.xml e 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 .flags separados (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.VisibleForTesting em todos os pacotes e com @VisibleForTesting (androidx.annotation.VisibleForTesting ou com.google.common.annotations.VisibleForTesting) nos pacotes android.**, com.android.** e com.google.android.**. Também preserva @TestApi, @Keep (androidx.annotation, android.support.annotation, com.android.internal.annotations), @KeepForWeakReference, @WeaklyReferencedCallback e @dalvik.annotation.optimization.**.
  • build/make/core/proguard_basic_keeps.flags:preserva os atributos SourceFile para stack traces, anotações de visibilidade do ambiente de execução (RuntimeVisible*Annotations), atributos Exceptions e AnnotationDefault, métodos native, membros Serializable, métodos @JavascriptInterface, construtores Throwable(String), campos Parcelable$CREATOR e campos MessageLite do protobuf.
  • build/make/core/proguard/kotlin.flags:silencia avisos inofensivos para metanotações específicas do Kotlin (kotlin.Metadata e kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) e remove as anotações DebugMetadata do 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.checkNotNull e dagger.internal.Preconditions.checkNotNull*) por verificações de nulo de bytecode concisas. Este arquivo omite intencionalmente Objects.requireNonNull para preservar mensagens de exceção explícitas nos limites da API do framework.
  • build/make/core/proguard/enumvalues.flags:retém os métodos values e valueOf em tipos enum, 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 cada Activity, Service, BroadcastReceiver, ContentProvider, BackupAgent, Application, subclasse View personalizada, subclasse Preference e método XML android:onClick registrados.

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_from ou allow_traced_references): quando o destino e um módulo dependente direto compilam o próprio DEX (android_app, android_test ou um java_library dexado) e estão conectados por instrumentation_for ou libs, o Soong pode executar o R8 tracereferences no 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 diretos instrumentation_for ou libs no módulo de destino. Isso também se propaga automaticamente pelas variantes override_android_app e override_android_test e 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_references e trace_references): quando o módulo de destino não pode listar o dependente diretamente em trace_references_from (como testes em outro repositório ou árvore de fornecedores), defina allow_traced_references: true no módulo de destino e ative cada dependente com trace_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 tracereferences inspeciona referências de pool de constantes de bytecode e não pode detectar reflexão baseada em string em testes (Class.forName, getDeclaredField, getDeclaredMethod ou 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 um android_library (MyApp.impl) e vincule estaticamente MyApp.impl a MyApp e MyAppTests:

    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 MyAppTests seja executado como um teste de autoinstrumentação com acesso total a classes internas, enquanto permite que MyApp seja 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 destino android_app, você poderá anotar essas declarações com @VisibleForTesting. A referência global build/make/core/proguard.flags retém automaticamente os itens @VisibleForTesting nos pacotes android.**, com.android.** e com.google.android.** sem arquivos .flags personalizados. Para módulos de fornecedor fora desses namespaces, use trace_references_from, @UsedByReflection ou regras de biblioteca exportadas. Ao fazer anotações manuais com @VisibleForTesting em vez de usar trace_references_from ou trace_references, também anote as classes transmitidas para o Mockito mock(...) ou spy(...) (não apenas os métodos de stub), porque o mockito-target (dexmaker-mockito) no dispositivo gera subclasses de execução, e o R8 pode marcar classes sem subclasses como final (MockitoException: Can't mock/spy class ... - final class).

  • Evite ou reempacote bibliotecas estáticas sobrepostas (jarjar_rules): quando um android_app e o APK de teste instrumentation_for vinculam estaticamente a mesma biblioteca (diretamente, como guava, ou transitivamente), o tracereferences vê 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, causando IllegalAccessError ou NoSuchMethodError. Se o teste não precisar da própria cópia empacotada, remova a duplicata do static_libs do teste (ou vincule-a usando libs) 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 usando jarjar_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 @UsesReflection codifica 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 com Class.forName de chaves Bundle ou Settings, ou classes de plug-in carregadas em carregadores de classe dinâmicos. Especifique kind (como KeepItemKind.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 @UsedByReflection a componentes registrados no manifesto, como JobService ou BroadcastReceiver, porque o AAPT2 já os mantém.
  • @UsedByNative:coloque em métodos ou campos acessados pelo código JNI em C ou C++ usando GetMethodID, GetStaticMethodID ou GetFieldID.
  • @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 quando keepanno não for aplicável. Aplicar @Keep a uma classe retém a classe e todos os membros dela incondicionalmente. Aplicar @Keep a 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, enquanto keepanno expressa a acessibilidade condicional.

Para usar as anotações keepanno do R8 em um módulo do Soong:

  1. Adicione "keepanno-annotations" a libs em Android.bp:

    libs: [
        "keepanno-annotations",
    ],
    
  2. 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 keepanno diretamente 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.xml mesclado final, ele força o R8 a reter todas as subclasses Activity, Service ou BroadcastReceiver encontradas 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 -keep exatas 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âmetro View.
  • 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 View no 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 View personalizadas infladas de arquivos XML de layout. Se o código anima uma propriedade de visualização usando nomes de string reflexivos, como ObjectAnimator.ofFloat(view, "translationZ", ...), substitua o nome da string por uma referência de propriedade tipada (View.TRANSLATION_Z ou uma implementação personalizada de FloatProperty ou IntProperty) 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 -dontoptimize ou -dontshrink em um arquivo .flags substitui silenciosamente as configurações de Android.bp do módulo e desativa as passagens de otimização em todo o destino. Pior ainda, se uma biblioteca exportar um arquivo .flags que contenha -dontoptimize, ela vai desativar a otimização do R8 para cada android_app downstream que vincular a biblioteca.
  • Correção recomendada:remova -dontoptimize, -dontshrink e -dontobfuscate dos arquivos .flags. Controle o comportamento de otimização explicitamente em Android.bp usando o bloco optimize (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 .flags confirmados. O Soong grava automaticamente as saídas de mapeamento e uso do R8, como proguard_dictionary e proguard_usage.zip, no diretório intermediário do módulo em out/soong/.intermediates/.

Regras de manutenção de produção para código de teste

  • Por que isso prejudica a performance:adicionar regras personalizadas do -keep apenas 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 de trace_references_from (ou allow_traced_references com trace_references: true) ou a anotação de métodos com @VisibleForTesting evite manter regras manuais do -keep e 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 -keep ativando trace_references_from (ou allow_traced_references e trace_references: true) ou estruture o app em uma biblioteca .impl vinculada a um APK de teste de autoinstrumentação usando static_libs para 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 tabela resources.arsc mapeada.
  • Correção recomendada:
    • Dê preferência a referências de recursos estáticos em vez de keep.xml:evite usar o método Resources.getIdentifier para procurar um conjunto limitado de recursos dinamicamente, como strings de experimentos numeradas ou drawables temáticos. Em vez disso, use uma instrução switch de tempo de compilação ou mapeie constantes estáticas R.id, R.string ou R.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 de keep.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.xml redundantes: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 excluir res/raw/keep.xml.

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

  1. 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>
    
  2. Execute testes de unidade e de instrumentação para o módulo usando atest:

    atest <TEST_MODULE_NAME>
    
  3. 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 apkanalyzer ou dexdump para 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>.apk
    
  • Ao modificar keep.xml ou ativar shrink_resources, use aapt2 dump resources para 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>.apk
    
  • Ao executar prebuilts/r8/r8.jar diretamente no classes.jar pré-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 --lib do módulo (android.jar, framework.jar e libs compartilhado) e as regras proguard.options geradas pelo AAPT2 de AndroidManifest.xml e 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:

  1. 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 arquivo r8keepradius.pb intermediário para cada módulo compilado em out/soong/.intermediates/. Para cada regra de manutenção e anotação keepanno, 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.
  2. Converta a saída de r8keepradius.pb em um relatório HTML interativo usando KeepRadiusHtmlReportGenerator (incluído em prebuilts/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_reports
    

    Quando recebe um diretório, o KeepRadiusHtmlReportGenerator percorre todos os arquivos *keepradius*.pb, gera um relatório HTML para cada módulo e cria out/keep_radius_reports/keepradius.html resumindo 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: grava r8inputs.zip no 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: grava r8trace.ptrace no 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 regras tracereferences configuradas por trace_references_from ou trace_references para 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.