Reduzir, ofuscar e otimizar o aplicativo

Para que o aplicativo seja o menor possível, ative a redução para remover código e recursos não utilizados da sua compilação de lançamento. Quando ativar a redução, você também aproveita o ofuscamento, que reduz os nomes das classes e dos membros do seu app, e a otimização, que aplica estratégias mais agressivas para reduzir ainda mais o tamanho do app. Esta página descreve como o R8 realiza essas tarefas de tempo de compilação para seu projeto e como é possível personalizá-las.

Quando você cria o projeto usando o plug-in do Android para Gradle 3.4.0 ou posterior, o ProGuard não é mais usado para realizar a otimização do código em 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 K 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 aplicativo empacotado, incluindo os que estiverem em dependências da biblioteca do aplicativo. 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.
  • Ofuscação: encurta o nome de classes e membros, o que resulta em arquivos DEX menores. Para saber mais, acesse a seção sobre como ofuscar o código.
  • Otimização: inspeciona e reescreve o código para reduzir ainda mais o tamanho dos arquivos DEX do aplicativo. 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ção else {}. Para saber mais, acesse a seção sobre otimização de código.

Ao criar a versão de lançamento do seu aplicativo, o R8 executa automaticamente, por padrão, as tarefas de tempo de compilação descritas acima. No entanto, é possível desativar algumas tarefas ou personalizar o comportamento do R8 por meio dos 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 aplicativo que você testou antes de publicar. Para ativar a redução, ofuscação e otimização, inclua o seguinte no seu arquivo build.gradle no projeto.

