Otimização para autores de bibliotecas

Como autor de uma biblioteca, você precisa garantir que os desenvolvedores de apps possam incorporá-la facilmente ao app, mantendo uma experiência de usuário de alta qualidade. Verifique se a biblioteca é compatível com a otimização do Android sem configuração adicional ou documente que a biblioteca 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 interna em um app grande modularizado.

Se você é um desenvolvedor de apps e quer saber como otimizar seu app Android, consulte Ativar a otimização de apps. Para saber quais bibliotecas são adequadas para uso, consulte Escolher bibliotecas com sabedoria.

Usar a geração de código em vez da reflexão

Sempre que possível, use 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 de texto clichê durante a 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. Ele provavelmente removerá o código usado dinamicamente por reflexão durante a execução, o que causa falhas no app para os usuários.

Muitas bibliotecas modernas usam a geração de código em vez da reflexão. Consulte KSP para conferir um ponto de entrada comum, usado por Room, Dagger2 e muitos outros.

Quando a reflexão é aceitável

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

  • Tipos segmentados específicos (implementadores de interface ou subclasses específicos)
  • Código usando uma anotação de tempo de execução específica

O uso da reflexão dessa maneira limita o custo de execução e permite a gravação de regras de manutenção de consumidores segmentados.

Essa forma específica e direcionada de reflexão é um padrão que pode ser encontrado no framework do Android (por exemplo, ao inflar atividades, visualizações e drawables) e nas bibliotecas do AndroidX (por exemplo, ao criar WorkManager ListenableWorkers ou RoomDatabases). Por outro lado, a reflexão aberta do Gson não é adequada para uso em apps Android.

Escrever regras de retenção do consumidor

As bibliotecas precisam empacotar regras de retenção "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 são consumidas automaticamente durante a otimização de apps Android quando a biblioteca é usada.

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 a 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 no JAR de saída.

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

Otimizar o build da biblioteca AAR (avançado)

Em geral, não é recomendável otimizar um build de biblioteca diretamente, porque as otimizações possíveis no momento de criação da biblioteca são muito limitadas. Somente durante um build de app, 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ários estágios de otimização e manter o comportamento, tanto no momento de criação da biblioteca quanto do app, antes de otimizar a biblioteca.

Se você ainda quiser otimizar sua biblioteca no momento do build, o Plug-in do Android para Gradle oferece suporte a isso.

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:

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

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

Se você usar -repackageclasses no build da biblioteca, reempacote as classes em um subpacote dentro do pacote da biblioteca. Por exemplo, use -repackageclasses 'com.example.mylibrary.internal' em vez de -repackageclasses 'internal'.

Suporte a diferentes versões do R8 (avançado)

É possível personalizar regras para segmentar versões específicas do R8. Isso permite que a biblioteca funcione de maneira otimizada em projetos que usam versões mais recentes do R8, permitindo que as regras atuais continuem sendo usadas em projetos com versões mais antigas do R8.

Para especificar regras R8 segmentadas, é necessário incluí-las 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 contendo as regras do R8, com qualquer nome e extensão de arquivo.

As partes -from-<X> e -upto-<Y> são opcionais, a versão <Y> é exclusiva, e os intervalos de versão geralmente são contínuos, mas também podem se sobrepor.

Por exemplo, r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0 e r8-from-8.2.0 são nomes de diretório que representam um conjunto de regras 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é, mas não incluindo a versão 8.2.0.

O Plug-in do Android para Gradle usa essas informações para selecionar todas as regras que podem ser usadas pela versão atual do R8. Se uma biblioteca não especificar regras R8 direcionadas, 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).