Para tornar seu app o menor e o mais rápido possível, é necessário otimizar e minificar
seu build de lançamento com isMinifyEnabled = true
.
Isso ativa a redução, o que remove códigos e recursos não utilizados. ofuscação, que encurta os nomes das classes e dos membros do app; e otimização, que aplica estratégias mais agressivas para reduzir ainda mais o e melhorar o desempenho do seu app. Esta página descreve como o R8 executa essas tarefas de tempo de compilação para seu projeto e como você pode personalizar para resolvê-los com rapidez.
Quando você cria o projeto usando o Plug-in do Android para Gradle 3.4.0 ou mais recente, o ProGuard não é mais usado para realizar a otimização do código durante o tempo de compilação. Em vez disso, o plug-in usará o compilador R8 para processar as seguintes tarefas de tempo de compilação:
- Redução de código (ou tree shaking): detecta e remove com segurança classes, campos, métodos e atributos não utilizados do app e das dependências de biblioteca dele, o que a torna uma ferramenta valiosa para superar o limite de 64 mil referências. Por exemplo, caso você use somente algumas APIs de uma dependência de biblioteca, a redução pode identificar o código da biblioteca que seu app não usa e remover apenas esse código do app. Para saber mais, vá para a seção sobre como reduzir seu código.
- Redução de recursos: remove recursos não utilizados do app empacotado, incluindo os que estiverem em dependências da biblioteca do app. Ele trabalha em conjunto com a redução de código, de forma que, depois que um código não utilizado é removido, qualquer recurso não referenciado também pode ser removido com segurança. Para saber mais, vá para a seção sobre como reduzir os recursos.
- Otimização:inspeciona e reescreve o código para melhorar o tempo de execução.
o desempenho do aplicativo e reduzir ainda mais o tamanho dos arquivos DEX do seu aplicativo. Isso
melhora o desempenho de execução do código em até 30%, melhorando consideravelmente
tempo de inicialização e de renderização de frames. Por exemplo, se o R8 detectar que a ramificação
else {}
de determinada instrução "if/else" nunca é usada, ele removerá o código da ramificaçãoelse {}
. Para saber mais, acesse a seção sobre otimização de código. - Ofuscação (ou minificação de identificador): encurta o nome das classes. e membros, o que resulta em arquivos DEX menores. Para saber mais, acesse a seção sobre como ofuscar o código.
Ao criar a versão de lançamento do seu aplicativo, o R8 pode ser configurado para executar as tarefas de tempo de compilação descritas acima. Também é possível desativar alguns tarefas ou personalizar o comportamento do R8 por meio de arquivos de regras do ProGuard. Na verdade, o R8 funciona com todos os arquivos de regras existentes do ProGuard. Desse modo, a atualização do Plug-in do Android para Gradle para usar o R8 não exige que você altere as regras existentes.
Ativar redução, ofuscação e otimização
Quando você usa o Android Studio 3.4 ou o Plug-in do Android para Gradle 3.4.0 e versões mais recentes, o R8 é o compilador padrão que converte o bytecode Java do projeto no formato DEX, que é executado na plataforma Android. No entanto, quando você cria um novo projeto usando o Android Studio, a redução, a ofuscação e a otimização de código não são ativadas por padrão. Isso ocorre porque essas otimizações em tempo de compilação aumentam o tempo de compilação do projeto e podem introduzir bugs se você não personalizar suficientemente o código a ser mantido.
Portanto, é melhor ativar essas tarefas de tempo de compilação ao criar a versão final do app que você testou antes de publicar. Para ativar a redução, a ofuscação e otimização, inclua o seguinte no script de build do projeto:
Kotlin
android { buildTypes { getByName("release") { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `isDebuggable=false`. isMinifyEnabled = true // Enables resource shrinking, which is performed by the // Android Gradle plugin. isShrinkResources = true proguardFiles( // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. getDefaultProguardFile("proguard-android-optimize.txt"), // Includes a local, custom Proguard rules file "proguard-rules.pro" ) } } ... }
Groovy
android { buildTypes { release { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `debuggable false`. minifyEnabled true // Enables resource shrinking, which is performed by the // Android Gradle plugin. shrinkResources true // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt'), 'proguard-rules.pro' } } ... }
Arquivos de configuração do R8
O R8 usa arquivos de regras do ProGuard para modificar o comportamento padrão e entender melhor a estrutura do app, como as classes que servem como pontos de entrada para o código do app. Embora você possa modificar alguns arquivos de regras, algumas regras podem ser geradas automaticamente por ferramentas de tempo de compilação, como AAPT2, ou herdadas das dependências da biblioteca do app. A tabela abaixo descreve as fontes dos arquivos de regras do ProGuard que o R8 usa.
Fonte | Local | Descrição |
Android Studio | <module-dir>/proguard-rules.pro
|
Quando você cria um novo módulo usando o Android Studio, o ambiente de desenvolvimento integrado cria
um arquivo proguard-rules.pro no diretório raiz desse módulo.
Por padrão, esse arquivo não aplica regras. Portanto, inclua suas regras do ProGuard aqui, como suas regras keep personalizadas. |
Plug-in do Android Gradle | Gerado pelo Plug-in do Android para Gradle no momento da compilação. | O Plug-in do Android para Gradle gera
proguard-android-optimize.txt , que inclui regras úteis
para a maioria dos projetos Android e permite
anotações
@Keep* .
Por padrão, ao criar um novo módulo usando o Android Studio, o nível de módulo O script de build inclui esse arquivo de regras no build de lançamento para você.
Observação: o Plug-in do Android para Gradle inclui outros arquivos de regras do ProGuard
predefinidos, mas é recomendável que você use o
|
Dependências de biblioteca |
Em uma biblioteca AAR:
Em uma biblioteca JAR: Além desses locais, o Plug-in do Android para Gradle 3.6 ou mais recente também oferece suporte às regras de redução segmentadas. |
Se uma biblioteca AAR ou JAR for publicada com o próprio arquivo de regras e você incluem essa biblioteca como uma dependência no tempo de compilação, o R8 automaticamente essas regras ao compilar o projeto. Além das regras convencionais do ProGuard, o Plug-in do Android para Gradle A versão 3.6 ou superior também tem suporte regras de redução segmentadas. Estas são regras voltados para redutores específicos (R8 ou ProGuard), bem como em versões mais redutoras. O uso de arquivos de regras empacotados com bibliotecas é útil se determinados regras são necessárias para que a biblioteca funcione corretamente, ou seja, a biblioteca desenvolvedor concluiu as etapas de solução de problemas para você. No entanto, você deve estar ciente de que, como as regras são aditivas, certas regras incluídas por uma dependência de biblioteca não podem ser removidas, pode afetar a compilação de outras partes do app. Por exemplo, se um inclui uma regra para desativar otimizações de código, essa regra desativa e otimizações em todo o projeto. |
Android Asset Packaging Tool 2 (AAPT2) | Após criar seu projeto com minifyEnabled true :
<module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt
|
O AAPT2 gera regras keep com base em referências a classes no manifesto, nos layouts e em outros recursos do app. Por exemplo, o AAPT2 inclui uma regra keep para cada atividade registrada no manifesto do app como um ponto de entrada. |
Arquivos de configuração personalizados | Por padrão, quando você cria um novo módulo usando o Android Studio, o ambiente de desenvolvimento integrado
cria <module-dir>/proguard-rules.pro para você incluir suas
regras.
|
Você pode incluir mais configurações, e o R8 as aplica no momento da compilação. |
Quando você define a propriedade minifyEnabled
como true
, o R8 combina regras de todas
as fontes disponíveis listadas acima. É importante lembrar disso ao
resolver problemas com o R8, porque outras dependências de tempo de compilação,
como dependências de biblioteca, podem introduzir mudanças desconhecidas
no comportamento do R8.
Para gerar um relatório completo de todas as regras que o R8 aplica ao criar seu
projeto, inclua o seguinte no arquivo proguard-rules.pro
do seu módulo:
// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt
Regras de redução segmentadas
O Plug-in do Android para Gradle 3.6 ou mais recente oferece suporte que segmentam redutores específicos (R8 ou ProGuard), bem como versões de redutor específicas. Isso permite que desenvolvedores de bibliotecas adaptem as regras para funcionar de maneira ideal nos projetos que usam novas versões de redução, permitindo ao mesmo tempo que as regras existentes continuem usadas em projetos com versões de redutor mais antigas.
Para especificar as regras de redução segmentadas, os desenvolvedores de bibliotecas precisarão incluí-las em locais específicos dentro de uma biblioteca AAR ou JAR, conforme descrito abaixo.
In an AAR library:
proguard.txt (legacy location)
classes.jar
└── META-INF
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
In a JAR library:
META-INF
├── proguard/<ProGuard-rules-file> (legacy location)
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
Isso significa que as regras de redução segmentadas são armazenadas no META-INF/com.android.tools
de um JAR ou no diretório META-INF/com.android.tools
dentro
classes.jar
de um AAR.
Nesse diretório, pode haver vários diretórios com os nomes no formato
de r8-from-<X>-upto-<Y>
ou proguard-from-<X>-upto-<Y>
para indicar
e em que redutor as regras dentro dos diretórios são escritas.
Observe que as partes -from-<X>
e -upto-<Y>
são opcionais, a versão <Y>
é exclusivo, e os intervalos de versão precisam ser contínuos.
Por exemplo, r8-upto-8.0.0
, r8-from-8.0.0-upto-8.2.0
e r8-from-8.2.0
formam um conjunto válido de regras de redução segmentadas. As regras na seção
O diretório r8-from-8.0.0-upto-8.2.0
será usado pelo R8 da versão 8.0.0 até
mas não incluindo a versão 8.2.0.
Com essas informações, o Plug-in do Android para Gradle 3.6 ou mais recente vai selecionar a
e as regras dos diretórios R8 correspondentes. Se uma biblioteca não especificar a segmentação
de redução, o Plug-in do Android para Gradle vai selecionar as regras das
locais (proguard.txt
para uma AAR ou
META-INF/proguard/<ProGuard-rules-file>
para um JAR).
Os desenvolvedores de bibliotecas podem incluir regras de redução segmentadas ou versões Regras do ProGuard nas bibliotecas deles ou em ambos os tipos se quiserem manter compatibilidade com o Plug-in do Android para Gradle anterior à 3.6 ou outras ferramentas.
Incluir configurações adicionais
Quando você cria um novo projeto ou módulo usando o Android Studio, o ambiente de desenvolvimento integrado cria um
arquivo <module-dir>/proguard-rules.pro
para incluir suas próprias regras. Você
também pode incluir regras adicionais de outros arquivos, adicionando-as ao
proguardFiles
no script de build do módulo.
Por exemplo, você pode adicionar regras específicas para cada variante de compilação incluindo
outra propriedade proguardFiles
no bloco productFlavor
correspondente. O
arquivo do Gradle a seguir adiciona flavor2-rules.pro
à variação flavor2
do produto.
Agora, flavor2
usa todas as três regras do ProGuard porque as do bloco release
também são aplicadas.
Também é possível adicionar a propriedade testProguardFiles
, que especifica um
lista de arquivos ProGuard incluídos somente no APK de teste:
Kotlin
android { ... buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). "proguard-rules.pro" ) testProguardFiles( // The proguard files listed here are included in the // test APK only. "test-proguard-rules.pro" ) } } flavorDimensions.add("version") productFlavors { create("flavor1") { ... } create("flavor2") { proguardFile("flavor2-rules.pro") } } }
Groovy
android { ... buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). 'proguard-rules.pro' testProguardFiles // The proguard files listed here are included in the // test APK only. 'test-proguard-rules.pro' } } flavorDimensions "version" productFlavors { flavor1 { ... } flavor2 { proguardFile 'flavor2-rules.pro' } } }
Reduzir seu código
A redução de código com R8 é ativada por padrão quando você define a propriedade minifyEnabled
como true
.
A redução de código (também conhecida como tree shaking) é o processo de remoção do código que o R8 determina não ser necessário no tempo de execução. Esse processo pode reduzir muito o tamanho do app se, por exemplo, ele inclui muitas dependências de biblioteca, mas utiliza apenas uma pequena parte da funcionalidade delas.
Para reduzir o código do app, o R8 primeiro determina todos os pontos de entrada no código do app com base no conjunto combinado de arquivos de configuração. Esses pontos de entrada incluem todas as classes que a Plataforma Android pode usar para abrir as atividades ou os serviços do app. A partir de cada ponto de entrada, o R8 inspeciona o código do app para criar um gráfico de todos os métodos, variáveis de membros e outras classes que o app possa acessar no tempo de execução. O código que não está conectado a esse gráfico é considerado inacessível e pode ser removido do app.
A Figura 1 mostra um app com uma dependência de biblioteca de tempo de execução. Ao inspecionar o
código do app, o R8 determina que os métodos foo()
, faz()
e bar()
podem
ser acessados no ponto de entrada do MainActivity.class
. No entanto, a classe
OkayApi.class
ou o método baz()
nunca são usados pelo app durante a execução.
Por esse motivo, o R8 remove esse código ao reduzir o app.
O R8 determina pontos de entrada usando regras -keep
nos
arquivos de configuração do R8 do projeto. Isso significa que as regras keep especificam
as classes que não podem ser descartadas pelo R8 ao reduzir seu app, e o R8 considera
essas classes como possíveis pontos de entrada no app. O Plug-in do Android para Gradle
e o AAPT2 geram automaticamente as regras keep que são exigidas pela maior parte
dos projetos de app, como atividades, visualizações e serviços do app. No entanto,
se você precisar personalizar esse comportamento padrão com mais regras keep, leia
a seção sobre como personalizar o código a ser mantido.
Se, em vez disso, você tiver interesse apenas em reduzir o tamanho dos recursos do app, acesse a seção sobre como reduzir os recursos.
Se um projeto de biblioteca for reduzido, um app que depende dessa biblioteca inclui classes de biblioteca reduzidas. Talvez seja necessário ajustar as regras de manutenção da biblioteca se há classes ausentes no APK da biblioteca; Se você está criando e publicando uma biblioteca no formato AAR, os arquivos JAR locais dos quais sua biblioteca depende não são reduzidos no arquivo AAR.
Personalizar o código a ser mantido
Na maioria das situações, o arquivo de regras padrão do ProGuard (proguard-android-optimize.txt
)
é suficiente para que o R8 remova apenas o código não utilizado. Entretanto,
algumas situações são difíceis para o R8 analisar corretamente, e ele pode remover
códigos de que seu app precisa. Alguns dos exemplos de quando códigos podem ser removidos incorretamente
incluem:
- Quando seu app chama um método da Java Native Interface (JNI)
- Quando seu aplicativo procura código em tempo de execução (por exemplo, com reflexão)
Os testes do app costumam revelar todos os erros causados por código removido indevidamente, mas você também pode inspecionar o código removido gerando um relatório do código removido.
Para corrigir erros e forçar o R8 a manter determinados códigos, adicione uma
linha -keep
(link em inglês) no arquivo de regras do ProGuard. Exemplo:
-keep public class MyClass
Como alternativa, você pode adicionar a
anotação @Keep
ao código que
prefere manter. Adicionar @Keep
a uma classe manterá toda a classe intacta.
Adicionar essa anotação a um método ou campo manterá o método/campo (e o nome), bem como
o nome da classe, intactos. Essa anotação estará disponível quando você estiver usando
a
biblioteca AndroidX Annotations
e quando incluir o arquivo de regras do ProGuard que acompanha o Plug-in
do Android para Gradle, conforme descrito na seção sobre como
ativar a redução.
Há muitos fatores a serem considerados ao usar a opção -keep
. Para
saber mais sobre como personalizar seu arquivo de configuração, leia o
Manual do ProGuard.
A
seção Solução de problemas
descreve outros problemas comuns que podem ser encontrados quando o código é
removido (links em inglês).
Remover bibliotecas nativas
Por padrão, as bibliotecas de código nativo são removidas nos builds de lançamento do seu app. Isso consiste na remoção da tabela de símbolos e das informações de depuração contidas nas bibliotecas nativas usadas pelo app. A remoção de bibliotecas de código nativo resulta em uma economia significativa de tamanho. No entanto, é impossível diagnosticar falhas no Google Play Console devido à falta de informações (como nomes de classe e função).
Compatibilidade com falhas nativas
O Google Play Console informa falhas nativas em Android vitals. Em poucas etapas, você pode gerar e fazer upload de um arquivo de símbolos de depuração nativo para seu app. Esse arquivo permite o stack trace de falhas nativas simbolizadas (que incluem nomes de classe e função) no Android vitals para ajudar você a depurar seu app na produção. Essas etapas variam dependendo da versão do Plug-in do Android para Gradle usada no projeto e da saída de build do projeto.
Versão do Plug-in do Android para Gradle: 4.1 ou mais recente
Se o projeto criar um Android App Bundle, você poderá incluir automaticamente o
arquivo de símbolos de depuração nativo nele. Para incluir esse arquivo em builds de lançamento, adicione o
seguinte ao arquivo build.gradle.kts
do seu app:
android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }
Selecione o nível do símbolo de depuração:
- Use
SYMBOL_TABLE
para conseguir os nomes de função nos stack traces simbólicos do Play Console. Esse nível tem suporte a Tombstones. - Use
FULL
para conseguir os nomes de função, arquivos e números de linha nos stack traces simbólicos do Play Console.
Se o projeto cria um APK, use a configuração de compilação build.gradle.kts
mostrada
anteriormente para gerar o arquivo de símbolos de depuração nativo separadamente. Faça
o upload do arquivo de símbolos de depuração nativo
manualmente para o Google Play Console. Como parte do processo de compilação, o Plug-in do
Android para Gradle gera o arquivo no seguinte local do projeto:
app/build/outputs/native-debug-symbols/variant-name/native-debug-symbols.zip
Plug-in do Android para Gradle 4.0 ou versões anteriores (e outros sistemas de compilação)
Como parte do processo de compilação, o Plug-in do Android para Gradle mantém uma cópia das bibliotecas sem símbolos de depuração em um diretório do projeto. Essa estrutura de diretório é semelhante a esta:
app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── arm64-v8a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── x86/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
└── x86_64/
├── libgameengine.so
├── libothercode.so
└── libvideocodec.so
Compacte o conteúdo do diretório:
cd app/build/intermediates/cmake/universal/release/obj
zip -r symbols.zip .
Faça upload do arquivo
symbols.zip
manualmente para o Google Play Console.
Reduzir recursos
A redução de recursos funciona apenas em conjunto com a redução de código. Depois que o redutor de código remove todos os códigos não utilizados, o redutor de recursos pode identificar quais recursos ainda são usados pelo app. Isso é válido especialmente ao adicionar bibliotecas de código que contêm recursos: é necessário remover o código não utilizado da biblioteca para que as referências aos recursos da biblioteca sejam removidas e, dessa forma, o redutor de recursos possa removê-los.
Para ativar a redução de recursos, defina a propriedade shrinkResources
como true
no script de build (junto
minifyEnabled
para redução de código). Exemplo:
Kotlin
android { ... buildTypes { getByName("release") { isShrinkResources = true isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } }
Groovy
android { ... buildTypes { release { shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
Caso ainda não tenha criado o app usando minifyEnabled
para a
redução do código, tente fazer isso antes de ativar shrinkResources
,
porque pode ser necessário editar o arquivo proguard-rules.pro
para
armazenar classes ou métodos criados ou invocados de maneira dinâmica antes de começar
a remover recursos.
Personalizar quais recursos manter
Se quiser manter ou descartar recursos específicos, crie um arquivo
XML no projeto com uma tag <resources>
e especifique os
recursos a serem mantidos no atributo tools:keep
e os que
serão descartados no atributo tools:discard
. Os dois atributos aceitam uma
lista separada por vírgulas de nomes de recursos. Você pode usar o caractere asterisco como
curinga.
Exemplo:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*" tools:discard="@layout/unused2" />
Salve esse arquivo nos recursos do projeto como, por exemplo, em
res/raw/my.package.keep.xml
. O build não empacota esse arquivo no
app.
Observação:use um nome exclusivo para o arquivo keep
. Quando
bibliotecas diferentes são vinculadas entre si, as regras Keep conflitam
caso contrário, o que poderia causar problemas com regras ignoradas ou regras de retenção
do Google Cloud.
Pode parecer bobagem especificar quais recursos descartar quando se poderia simplesmente
excluí-los, mas essa ferramenta pode ser útil ao usar variantes de compilação. Para
Por exemplo, é possível colocar todos os seus recursos em um diretório de projeto comum,
depois criar um arquivo my.package.build.variant.keep.xml
diferente para cada
variante de build quando você sabe que um determinado recurso parece ser usado no código
(e, portanto, não será removida pelo redutor), mas sabe que ela não será
usada para a variante de build em questão. Também é possível que as ferramentas de build
identificou incorretamente um recurso como necessário, o que é possível porque
o compilador adiciona os IDs de recursos inline e, em seguida, o analisador de recursos pode não
saber a diferença entre um recurso realmente referenciado e um valor inteiro
no código que têm o mesmo valor.
Ativar verificações de referências rígidas
Normalmente, o redutor de recursos pode determinar com precisão se um recurso
está sendo usado. No entanto, se o código chamar
Resources.getIdentifier()
(ou se uma das bibliotecas fizer isso, como a biblioteca AppCompat
faz), isso significa que o código está procurando nomes de recursos com base em
strings geradas de forma dinâmica. Ao fazer isso, o redutor de recursos se comporta
de maneira defensiva por padrão e marca todos os recursos que tenham um formato de nome correspondente como
potencialmente utilizado e indisponível para remoção.
O código a seguir, por exemplo, faz com que todos os recursos com o
prefixo img_
sejam marcados como utilizados.
Kotlin
val name = String.format("img_%1d", angle + 1) val res = resources.getIdentifier(name, "drawable", packageName)
Java
String name = String.format("img_%1d", angle + 1); res = getResources().getIdentifier(name, "drawable", getPackageName());
O redutor de recursos também examina todas as constantes de strings no código,
bem como em vários recursos res/raw/
, procurando URLs de recursos
com formato semelhante a
file:///android_res/drawable//ic_plus_anim_016.png
. Se ele encontra
strings como essa ou outras que pareçam poder ser usadas para construir URLs
como esse, elas não são removidas.
Esses são exemplos do modo de redução segura que é ativado por padrão.
No entanto, você pode desativar essa abordagem de "melhor prevenir do que remediar" e especificar
que o redutor de recursos precisa manter apenas os recursos que com certeza estão sendo usados. Para
fazer isso, defina shrinkMode
como strict
no
arquivo keep.xml
, da seguinte forma:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="strict" />
Se você ativar o modo de redução rígida e o código também referir-se
a recursos com strings geradas dinamicamente, como mostrado acima, precisará
manter esses recursos manualmente usando o atributo tools:keep
.
Remover recursos alternativos não utilizados
O redutor de recursos do Gradle remove apenas os recursos não referenciados
pelo código do seu app, o que significa que ele não removerá
recursos alternativos para diferentes configurações de dispositivo. Se necessário,
você pode usar a propriedade resConfigs
do Plug-in do Android para Gradle para
remover os arquivos de recursos alternativos que não são usados pelo app.
Por exemplo, se você estiver usando uma biblioteca que contenha recursos de idioma
(como AppCompat ou Google Play Services), seu app conterá todas
as strings de idiomas traduzidas para as mensagens nessas bibliotecas, independentemente do
restante do seu app estar traduzido ou não para os mesmos idiomas. Se quiser
manter apenas os idiomas aos quais o app tem suporte oficial,
use a propriedade resConfig
para a especificação deles. Os recursos para
idiomas não especificados serão removidos.
O snippet a seguir mostra como limitar seus recursos de idioma para apenas inglês e francês:
Kotlin
android { defaultConfig { ... resourceConfigurations.addAll(listOf("en", "fr")) } }
Groovy
android { defaultConfig { ... resConfigs "en", "fr" } }
Ao lançar um app usando o formato Android App Bundle, por padrão, apenas os idiomas configurados no dispositivo de um usuário serão transferidos por download ao instalar o app. Da mesma forma, somente os recursos que correspondem à densidade da tela do dispositivo e às bibliotecas nativas correspondentes da ABI do dispositivo serão incluídas no download. Para ver mais informações, consulte a configuração do Android App Bundle.
Para apps legados lançados com APKs (criados antes de agosto de 2021), você pode personalizar a densidade da tela ou os recursos da ABI que serão incluídos no seu APK criando vários APKs, cada um deles para uma configuração de dispositivo diferente.
Mesclar recursos duplicados
Por padrão, o Gradle também mescla recursos com nomes idênticos, como
drawables com o mesmo nome que possam estar em pastas de recursos diferentes. Esse
comportamento não é controlado pela propriedade shrinkResources
e
não pode ser desativado, porque ele é necessário para evitar erros quando vários
recursos possuem o mesmo nome procurado pelo código.
A mesclagem de recursos ocorre apenas quando dois ou mais recursos compartilham um nome, tipo ou qualificador de recurso idêntico. O Gradle seleciona qual arquivo considera ser a melhor escolha entre as cópias, com base na ordem de prioridade descrita abaixo, e transmite apenas esse recurso ao AAPT para distribuição no artefato final.
O Gradle procura recursos duplicados nos seguintes locais:
- Nos recursos principais associados ao conjunto de origem principal, normalmente
localizados em
src/main/res/
- Nas sobreposições de variações, dos tipos e variações de compilação
- Nas dependências de biblioteca do projeto
O Gradle combina os recursos duplicados na seguinte ordem de prioridade (crescente):
Dependências → Principal → Variação de compilação → Tipo de build
Por exemplo, se um recurso duplicado aparece tanto nos recursos principais quanto em uma variação de compilação, o Gradle seleciona o recurso na variação de compilação.
Se recursos idênticos aparecerem no mesmo conjunto de origem, o Gradle não poderá mesclá-los
e emitirá um erro de mesclagem de recursos. Isso poderá ocorrer se você definir vários
conjuntos de origem na propriedade sourceSet
do arquivo
build.gradle.kts
, por exemplo, se src/main/res/
e src/main/res2/
tiverem recursos idênticos.
Ofuscar o código
O objetivo da ofuscação é reduzir o tamanho do app encurtando os nomes das classes, dos métodos e dos campos dele. Veja a seguir um exemplo de ofuscação usando o R8.
androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
android.content.Context mContext -> a
int mListItemLayout -> O
int mViewSpacingRight -> l
android.widget.Button mButtonNeutral -> w
int mMultiChoiceItemLayout -> M
boolean mShowTitle -> P
int mViewSpacingLeft -> j
int mButtonPanelSideLayout -> K
Embora a ofuscação não remova o código do seu app, economias significativas de tamanho podem ser vistas em apps com arquivos DEX que indexam muitas classes, métodos e campos. No entanto, considerando que a ofuscação renomeia diferentes partes do seu código, algumas tarefas, como a inspeção de stack traces, exigem outras ferramentas. Para entender o stack trace após a ofuscação, leia a seção sobre como decodificar um stack trace ofuscado.
Além disso, se o código depende de nomenclatura previsível para os métodos e as classes do app (ao usar reflexão, por exemplo), você precisa tratar essas assinaturas como pontos de entrada e especificar regras keep para elas, conforme descrito na seção sobre como personalizar os códigos a serem mantidos. As regras keep instruem o R8 não só a manter esse código no DEX final do seu app, mas também a manter a nomenclatura original.
Decodificar um stack trace ofuscado
Depois que o R8 ofusca seu código, é difícil (se não impossível) entender um stack trace, porque os nomes de classes e métodos podem ter sido modificados. Para acessar o stack trace original, refaça o rastreamento.
Otimização de código
Para otimizar ainda mais seu app, o R8 inspeciona o código em um nível mais profundo para remover mais códigos não utilizados ou, quando possível, reescrever o código para torná-los com menos detalhes. Veja a seguir alguns exemplos dessas otimizações:
- Caso seu código nunca receba a ramificação
else {}
de determinada instrução "if/else", o R8 poderá remover o código da ramificaçãoelse {}
. - Se o código chamar um método em apenas alguns lugares, o R8 poderá remover o método e in-line nos locais de chamadas.
- Se o R8 determinar que uma classe tem apenas uma subclasse, e a classe em si não for instanciada (por exemplo, uma classe base abstrata usada apenas por uma classe de implementação concreta), o R8 poderá combinar as duas classes e remover uma classe do app.
- Para saber mais, leia os posts do blog sobre otimização do R8 (em inglês) de Jake Wharton.
O R8 não permite desativar ou ativar otimizações discretas nem modificar o
comportamento de uma otimização. Na verdade, o R8 ignora todas as regras do ProGuard que tentam
modificar otimizações padrão, como -optimizations
e
-optimizationpasses
. Essa restrição é importante porque, como o R8 continua a
melhorar, manter um comportamento padrão para otimizações ajuda a equipe do Android
Studio a solucionar facilmente os problemas que podem ocorrer.
Ao ativar a otimização, os stack traces do app mudam. Por exemplo, a inserção de linhas remove frames de pilha. Consulte a seção sobre como refazer o rastreamento para aprender a acessar os stack traces originais.
Impacto no desempenho do tempo de execução
Se a redução, a ofuscação e a otimização estiverem ativadas, o R8 melhorará o desempenho em tempo de execução do código (incluindo o tempo de inicialização e para a renderização do frame na linha de execução de IU) em até 30%. Desativar qualquer uma dessas opções limita drasticamente o conjunto de otimizações O R8 usa.
Se o R8 estiver ativado, você também criar perfis de inicialização para um desempenho de inicialização ainda melhor.
Ativar otimizações mais agressivas
O R8 inclui um conjunto de otimizações adicionais (chamadas de "modo completo") que faz com que ele se comporte de forma diferente do ProGuard. Essas otimizações são possibilitadas padrão desde Plug-in do Android para Gradle versão 8.0.0.
Para desativar essas outras otimizações, inclua o seguinte em
o arquivo gradle.properties
do seu projeto:
android.enableR8.fullMode=false
Como as otimizações adicionais fazem o R8 se comportar de maneira diferente do ProGuard, pode ser necessário incluir regras adicionais do ProGuard para evitar o tempo de execução se estiver usando regras projetadas para o ProGuard. Por exemplo, digamos que seu referencia uma classe usando a API Java Reflection. Quando não usar "modo completo", O R8 pressupõe que você pretende examinar e manipular objetos de essa classe no tempo de execução, mesmo que o código não faça isso, e ela automaticamente mantém a classe e seu inicializador estático.
No entanto, ao usar o "modo completo", o R8 não faz essa suposição e, se o R8 declara que seu código nunca usa a classe em tempo de execução, remove a do DEX final do seu app. Ou seja, se você quiser manter a classe e estático, você precisa incluir uma regra keep em seu arquivo de regras para fazer isso.
Se você tiver problemas ao usar o "modo completo" do R8, consulte a Página de perguntas frequentes do R8 para uma possível solução. Caso não consiga resolver o problema, informe um bug (link em inglês).
Refazer o rastreamento de stack traces
O código processado pelo R8 muda de várias maneiras que podem dificultar a compreensão dos stack traces, já que eles deixam de corresponder exatamente ao código-fonte. Por exemplo, os números das linhas podem mudar quando as informações de depuração não forem mantidas. Isso pode ocorrer devido a otimizações como a inserção ou remoção de linhas. O fator que mais contribui para isso é a ofuscação, que faz com que até mesmo as classes e os métodos mudem de nome.
Para recuperar o stack trace original, o R8 oferece a ferramenta de linha de comando retrace, que faz parte do pacote de ferramentas de linha de comando.
Refazer o rastreamento dos stack traces do app exige que o
build retenha informações suficientes para executar o rastreamento. Para isso, adicione as seguintes
regras ao arquivo proguard-rules.pro
do módulo:
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
O atributo LineNumberTable
retém as informações sobre os posicionamentos
nos métodos de modo que essas posições sejam mostradas nos stack traces. O atributo SourceFile
garante que todas as possíveis execuções mostrem as informações sobre os posicionamentos,
e a diretiva -renamesourcefileattribute
define o nome do arquivo de origem em stack
traces como SourceFile
. O nome real do arquivo de origem inicial não
é necessário neste processo, já que o arquivo de mapeamento contém o de origem inicial.
O R8 cria um arquivo mapping.txt
sempre que é executado,
contendo as informações necessárias para associar os novos stack traces aos
originais. O Android Studio salva o arquivo no
diretório
<module-name>/build/outputs/mapping/<build-type>/
.
É possível fazer upload do arquivo mapping.txt
para cada versão do app durante a publicação dele no Google Play. Ao publicar usando Android App Bundles, esse
arquivo é incluído automaticamente como parte do conteúdo do pacote de app. O Google
Play vai rastrear novamente os stack
traces de problemas informados pelos usuários para que você possa fazer a análise deles no Play
Console. Para saber mais, consulte o artigo da Central de Ajuda sobre como
desofuscar stack traces de falhas.
Resolver problemas com o R8
Esta seção descreve algumas estratégias para solucionar problemas ao ativar redução, ofuscação e otimização usando o R8. Se você não encontrar uma solução para seu problema abaixo, leia também a página de perguntas frequentes do R8 e o guia de solução de problemas do ProGuard (links em inglês).
Gerar um relatório de código removido (ou mantido)
Para ajudar a resolver problemas específicos do R8, pode ser útil ver um relatório sobre
todo o código removido do app pelo R8. Adicione -printusage <output-dir>/usage.txt
ao arquivo de regras
personalizadas para cada módulo que você quer
gerar um relatório. Quando você ativa o R8 e cria seu app, o R8 gera um
relatório com o caminho e o nome do arquivo especificados. O relatório do código removido
é semelhante ao seguinte:
androidx.drawerlayout.R$attr
androidx.vectordrawable.R
androidx.appcompat.app.AppCompatDelegateImpl
public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
public boolean hasWindowFeature(int)
public void setHandleNativeActionModesEnabled(boolean)
android.view.ViewGroup getSubDecor()
public void setLocalNightMode(int)
final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
private static final boolean DEBUG
private static final java.lang.String KEY_LOCAL_NIGHT_MODE
static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
...
Se, em vez disso, você quiser ver um relatório dos pontos de entrada que o R8 determina a partir
das regras keep do projeto, inclua -printseeds <output-dir>/seeds.txt
no
arquivo de regras personalizadas. Quando você ativa o R8 e cria seu app, o R8 gera
um relatório com o caminho e o nome do arquivo especificados. O relatório de pontos de entrada mantidos
é semelhante ao seguinte:
com.example.myapplication.MainActivity
androidx.appcompat.R$layout: int abc_action_menu_item_layout
androidx.appcompat.R$attr: int activityChooserViewStyle
androidx.appcompat.R$styleable: int MenuItem_android_id
androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
androidx.lifecycle.FullLifecycleObserverAdapter
...
Solucionar problemas com a redução de recursos
Quando você reduz recursos, a janela Build mostra um resumo dos recursos removidos do app. Você precisa primeiro clicar em Toggle view no lado esquerdo da janela para exibir a saída de texto detalhada do Gradle. Exemplo:
:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning
O Gradle também cria um arquivo de diagnóstico chamado resources.txt
em
<module-name>/build/outputs/mapping/release/
(a mesma
pasta dos arquivos de saída do ProGuard). Esse arquivo traz detalhes como quais
recursos fazem referência a outros e quais são utilizados ou
removidos.
Por exemplo: para descobrir por que @drawable/ic_plus_anim_016
ainda
está no app, abra o arquivo resources.txt
e pesquise o
nome desse arquivo. Para descobrir se ele é referenciado em outro recurso, faça
o seguinte:
16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out] @drawable/ic_plus_anim_016
Agora é preciso saber por que @drawable/add_schedule_fab_icon_anim
está acessível. Se você procurar de baixo para cima, verá que esse recurso está listado
em "The root reachable resources are:". Isso significa que há uma referência de código
para add_schedule_fab_icon_anim
, ou seja, o ID do R.drawable foi
encontrado em código acessível.
Se você não estiver usando a verificação rígida, os IDs de recursos poderão ser marcados como alcançáveis se houver constantes de strings que pareçam poder ser usadas para construir nomes para recursos dinamicamente carregados. Nesse caso, se você pesquisar na saída de build pelo nome do recurso, poderá ver uma mensagem como esta:
10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
used because it format-string matches string pool constant ic_plus_anim_%1$d.
Se vir uma dessas strings e tiver certeza de que ela não
está sendo usada para carregar o recurso em questão de forma dinâmica, você poderá usar o
atributo tools:discard
para instruir o sistema de compilação a removê-la,
conforme descrito na seção sobre como personalizar quais recursos serão mantidos.