Otimização para autores de bibliotecas

Como autor de uma biblioteca, você precisa garantir que os desenvolvedores de apps possam incorporar facilmente sua biblioteca ao app deles, mantendo uma experiência de alta qualidade para o usuário final. Verifique se a biblioteca é compatível com a otimização do Android sem configuração adicional ou documente que ela pode ser inadequada para uso no Android.

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.

Usar codegen em vez de reflexão

Sempre que 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ódigos clichê na programação, mas a geração de código é mais compatível com um otimizador de apps como o R8:

  • Com a geração de código, o código é analisado e modificado durante o processo de build. Como não há modificações importantes após o tempo de compilação, o otimizador sabe qual código é necessário e o que pode ser removido com segurança.
  • Com a reflexão, o código é analisado e manipulado 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. É provável que ele remova 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.

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.

Tipos de regras de manutenção em bibliotecas

Há dois tipos distintos de regras de manutenção que podem ser usadas em bibliotecas:

  • As regras de manutenção do consumidor 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 manutenção do consumidor, que usam o mesmo formato das regras de manutenção do app. Essas regras são agrupadas em artefatos de biblioteca (AARs ou JARs) e são consumidas automaticamente durante a otimização de apps 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 ela é criada. Elas só são necessárias se você decidir otimizar parcialmente sua biblioteca no momento da 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.

Escrever regras de retenção de consumidores

Além das orientações gerais sobre regras de retenção, confira as recomendações específicas para autores de bibliotecas.

  • Não use regras globais inadequadas. Evite colocar configurações globais como -dontobfuscate ou -allowaccessmodification no arquivo de regras de preservação do consumidor da biblioteca, já que elas afetam todos os apps que usam sua biblioteca.
  • Não use -repackageclasses no arquivo de regras de manutençã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 tornar sua biblioteca mais eficiente, mesmo que os apps que a consomem não estejam otimizados, mas geralmente não é necessário, já que os apps também precisam ser otimizados. Para mais detalhes sobre a otimização de bibliotecas, consulte Otimização para autores de bibliotecas.
  • 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 manutençã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
    • -overloadaggressively
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

Bibliotecas AAR

Para adicionar regras de consumidor a uma biblioteca AAR, use a opção consumerProguardFiles no script de build do módulo da biblioteca Android. Para mais informações, consulte nossas orientações sobre como criar módulos de biblioteca.

Kotlin

android {
    defaultConfig {
        consumerProguardFiles("consumer-proguard-rules.pro")
    }
    ...
}

Groovy

android {
    defaultConfig {
        consumerProguardFiles 'consumer-proguard-rules.pro'
    }
    ...
}

Bibliotecas JAR

Para agrupar regras com sua biblioteca Kotlin/Java que é enviada como um JAR, coloque o arquivo de regras no diretório META-INF/proguard/ do JAR final, com qualquer nome de arquivo. Por exemplo, se o código estiver em <libraryroot>/src/main/kotlin, coloque um arquivo de regras do consumidor em <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro, e as regras serão agrupadas no local correto 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 build da biblioteca 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. Somente durante um build de aplicativo, quando uma biblioteca é incluída como parte de um aplicativo, o R8 pode saber como todos os métodos da biblioteca são usados e quais parâmetros são transmitidos. 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 essa 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 build, geralmente com getDefaultProguardFile("proguard-android-optimize.txt"), para definir qual parte da biblioteca deve ser mantida durante a build. 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 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, além de permitir que 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 não incluindo 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).