android {
        buildTypes {
            release {
                // Enables code shrinking, obfuscation, and optimization for only
                // your project's release build type.
                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 aplicativo, como as classes que servem como pontos de entrada para o código do aplicativo. Embora você possa modificar alguns desses 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 aplicativo. 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 próprias regras do ProGuard aqui, como suas regras keep personalizadas.

Plug-in do Android para 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, quando um novo módulo for criado usando o Android Studio, o arquivo build.gradle do módulo incluirá esse arquivo de regras na compilação de lançamento.

Observação: o Plug-in do Android para Gradle inclui outros arquivos de regras do ProGuard predefinidos, mas é recomendável que você use o proguard-android-optimize.txt.

Dependências de biblioteca Bibliotecas AAR: <library-dir>/proguard.txt

Bibliotecas JAR: <library-dir>/META-INF/proguard/

Se uma biblioteca AAR for publicada com o próprio arquivo de regras do ProGuard e você incluir essa AAR como uma dependência no tempo de compilação, o R8 aplicará automaticamente as regras dele ao compilar o projeto.

O uso de arquivos de regras empacotados com bibliotecas AAR é útil se determinadas regras keep forem necessárias para que a biblioteca funcione corretamente, ou seja, se o desenvolvedor da biblioteca tiver executado as etapas de solução de problemas para você.

No entanto, como as regras do ProGuard são aditivas, algumas regras incluídas na dependência da biblioteca AAR não podem ser removidas e podem afetar a compilação de outras partes do app. Por exemplo, se uma biblioteca incluir uma regra para desativar as otimizações de código, essa regra desativará as otimizações para todo o projeto.

Android Asset Packaging Tool 2 (AAPT2) Após compilar seu projeto com minifyEnabled true: <module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt O AAPT2 gera regras keep com base em referências a classes no manifesto, nos layouts e em outros recursos do aplicativo. Por exemplo, o AAPT2 inclui uma regra keep para cada atividade registrada no manifesto do aplicativo como um ponto de entrada.
Arquivos de configuração personalizados Por padrão, quando você criar um novo módulo usando o Android Studio, o ambiente de desenvolvimento integrado criará <module-dir>/proguard-rules.pro para incluir suas próprias regras. Você pode incluir configurações adicionais, e o R8 as aplicará 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 compilar 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
    

Incluir configurações adicionais

Quando você criar um novo projeto ou módulo usando o Android Studio, o ambiente de desenvolvimento integrado criará um <module-dir>/proguard-rules.pro para incluir suas próprias regras. Você também pode incluir mais regras de outros arquivos, adicionando-as à propriedade proguardFiles no arquivo build.gradle 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 seguinte arquivo do Gradle 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.

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'
            }
        }
        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 ao definir 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 que não é necessário no momento da execução. Esse processo pode reduzir muito o tamanho do aplicativo se, por exemplo, ele incluir muitas dependências de biblioteca, mas utilizar apenas uma pequena parte da funcionalidade delas.

Para reduzir o código do aplicativo, o R8 primeiro determina todos os pontos de entrada no código 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 aplicativo. A partir de cada ponto de entrada, o R8 inspeciona o código do aplicativo para criar um gráfico de todos os métodos, variáveis de membros e outras classes que o aplicativo possa acessar no momento da 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 aplicativo 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.

Figura 1. No tempo de compilação, o R8 cria um gráfico com base nas regras keep combinadas do projeto para determinar o código inacessível.

O R8 determina pontos de entrada por meio de 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 aplicativo, acesse a seção sobre como reduzir os recursos.

Personalizar o código a ser mantido

Para a maior parte das situações, o arquivo de regras padrão do ProGuard (proguard-android- optimize.txt) é suficiente para que o R8 remova somente 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 aplicativo precisa. Alguns dos exemplos de quando códigos podem ser removidos incorretamente incluem:

  • Quando seu aplicativo chama um método da Java Native Interface (JNI)
  • Quando seu aplicativo procura código no momento da execução (por exemplo, com reflexão)

Os testes do aplicativo 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 for removido (links em inglês).

Reduzir recursos

A redução de recurso funciona apenas em conjunto com a redução de código. Depois que o redutor de código remover todos os códigos não utilizados, o redutor de recursos poderá identificar quais recursos ainda são usados pelo aplicativo. 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 arquivo build.gradle (junto com minifyEnabled para a redução de código). Exemplo:

android {
        ...
        buildTypes {
            release {
                shrinkResources true
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'),
                        'proguard-rules.pro'
            }
        }
    }
    

Caso ainda não tenha compilado o app usando minifyEnabled para reduzir o código, tente fazer isso antes de ativar shrinkResources, porque pode ser necessário editar o arquivo proguard-rules.pro para manter 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/keep.xml. A compilação não empacota esse arquivo no APK.

Pode parecer bobagem especificar quais recursos descartar quando se poderia simplesmente exclui-los, mas essa ferramenta pode ser útil ao usar variantes de compilação. Por exemplo, é possível colocar todos os seus recursos no diretório comum do projeto e criar um arquivo keep.xml diferente para cada variante de compilação quando um recurso for aparentemente usado no código (e, portanto, não foi removido pelo redutor), mas você sabe que esse recurso não será usado pela variante de compilação em questão. Também é possível que as ferramentas de compilação identifiquem incorretamente um recurso como necessário. Isso pode acontecer porque o compilador adiciona os IDs de recursos in-line e o analisador de recursos pode desconhecer a diferença entre um recurso realmente referenciado e um valor inteiro no código que tem acidentalmente 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 por URLs de recursos com formato semelhante a file:///android_res/drawable//ic_plus_anim_016.png. Se ele encontrar strings como essa ou outras que pareçam poder ser usadas para construir URLs como esse, elas não serã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 fizer referência 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 aplicativo, 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 a AppCompat ou a Google Play Services), seu APK conterá todas as strings de idiomas traduzidas para as mensagens nessas bibliotecas, independentemente do restante do seu aplicativo estar traduzido ou não para os mesmos idiomas. Se quiser manter apenas os idiomas com os quais o app é compatível oficialmente, use a propriedade resConfig para especificá-los. 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.

android {
        defaultConfig {
            ...
            resConfigs "en", "fr"
        }
    }
    

Da mesma forma, você pode personalizar a densidade da tela ou os recursos de ABI que serão incluídos no seu APK criando vários APKs voltados para diferentes configurações de dispositivos.

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 passa apenas esse recurso ao AAPT para distribuição no arquivo do APK.

O Gradle procura por recursos duplicados nos seguintes locais:

  • Nos recursos principais associados ao conjunto de origem principal, normalmente localizados em src/main/res/.
  • Nas sobreposições de variante, 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 compilação

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, 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 aplicativo 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 aplicativo, economias significativas de tamanho podem ser vistas em aplicativos com arquivos DEX que indexam muitas classes, métodos e campos. No entanto, como a ofuscação renomeia diferentes partes do seu código, algumas tarefas, como a inspeção de rastreamentos de pilha, exigem outras ferramentas. Para entender o stack trace após a ofuscação, leia a próxima 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 aplicativo (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 aplicativo como também a manter a nomenclatura original.

Decodificar e ofuscar o rastreamento de pilha

Depois que o R8 ofusca seu código, é difícil (se não impossível) entender um rastreamento de pilha, porque os nomes de classes e métodos podem ter sido alterados. Além de renomear, o R8 também pode alterar os números de linha presentes nos rastreamentos de pilha para conseguir mais economia de tamanho ao escrever os arquivos DEX. Felizmente, o R8 cria um arquivo mapping.txt sempre que é executado, contendo os nomes de classes, métodos e campos ofuscados mapeados para os nomes originais. Esse arquivo de mapeamento também contém informações para mapear os números de linha de volta para os números originais de linha do arquivo de origem. O R8 salva o arquivo no diretório <module- name>/build/outputs/mapping/<build-type>/.

Quando publicar o aplicativo no Google Play, você poderá fazer o upload do arquivo mapping.txt para cada versão do APK. O Google Play desofuscará os novos stack traces de problemas informados pelos usuários para que você possa analisá-los no Google Play Console. Para saber mais, consulte o artigo da Central de Ajuda sobre como desofuscar stack traces com falhas.

Para converter um stack trace ofuscado em um que você possa entender, use o script ReTrace que faz parte do pacote do ProGuard (links em inglês).

Otimização de código

Para reduzir ainda mais seu aplicativo, o R8 inspeciona o código de forma mais profunda para remover mais códigos não utilizados ou, quando possível, reescrever o código para torná-lo menos detalhado. Veja a seguir alguns exemplos dessas otimizações:

  • Se seu código nunca receber a ramificação else {} de determinada instrução "if/else", o R8 poderá remover o código da ramificação .
  • Se seu código chamar um método em apenas um local, o R8 poderá remover o método e incorporá-lo no site de chamada única.
  • 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 aplicativo.
  • 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 as 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.

Ativar otimizações mais agressivas

O R8 inclui um conjunto de outras otimizações que não estão ativadas por padrão. Para ativar essas outras otimizações, inclua o seguinte no arquivo gradle.properties do projeto:

android.enableR8.fullMode=true
    

Como as otimizações extras fazem o R8 se comportar de maneira diferente do ProGuard, elas podem exigir que você inclua mais regras do ProGuard para evitar problemas de tempo de execução. Por exemplo, digamos que seu código referencie uma classe por meio da API Java Reflection. Por padrão, o R8 considera que você pretende examinar e manipular objetos dessa classe no momento da execução, mesmo que o código não faça isso, e mantém automaticamente 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 declarar que seu código nunca usa a classe no momento da execução, ele removerá a classe do DEX final do aplicativo. Ou seja, se você quiser manter a classe e o inicializador estático dela, precisará incluir uma regra keep no arquivo de regras.

Se houver algum problema ao usar o "modo completo" do R8, consulte a página de perguntas frequentes do R8 (link em inglês) para encontrar uma possível solução. Caso não consiga resolver o problema, informe um bug (link em inglês).

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 do qual 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 APK. 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: Binary 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 APK, 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] &#64;drawable/add_schedule_fab_icon_anim : reachable=true
    16:25:48.009 [QUIET] [system.out]     &#64;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 da compilação 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.