Otimização para autores de bibliotecas

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 obedeçam a 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ê é 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 Escolha bibliotecas com sabedoria.

Entender os tipos de regra de retenção

Há dois tipos distintos de regras de manutenção que podem ser usadas 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 próprio código ou código definido por um app cliente, essas regras precisam descrever qual código precisa ser mantido. As bibliotecas precisam empacotar 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 consumidas automaticamente durante a otimização do app Android quando a biblioteca é usada. Essas regras são mantidas no arquivo especificado com a propriedade consumerProguardFiles no seu arquivo build.gradle.kts (ou build.gradle). Para saber mais, consulte Escrever regras de retenção do consumidor.
  • As regras de manutenção de build da biblioteca são aplicadas quando a biblioteca é criada. Eles só são necessários se você decidir otimizar parcialmente sua biblioteca no tempo de build. Eles 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 propriedade proguardFiles no arquivo build.gradle.kts (ou build.gradle). 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 no desempenho do binário final do app que as consome. Além das práticas recomendadas gerais de regras de retenção, os autores de bibliotecas precisam obedecer a requisitos específicos e considerar outras diretrizes.

Seguir os requisitos de otimização

A ineficiência nas bibliotecas é um dos principais fatores que contribuem para o bloat do app, o desperdício de memória, a lentidão na inicialização 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.

  • Sem regras de retenção amplas ou em todo o pacote:sua biblioteca não pode incluir regras de retenção amplas que mantêm a maior parte do código na biblioteca ou em outra biblioteca. Regras de retenção abrangentes podem resolver falhas no curto prazo, mas aumentam o tamanho do app 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 use regras globais inadequadas:nunca use opções globais como -dontobfuscate ou -allowaccessmodification.

  • Use a geração de código 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. A geração de código e a reflexão são abordagens comuns para evitar código boilerplate na programação, mas a geração de código é mais compatível com um otimizador de app 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 em tempo 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. Isso 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 pelo Room, Dagger2 e muitos outros.

  • Compatibilidade com o 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 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 -repackageclasses no arquivo de regras de retenção do consumidor da biblioteca. No entanto, para otimizar o build da biblioteca, use -repackageclasses com 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. No entanto, isso 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 da biblioteca, não no arquivo de regras de manutenção do consumidor da biblioteca:
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • Os autores de bibliotecas precisam manter o atributo RuntimeVisibleAnnotations nas regras de retenção do consumidor se as anotações forem usadas no tempo de execução.
  • Os autores de bibliotecas não devem usar as seguintes opções globais nas regras de preservação 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 é adequada

Se você precisar usar a reflexão, faça isso apenas em uma das seguintes opções:

  • Tipos de destino específicos (implementadores ou subclasses de interface específicos)
  • Codificar usando uma anotação de ambiente de execução específica

Usar a reflexão dessa forma limita o custo de tempo de execução e permite escrever regras de retenção de consumidores segmentadas.

Essa forma específica e direcionada de reflexão é um padrão que pode ser visto na estrutura do Android (por exemplo, ao inflar atividades, visualizações e elementos gráficos) e nas bibliotecas do 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. Isso inclui 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 à 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 fusão 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 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 a criação da biblioteca AAR.

  • Entendimento incorreto da opção -keep: a regra -keep impede que o R8 execute qualquer uma das transmissões de otimização. Para mais informações, consulte Escolher a opção de retenção certa.

Configurar o pacote de regras

Para garantir que as regras de retenção do consumidor sejam aplicadas corretamente, é necessário empacotá-las de acordo com o formato da biblioteca.

Bibliotecas AAR

Para adicionar regras de consumidor a uma biblioteca AAR, use a opção consumerProguardFiles no script de build do módulo de biblioteca do 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. As regras serão agrupadas no local correto do JAR de saída.

Verifique se o JAR final agrupa as regras corretamente. Para isso, confira se elas estão no diretório META-INF/proguard.

Otimizar a criação de bibliotecas AAR (avançado)

Em geral, não é necessário otimizar uma build de biblioteca diretamente porque as possíveis otimizações no momento da build são muito limitadas. Como desenvolvedor de bibliotecas, você precisa considerar várias etapas de otimização e manter o comportamento, tanto no momento da criação da biblioteca quanto do app, antes de otimizar a biblioteca.

Se você ainda quiser otimizar sua biblioteca no momento da 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:

  • Os proguardFiles são usados no momento da criação, geralmente com getDefaultProguardFile("proguard-android-optimize.txt"), para definir qual parte da biblioteca deve ser mantida durante a criação. No mínimo, essa é sua API pública.
  • consumerProguardFiles, por outro lado, são empacotados na biblioteca para afetar quais otimizações acontecem mais tarde, durante o build de um app que consome sua biblioteca.

Por exemplo, se a biblioteca usar reflexão para construir classes internas, talvez seja necessário definir as regras de manutenção em proguardFiles e consumerProguardFiles.

Se você usar -repackageclasses no build da sua biblioteca, reembale 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, ao mesmo tempo em que 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 para quais versões do R8 as regras foram escritas. Cada subdiretório pode ter um ou mais arquivos com as regras do R8, com qualquer nome de arquivo e extensão.

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ó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é a versão 8.2.0, mas sem incluir essa última.

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 segmentadas, o plug-in do Android Gradle vai selecionar as regras dos locais legados (proguard.txt para um AAR ou META-INF/proguard/<ProGuard-rule-files> para um JAR).