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. Ao ativar a redução, você também se beneficia com a ofuscação, que encurta os nomes das classes e dos membros do aplicativo, e com a otimização, que aplica estratégias mais agressivas para reduzir ainda mais o tamanho do aplicativo. Esta página descreve como o R8 executa essas tarefas de tempo de compilação para seu projeto e como você pode 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 de código em tempo de compilação. Em vez disso, o plug-in usa o compilador R8 para lidar com 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 aplicativo e das dependências de biblioteca dele (o que faz dela uma ferramenta valiosa para superar o limite de 64 K referências). Por exemplo, se você usar apenas algumas APIs de uma dependência de biblioteca, a redução poderá identificar o código da biblioteca que seu aplicativo não está usando e remover apenas ele do aplicativo. Para saber mais, acesse a seção sobre como reduzir o 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, acesse 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 o branch else {} de determinada instrução "if/else" nunca é usado, ele removerá o código do branch 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 posterior, 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, a ofuscação e a otimização, inclua o seguinte no arquivo build.gradle do 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 IDE cria um arquivo proguard-rules.pro no diretório raiz do 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 em tempo de compilação. O plug-in do Android para Gradle gera o proguard-android-optimize.txt, que inclui regras úteis para a maioria dos projetos do Android e permite anotações @Keep*.

Por padrão, ao criar um novo módulo usando o Android Studio, o arquivo build.gradle do módulo inclui 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 esse AAR como uma dependência em 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, esteja ciente de que, como as regras do ProGuard são aditivas, algumas regras que uma dependência de biblioteca AAR inclui não podem ser removidas e podem afetar a compilação de outras partes do seu aplicativo. Por exemplo, se uma biblioteca incluir uma regra para desativar as otimizações de código, essa regra desativará as otimizações do projeto inteiro.

Android Asset Packaging Tool 2 (AAPT2) Depois de criar 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ê cria um novo módulo usando o Android Studio, o IDE cria <module-dir>/proguard-rules.pro para você adicionar suas próprias regras. Você pode incluir configurações adicionais, e o R8 as aplicará em tempo de 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 solucionar problemas com o R8, porque outras dependências de tempo de compilação, como dependências de biblioteca, podem introduzir alterações 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ê cria um novo projeto ou módulo usando o Android Studio, o IDE cria um arquivo <module-dir>/proguard-rules.pro para incluir suas próprias regras. Também é possível incluir 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 adicionando outra propriedade proguardFiles no bloco productFlavor correspondente. O arquivo do Gradle a seguir adiciona flavor2-rules.pro à variável do produto flavor2. Agora, o flavor2 usa as três regras do ProGuard, porque as do bloco release também são aplicáveis.

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 quando você configura 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 tempo de 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 em tempo de execução. O código que não está conectado a esse gráfico é considerado inacessível e pode ser removido do aplicativo.

A Figura 1 mostra um aplicativo com uma dependência de biblioteca de tempo de execução. Ao inspecionar o código do aplicativo, o R8 determina que os métodos foo(), faz() e bar() podem ser acessados a partir do ponto de entrada MainActivity.class. No entanto, a classe OkayApi.class ou o método baz() nunca é usado pelo aplicativo em tempo de execução, então o R8 remove esse código ao reduzir o aplicativo.

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 o R8 não deve descartar ao reduzir seu aplicativo, e o R8 considera essas classes como possíveis pontos de entrada no aplicativo. O plug-in do Android para Gradle e o AAPT2 geram automaticamente as regras keep exigidas pela maioria dos projetos de aplicativo, como as atividades, as visualizações e os serviços do aplicativo. 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ê estiver interessado apenas em reduzir o tamanho dos recursos do aplicativo, vá para a seção sobre como reduzir os recursos.

Personalizar qual código manter

Na maioria das situações, o arquivo de regras padrão do ProGuard (proguard-android- optimize.txt) é suficiente para o R8 remover 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 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 em tempo de 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 os erros e forçar o R8 a manter determinado código, adicione uma linha -keep ao arquivo de regras do ProGuard. Por exemplo:

-keep public class MyClass
    

Como alternativa, você pode adicionar a anotação @Keep ao código que quer manter. A adição de @Keep a uma classe mantém a classe inteira como está. Adicioná-la a um método ou campo manterá intacto esse método ou campo (e o respectivo nome), além do nome da classe. Observe que essa anotação está disponível quando você estiver usando a Biblioteca de anotações do AndroidX 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 regras, leia o Manual do ProGuard (link em inglês). A seção Solução de problemas descreve outros problemas comuns que você pode encontrar quando seu código for removido.

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 (juntamente com minifyEnabled para a redução de código). Por exemplo:

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

Se você ainda não tiver compilado seu aplicativo usando minifyEnabled para reduzir o código, tente fazê-lo antes de ativar shrinkResources, porque pode ser necessário editar o arquivo proguard-rules.pro para manter classes ou métodos criados ou invocados dinamicamente 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 recursos a serem 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.

Por 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 códigos de recursos em linha 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 — a biblioteca AppCompat faz), isso significa que seu código está procurando nomes de recursos com base em strings geradas dinamicamente. 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 isso, defina shrinkMode como strict no arquivo keep.xml, da seguinte maneira:

    <?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 não usados pelo aplicativo.

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 aplicativo é oficialmente compatível, 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 a incluir 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 é necessário para evitar erros quando vários recursos têm o mesmo nome que o código está procurando.

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 duplicatas (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, localizados normalmente 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 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 pode 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 rastreamento de pilha após a ofuscação, leia a próxima seção sobre como decodificar um rastreamento de pilha 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 economias 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 para os números de linha do arquivo de origem original. O R8 salva o arquivo no diretório <module- name>/build/outputs/mapping/<build-type>/.

Quando publicar seu aplicativo no Google Play, você poderá fazer upload do arquivo mapping.txt para cada versão do APK. O Google Play desofuscará os rastreamentos de pilha recebidos de problemas reportados por usuários, assim você poderá analisá-los no Google Play Console. Para saber mais, consulte o artigo da Central de Ajuda sobre como desofuscar rastreamentos de pilha de falhas.

Para converter um rastreamento de pilha ofuscado em um que você possa entender, use o script ReTrace fornecido com o ProGuard.

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 o branch else {} de determinada instrução if/else, o R8 poderá remover o código do branch else {}.
  • 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 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. Você pode ativar essas otimizações incluindo 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 faça referência a uma classe por meio da API Java Reflection. Por padrão, o R8 presume que você pretende examinar e manipular objetos dessa classe em tempo de execução, mesmo que o código não faça isso, e mantém automaticamente a classe e o inicializador estático dela.

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 em tempo de 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. Se você não conseguir resolver o problema, informe um bug.

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 (os dois links estão em inglês).

Gerar um relatório de código removido (ou mantido)

Para ajudar a solucionar certos problemas do R8, pode ser útil ver um relatório de todo o código removido pelo R8 do seu aplicativo. Para cada módulo para o qual você quer gerar esse relatório, adicione -printusage <output-dir>/usage.txt ao arquivo de regras personalizado. Quando você ativa o R8 e cria seu aplicativo, 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 personalizado. Quando você ativa o R8 e cria seu aplicativo, 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. Por 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 contém detalhes como quais recursos fazem referência a outros recursos 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 pode ser alcançado. Se você olhar 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 código R.drawable dele foi encontrado em código alcançá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 você vir uma dessas strings e estiver certo de que ela não está sendo usada para carregar dinamicamente o recurso em questão, 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 manter.