Уменьшите, запутайте и оптимизируйте свое приложение

Чтобы сделать ваше приложение максимально маленьким и быстрым, вам следует оптимизировать и минимизировать сборку выпуска с помощью isMinifyEnabled = true .

Это позволит сжать , что удалит неиспользуемый код; обфускация , которая сокращает имена классов и членов вашего приложения; и оптимизация , которая применяет улучшенные стратегии оптимизации кода для дальнейшего уменьшения размера и повышения производительности вашего приложения. На этой странице описывается, как R8 выполняет эти задачи времени компиляции вашего проекта и как вы можете их настроить.

Когда вы создаете проект с помощью плагина Android Gradle 3.4.0 или выше, плагин больше не использует ProGuard для оптимизации кода во время компиляции. Вместо этого плагин работает с компилятором R8 для выполнения следующих задач времени компиляции:

  • Сжатие кода (или встряхивание дерева): обнаруживает и безопасно удаляет неиспользуемые классы, поля, методы и атрибуты из вашего приложения и его зависимостей библиотеки (что делает его ценным инструментом для обхода ограничения ссылок в 64 КБ ). Например, если вы используете только несколько API-интерфейсов зависимости библиотеки, сжатие может определить код библиотеки, который не использует ваше приложение, и удалить из приложения только этот код. Чтобы узнать больше, перейдите в раздел о том, как уменьшить код .
  • Сжатие ресурсов: удаляет неиспользуемые ресурсы из вашего упакованного приложения, включая неиспользуемые ресурсы в зависимостях библиотеки вашего приложения. Он работает в сочетании с сокращением кода, так что после удаления неиспользуемого кода любые ресурсы, на которые больше нет ссылок, также могут быть безопасно удалены. Чтобы узнать больше, перейдите в раздел о том, как сократить ресурсы .
  • Оптимизация: проверяет и переписывает ваш код, чтобы повысить производительность во время выполнения и еще больше уменьшить размер файлов DEX вашего приложения. Это повышает производительность кода во время выполнения до 30 %, значительно улучшая запуск и синхронизацию кадров. Например, если R8 обнаруживает, что ветвь else {} для данного оператора if/else никогда не используется, R8 удаляет код для ветви else {} . Чтобы узнать больше, перейдите в раздел об оптимизации кода .
  • Обфускация (или минимизация идентификатора): сокращает имена классов и членов, что приводит к уменьшению размеров файлов DEX. Чтобы узнать больше, перейдите в раздел о том, как запутать ваш код .

При создании релизной версии вашего приложения R8 можно настроить для выполнения за вас задач времени компиляции, описанных выше. Вы также можете отключить определенные задачи или настроить поведение R8 с помощью файлов правил ProGuard. Фактически, R8 работает со всеми существующими файлами правил ProGuard , поэтому обновление плагина Android Gradle для использования R8 не должно требовать от вас изменения существующих правил.

Включите сжатие, запутывание и оптимизацию.

Когда вы используете Android Studio 3.4 или плагин Android Gradle 3.4.0 и выше, R8 является компилятором по умолчанию, который преобразует байт-код Java вашего проекта в формат DEX, который работает на платформе Android. Однако при создании нового проекта с помощью Android Studio сжатие, обфускация и оптимизация кода по умолчанию не включены. Это связано с тем, что такая оптимизация времени компиляции увеличивает время сборки вашего проекта и может привести к появлению ошибок, если вы недостаточно настроите код, который следует сохранить .

Поэтому лучше всего включить эти задачи времени компиляции при создании окончательной версии вашего приложения, которую вы тестируете перед публикацией. Чтобы включить сжатие, обфускацию и оптимизацию, включите следующее в сценарий сборки на уровне проекта.

Котлин

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"
            )
        }
    }
    ...
}

классный

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'
        }
    }
    ...
}

Конфигурационные файлы R8

R8 использует файлы правил ProGuard для изменения поведения по умолчанию и лучшего понимания структуры вашего приложения, например классов, которые служат точками входа в код вашего приложения. Хотя вы можете изменить некоторые из этих файлов правил, некоторые правила могут быть созданы автоматически с помощью инструментов времени компиляции, таких как AAPT2, или унаследованы от зависимостей библиотеки вашего приложения. В таблице ниже описаны источники файлов правил ProGuard, которые использует R8.

Источник Расположение Описание
Android-студия <module-dir>/proguard-rules.pro Когда вы создаете новый модуль с помощью Android Studio, IDE создает файл proguard-rules.pro в корневом каталоге этого модуля.

По умолчанию к этому файлу не применяются никакие правила. Поэтому добавьте сюда свои собственные правила ProGuard, например собственные правила хранения .

Плагин Android Gradle Создается плагином Android Gradle во время компиляции. Плагин Android Gradle генерирует proguard-android-optimize.txt , который включает правила, полезные для большинства проектов Android, и включает аннотации @Keep* .

По умолчанию при создании нового модуля с помощью Android Studio сценарий сборки на уровне модуля включает этот файл правил в вашу сборку выпуска.

Примечание. Плагин Android Gradle включает дополнительные предопределенные файлы правил ProGuard, но рекомендуется использовать proguard-android-optimize.txt .

Зависимости библиотеки

В библиотеке AAR:
proguard.txt

В JAR-библиотеке:
META-INF/proguard/<ProGuard-rules-file>

В дополнение к этим местоположениям плагин Android Gradle 3.6 или выше также поддерживает целевые правила сжатия .

Если библиотека AAR или JAR публикуется с собственным файлом правил, и вы включаете эту библиотеку в качестве зависимости времени компиляции, R8 автоматически применяет эти правила при компиляции вашего проекта.

В дополнение к обычным правилам ProGuard плагин Android Gradle 3.6 или выше также поддерживает целевые правила сжатия . Это правила, предназначенные для конкретных термоусадочных устройств (R8 или ProGuard), а также для конкретных версий термоусадочных устройств.

Использование файлов правил, упакованных с библиотеками, полезно, если для правильной работы библиотеки требуются определенные правила, то есть разработчик библиотеки выполнил за вас действия по устранению неполадок.

Однако вы должны знать, что, поскольку правила являются аддитивными , некоторые правила, которые включает в себя зависимость библиотеки, не могут быть удалены и могут повлиять на компиляцию других частей вашего приложения. Например, если библиотека включает правило отключения оптимизации кода, это правило отключает оптимизацию для всего вашего проекта.

Инструмент пакета ресурсов Android 2 (AAPT2) После сборки проекта с помощью minifyEnabled true : <module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt AAPT2 генерирует правила сохранения на основе ссылок на классы в манифесте вашего приложения, макетах и ​​других ресурсах приложения. Например, AAPT2 включает правило сохранения для каждого действия, которое вы регистрируете в манифесте вашего приложения в качестве точки входа.
Пользовательские файлы конфигурации По умолчанию, когда вы создаете новый модуль с помощью Android Studio, IDE создает <module-dir>/proguard-rules.pro чтобы вы могли добавить свои собственные правила. Вы можете включить дополнительные конфигурации , и R8 применит их во время компиляции.

Когда вы устанавливаете для свойства minifyEnabled значение true , R8 объединяет правила из всех доступных источников, перечисленных выше. Это важно помнить при устранении неполадок с помощью R8 , поскольку другие зависимости времени компиляции, например зависимости библиотек, могут вносить изменения в поведение R8, о которых вы не знаете.

Чтобы вывести полный отчет обо всех правилах, которые R8 применяет при создании вашего проекта, включите следующее в файл proguard-rules.pro вашего модуля:

// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt

Правила целевого сжатия

Плагин Android Gradle 3.6 или более поздней версии поддерживает правила библиотек, ориентированные на конкретные программы сжатия (R8 или ProGuard), а также на определенные версии программы сжатия. Это позволяет разработчикам библиотек адаптировать свои правила для оптимальной работы в проектах, использующих новые версии сжатия, позволяя при этом продолжать использовать существующие правила в проектах со старыми версиями сжатия.

Чтобы указать целевые правила сжатия, разработчикам библиотек необходимо будет включить их в определенные места внутри библиотеки AAR или JAR, как описано ниже.

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>

Это означает, что целевые правила сжатия хранятся в каталоге META-INF/com.android.tools файла JAR или в каталоге META-INF/com.android.tools внутри classes.jar файла AAR.

В этом каталоге может быть несколько каталогов с именами в форме r8-from-<X>-upto-<Y> или proguard-from-<X>-upto-<Y> чтобы указать, для каких версий какого сжатия написаны правила внутри каталогов. Обратите внимание, что части -from-<X> и -upto-<Y> являются необязательными, версия <Y> является эксклюзивной , а диапазоны версий должны быть непрерывными.

Например, r8-upto-8.0.0 , r8-from-8.0.0-upto-8.2.0 и r8-from-8.2.0 образуют действительный набор целевых правил сжатия. Правила в каталоге r8-from-8.0.0-upto-8.2.0 будут использоваться R8 начиная с версии 8.0.0 до версии 8.2.0, но не включая ее.

Учитывая эту информацию, плагин Android Gradle 3.6 или выше выберет правила из соответствующих каталогов R8. Если в библиотеке не указаны целевые правила сжатия, подключаемый модуль Android Gradle выберет правила из устаревших расположений ( proguard.txt для AAR или META-INF/proguard/<ProGuard-rules-file> для JAR).

Разработчики библиотек могут включить в свои библиотеки либо целевые правила сжатия, либо устаревшие правила ProGuard, либо оба типа, если они хотят сохранить совместимость с плагином Android Gradle старше 3.6 или другими инструментами.

Включить дополнительные конфигурации

Когда вы создаете новый проект или модуль с помощью Android Studio, IDE создает файл <module-dir>/proguard-rules.pro в который вы можете включить свои собственные правила. Вы также можете включить дополнительные правила из других файлов, добавив их в свойство proguardFiles в скрипте сборки вашего модуля.

Например, вы можете добавить правила, специфичные для каждого варианта сборки, добавив еще одно свойство proguardFiles в соответствующий блок productFlavor . Следующий файл Gradle добавляет файл flavor2-rules.pro к аромату продукта flavor2 . Теперь flavor2 использует все три правила ProGuard, поскольку применяются также правила из блока release .

Кроме того, вы можете добавить свойство testProguardFiles , которое определяет список файлов ProGuard, включенных только в тестовый APK:

Котлин

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")
        }
    }
}

классный

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'
        }
    }
}

Уменьшите свой код

Сжатие кода с помощью R8 включено по умолчанию, если для свойства minifyEnabled установлено значение true .

Сжатие кода (также известное как встряхивание дерева) — это процесс удаления кода, который, по мнению R8, не требуется во время выполнения. Этот процесс может значительно уменьшить размер вашего приложения, если, например, ваше приложение включает в себя множество зависимостей библиотек, но использует лишь небольшую часть их функциональности.

Чтобы сократить код вашего приложения, R8 сначала определяет все точки входа в код вашего приложения на основе объединенного набора файлов конфигурации . Эти точки входа включают все классы, которые платформа Android может использовать для открытия действий или служб вашего приложения. Начиная с каждой точки входа, R8 проверяет код вашего приложения, чтобы построить граф всех методов, переменных-членов и других классов, к которым ваше приложение может получить доступ во время выполнения. Код, не связанный с этим графом, считается недоступным и может быть удален из приложения.

На рис. 1 показано приложение с зависимостью от библиотеки времени выполнения. Проверяя код приложения, R8 определяет, что методы foo() , faz() и bar() доступны из точки входа MainActivity.class . Однако класс OkayApi.class или его метод baz() никогда не используются вашим приложением во время выполнения, и R8 удаляет этот код при сжатии вашего приложения.

Рисунок 1. Во время компиляции R8 строит график на основе комбинированных правил сохранения вашего проекта для определения недостижимого кода.

R8 определяет точки входа с помощью правил -keep в файлах конфигурации R8 проекта. То есть правила сохранения определяют классы, которые R8 не должен отбрасывать при сжатии вашего приложения, и R8 рассматривает эти классы как возможные точки входа в ваше приложение. Плагин Android Gradle и AAPT2 автоматически генерируют правила сохранения, необходимые для большинства проектов приложений, например действия, представления и службы вашего приложения. Однако если вам нужно настроить это поведение по умолчанию с помощью дополнительных правил сохранения, прочитайте раздел о том, как настроить, какой код сохранять .

Если вместо этого вы заинтересованы только в уменьшении размера ресурсов вашего приложения, перейдите к разделу о том, как сократить ресурсы .

Обратите внимание: если проект библиотеки сжат, приложение, зависящее от этой библиотеки, включает в себя сжатые библиотечные классы. Возможно, вам придется изменить правила хранения библиотеки, если в APK библиотеки отсутствуют классы. Если вы создаете и публикуете библиотеку в формате AAR, локальные файлы JAR, от которых зависит ваша библиотека , не сжимаются в файле AAR.

Настройте, какой код сохранить

В большинстве ситуаций файла правил ProGuard по умолчанию ( proguard-android-optimize.txt ) достаточно, чтобы R8 удалил только неиспользуемый код. Однако в некоторых ситуациях R8 сложно правильно проанализировать, и он может удалить код, который действительно нужен вашему приложению. Вот некоторые примеры случаев, когда код может быть неправильно удален:

  • Когда ваше приложение вызывает метод из собственного интерфейса Java (JNI)
  • Когда ваше приложение ищет код во время выполнения (например, с помощью отражения)

Тестирование вашего приложения должно выявить любые ошибки, вызванные ненадлежащим удалением кода, но вы также можете проверить, какой код был удален, создав отчет об удаленном коде .

Чтобы исправить ошибки и заставить R8 сохранять определенный код, добавьте строку -keep в файл правил ProGuard. Например:

-keep public class MyClass

Альтернативно вы можете добавить аннотацию @Keep к коду, который хотите сохранить. Добавление @Keep в класс сохраняет весь класс как есть. Добавление его в метод или поле сохранит нетронутым метод/поле (и его имя), а также имя класса. Обратите внимание, что эта аннотация доступна только при использовании библиотеки аннотаций AndroidX и при включении файла правил ProGuard, который упакован с подключаемым модулем Android Gradle, как описано в разделе о том, как включить сжатие .

При использовании опции -keep следует учитывать множество факторов; Для получения дополнительной информации о настройке файла правил прочтите руководство ProGuard . В разделе «Устранение неполадок» описаны другие распространенные проблемы, с которыми вы можете столкнуться при удалении кода.

Удаление родных библиотек

По умолчанию библиотеки собственного кода удаляются из сборок выпуска вашего приложения. Это удаление состоит из удаления таблицы символов и отладочной информации, содержащейся во всех собственных библиотеках, используемых вашим приложением. Удаление библиотек собственного кода приводит к значительной экономии размера; однако невозможно диагностировать сбои в консоли Google Play из-за отсутствия информации (например, имен классов и функций).

Встроенная поддержка сбоев

Консоль Google Play сообщает о собственных сбоях в Android Vitals . Выполнив несколько шагов, вы можете создать и загрузить собственный файл символов отладки для своего приложения. Этот файл позволяет отображать собственные трассировки стека сбоев (которые включают имена классов и функций) в Android Vitals, чтобы помочь вам отладить ваше приложение в рабочей среде. Эти шаги различаются в зависимости от версии плагина Android Gradle, используемой в вашем проекте, и результатов сборки вашего проекта.

Плагин Android Gradle версии 4.1 или новее

Если в вашем проекте создается пакет Android App Bundle, вы можете автоматически включить в него собственный файл символов отладки. Чтобы включить этот файл в сборки выпуска, добавьте следующее в файл build.gradle.kts вашего приложения:

android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }

Выберите уровень символа отладки из следующих:

  • Используйте SYMBOL_TABLE , чтобы получить имена функций в символьных трассировках стека Play Console. Этот уровень поддерживает надгробия .
  • Используйте FULL , чтобы получить имена функций, файлы и номера строк в символизированных трассировках стека Play Console.

Если ваш проект создает APK, используйте параметр сборки build.gradle.kts показанный ранее, чтобы отдельно сгенерировать собственный файл символов отладки. Вручную загрузите файл собственных символов отладки в консоль Google Play. В рамках процесса сборки плагин Android Gradle выводит этот файл в следующую папку проекта:

app/build/outputs/native-debug-symbols/ variant-name /native-debug-symbols.zip

Плагин Android Gradle версии 4.0 или более ранней (и другие системы сборки)

В рамках процесса сборки плагин Android Gradle сохраняет копии неубранных библиотек в каталоге проекта. Эта структура каталогов аналогична следующей:

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
  1. Заархивируйте содержимое этого каталога:

    cd app/build/intermediates/cmake/universal/release/obj
    zip -r symbols.zip .
    
  2. Вручную загрузите файл symbols.zip в консоль Google Play.

Сократите свои ресурсы

Сокращение ресурсов работает только в сочетании с сокращением кода. После того как программа сжатия кода удалит весь неиспользуемый код, программа сжатия ресурсов сможет определить, какие ресурсы приложение все еще использует. Это особенно актуально при добавлении библиотек кода, включающих ресурсы: необходимо удалить неиспользуемый код библиотеки, чтобы на ресурсы библиотеки не было ссылок и, следовательно, их можно было удалить с помощью средства сжатия ресурсов.

Чтобы включить сжатие ресурсов, установите для свойства shrinkResources значение true в сценарии сборки (наряду с minifyEnabled для сжатия кода). Например:

Котлин

android {
    ...
    buildTypes {
        getByName("release") {
            isShrinkResources = true
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

классный

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

Если вы еще не создали свое приложение с использованием minifyEnabled для сжатия кода, попробуйте это перед включением shrinkResources , поскольку вам может потребоваться отредактировать файл proguard-rules.pro , чтобы сохранить классы или методы, которые создаются или вызываются динамически, прежде чем вы начнете удалять ресурсы.

Настройте, какие ресурсы следует сохранять

Если есть определенные ресурсы, которые вы хотите сохранить или удалить, создайте XML-файл в своем проекте с тегом <resources> и укажите каждый ресурс, который нужно сохранить, в атрибуте tools:keep и каждый ресурс, который нужно удалить, в атрибуте tools:discard . Оба атрибута принимают список имен ресурсов, разделенных запятыми. В качестве подстановочного знака можно использовать символ звездочки.

Например:

<?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" />

Сохраните этот файл в ресурсах вашего проекта, например, по адресу res/raw/my.package.keep.xml . Сборка не упаковывает этот файл в ваше приложение.

Примечание. Обязательно используйте уникальное имя для файла keep . Когда разные библиотеки связаны друг с другом, их правила хранения в противном случае будут конфликтовать, что приведет к потенциальным проблемам с игнорируемыми правилами или ненужными сохраненными ресурсами.

Указание того, какие ресурсы следует отбросить, может показаться глупым, если вместо этого вы можете удалить их, но это может быть полезно при использовании вариантов сборки. Например, вы можете поместить все свои ресурсы в общий каталог проекта, а затем создать отдельный файл my.package.build.variant.keep.xml для каждого варианта сборки, если вы знаете, что данный ресурс используется в коде (и, следовательно, не удаляется программой сжатия), но вы знаете, что на самом деле он не будет использоваться для данного варианта сборки. Также возможно, что инструменты сборки неправильно определили необходимый ресурс, что возможно, потому что компилятор добавляет идентификаторы ресурсов в строку, и тогда анализатор ресурсов может не узнать разницу между действительно указанным ресурсом и целочисленным значением в коде, которое имеет одно и то же значение.

Включить строгую проверку рекомендаций

Обычно программа сжатия ресурсов может точно определить, используется ли ресурс. Однако если ваш код вызывает Resources.getIdentifier() (или если это делает какая-либо из ваших библиотек — библиотека AppCompat ), это означает, что ваш код ищет имена ресурсов на основе динамически генерируемых строк. Когда вы это сделаете, средство сжатия ресурсов по умолчанию ведет себя оборонительно и помечает все ресурсы с соответствующим форматом имени как потенциально используемые и недоступные для удаления.

Например, следующий код помечает все ресурсы с префиксом img_ как используемые.

Котлин

val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)

Ява

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

Средство сжатия ресурсов также просматривает все строковые константы в вашем коде, а также различные ресурсы res/raw/ , ища URL-адреса ресурсов в формате, аналогичном file:///android_res/drawable//ic_plus_anim_016.png . Если он находит такие строки или другие, которые выглядят так, будто их можно использовать для создания подобных URL-адресов, он не удаляет их.

Это примеры безопасного режима сжатия, который включен по умолчанию. Однако вы можете отключить эту обработку «лучше перестраховаться, чем потом сожалеть» и указать, чтобы средство сжатия ресурсов сохраняло только те ресурсы, которые, по его мнению, используются. Для этого установите для shrinkMode значение strict в файле keep.xml следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:shrinkMode="strict" />

Если вы включили режим строгого сжатия и ваш код также ссылается на ресурсы с динамически генерируемыми строками, как показано выше, вам придется вручную сохранять эти ресурсы с помощью атрибута tools:keep .

Удалить неиспользуемые альтернативные ресурсы

Средство сжатия ресурсов Gradle удаляет только те ресурсы, на которые не ссылается код вашего приложения, что означает, что оно не будет удалять альтернативные ресурсы для разных конфигураций устройств. При необходимости вы можете использовать свойство resConfigs подключаемого модуля Android Gradle, чтобы удалить альтернативные файлы ресурсов, которые не нужны вашему приложению.

Например, если вы используете библиотеку, включающую языковые ресурсы (например, AppCompat или Google Play Services), тогда ваше приложение включает все переведенные языковые строки для сообщений в этих библиотеках, независимо от того, переведена ли остальная часть вашего приложения на те же языки или нет. Если вы хотите сохранить только те языки, которые официально поддерживает ваше приложение, вы можете указать эти языки с помощью свойства resConfig . Все ресурсы для неуказанных языков будут удалены.

В следующем фрагменте показано, как ограничить ваши языковые ресурсы только английским и французским:

Котлин

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

классный

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

При выпуске приложения в формате Android App Bundle при установке приложения по умолчанию загружаются только языки, настроенные на устройстве пользователя. Аналогично, в загрузку включены только ресурсы, соответствующие плотности экрана устройства, и собственные библиотеки, соответствующие ABI устройства. Дополнительную информацию см. в конфигурации Android App Bundle .

Для устаревших приложений, выпускаемых с APK-файлами (созданными до августа 2021 г.), вы можете настроить плотность экрана или ресурсы ABI, которые следует включать в APK, создав несколько APK-файлов , каждый из которых предназначен для разных конфигураций устройств.

Объединение повторяющихся ресурсов

По умолчанию Gradle также объединяет ресурсы с одинаковыми именами, например файлы с одинаковым именем, которые могут находиться в разных папках ресурсов. Это поведение не контролируется свойством shrinkResources и не может быть отключено, поскольку оно необходимо для предотвращения ошибок, когда несколько ресурсов соответствуют имени, которое ищет ваш код.

Объединение ресурсов происходит только в том случае, если два или более файлов имеют одинаковое имя, тип и квалификатор ресурса. Gradle выбирает, какой файл он считает лучшим выбором среди дубликатов (на основе порядка приоритета, описанного ниже), и передает только этот один ресурс в AAPT для распространения в конечном артефакте.

Gradle ищет дубликаты ресурсов в следующих местах:

  • Основные ресурсы, связанные с основным набором исходного кода, обычно расположены в src/main/res/ .
  • Варианты наложения зависят от типа сборки и вариантов сборки.
  • Зависимости проекта библиотеки.

Gradle объединяет повторяющиеся ресурсы в следующем порядке каскадного приоритета:

Зависимости → Главная → Вариант сборки → Тип сборки

Например, если дубликат ресурса появляется как в основных ресурсах, так и в версии сборки, Gradle выбирает тот, который присутствует в версии сборки.

Если идентичные ресурсы появляются в одном исходном наборе, Gradle не может их объединить и выдает ошибку объединения ресурсов. Это может произойти, если вы определите несколько исходных наборов в свойстве sourceSet вашего файла build.gradle.kts — например, если оба src/main/res/ и src/main/res2/ содержат идентичные ресурсы.

Запутайте свой код

Цель запутывания — уменьшить размер вашего приложения за счет сокращения имен его классов, методов и полей. Ниже приведен пример обфускации с использованием 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

Хотя обфускация не удаляет код из вашего приложения, значительную экономию размера можно увидеть в приложениях с файлами DEX, которые индексируют множество классов, методов и полей. Однако, поскольку обфускация переименовывает различные части вашего кода, некоторые задачи, такие как проверка трассировок стека, требуют дополнительных инструментов. Чтобы понять трассировку стека после обфускации, прочтите раздел о том, как декодировать запутанную трассировку стека .

Кроме того, если ваш код основан на предсказуемом именовании методов и классов вашего приложения (например, при использовании отражения), вы должны рассматривать эти сигнатуры как точки входа и указывать для них правила сохранения, как описано в разделе о том, как настроить, какой код сохранять . Эти правила сохранения предписывают R8 не только сохранять этот код в окончательном DEX вашего приложения, но и сохранять его исходное имя.

Декодирование запутанной трассировки стека

После того, как R8 запутывает ваш код, понять трассировку стека становится сложно (если не невозможно), поскольку имена классов и методов могли быть изменены. Чтобы получить исходную трассировку стека, вам следует повторить трассировку стека .

Оптимизация кода

Чтобы еще больше оптимизировать ваше приложение, R8 проверяет ваш код на более глубоком уровне, чтобы удалить больше неиспользуемого кода или, где это возможно, переписать ваш код, чтобы сделать его менее многословным. Ниже приведены несколько примеров такой оптимизации:

  • Если ваш код никогда не использует ветвь else {} для данного оператора if/else, R8 может удалить код для ветви else {} .
  • Если ваш код вызывает метод только в нескольких местах, R8 может удалить этот метод и встроить его в несколько мест вызова.
  • Если R8 определяет, что класс имеет только один уникальный подкласс, а сам класс не создан (например, абстрактный базовый класс используется только одним конкретным классом реализации), то R8 может объединить два класса и удалить класс из приложения.
  • Чтобы узнать больше, прочтите статьи Джейка Уортона в блоге об оптимизации R8 .

R8 не позволяет вам отключать или включать дискретные оптимизации или изменять поведение оптимизации. Фактически, R8 игнорирует любые правила ProGuard, которые пытаются изменить оптимизации по умолчанию, такие как -optimizations и -optimizationpasses . Это ограничение важно, потому что, поскольку R8 продолжает совершенствоваться, сохранение стандартного поведения при оптимизации помогает команде Android Studio легко устранять неполадки и решать любые проблемы, с которыми вы можете столкнуться.

Обратите внимание, что включение оптимизации приведет к изменению трассировки стека вашего приложения. Например, встраивание удалит кадры стека. См. раздел, посвященный повторной трассировке , чтобы узнать, как получить исходные трассировки стека.

Влияние на производительность во время выполнения

Если включены сжатие, обфускация и оптимизация, R8 улучшит производительность кода во время выполнения (включая запуск и время кадра в потоке пользовательского интерфейса) до 30%. Отключение любого из них резко ограничивает набор оптимизаций, которые использует R8.

Если R8 включен, вам также следует создать профили запуска для еще большей производительности при запуске.

Включить улучшенную оптимизацию

R8 включает в себя набор дополнительных оптимизаций (называемых «полным режимом»), которые отличают его поведение от ProGuard. Эти оптимизации включены по умолчанию, начиная с версии плагина Android Gradle 8.0.0 .

Вы можете отключить эти дополнительные оптимизации, включив в файл gradle.properties вашего проекта следующее:

android.enableR8.fullMode=false

Поскольку дополнительные оптимизации заставляют R8 вести себя иначе, чем ProGuard, они могут потребовать от вас включения дополнительных правил ProGuard, чтобы избежать проблем во время выполнения, если вы используете правила, разработанные для ProGuard. Например, предположим, что ваш код ссылается на класс через API Java Reflection. Если «полный режим» не используется, R8 предполагает, что вы намерены проверять объекты этого класса и манипулировать ими во время выполнения — даже если ваш код на самом деле этого не делает — и автоматически сохраняет класс и его статический инициализатор.

Однако при использовании «полного режима» R8 не делает этого предположения, и, если R8 утверждает, что иначе ваш код никогда не использует этот класс во время выполнения, он удаляет класс из окончательного DEX вашего приложения. То есть, если вы хотите сохранить класс и его статический инициализатор, для этого вам необходимо включить правило сохранения в файл правил.

Если у вас возникнут какие-либо проблемы при использовании «полного режима» R8, обратитесь к странице часто задаваемых вопросов R8 для возможного решения. Если вам не удалось решить проблему, сообщите об ошибке .

Отслеживание трассировки стека

Код, обрабатываемый R8, изменяется различными способами, что может затруднить понимание трассировок стека, поскольку трассировки стека не будут точно соответствовать исходному коду. Это может произойти при изменении номеров строк, когда отладочная информация не сохраняется. Это может быть связано с такими оптимизациями, как встраивание и структурирование. Самый большой вклад в это вносит запутывание, когда даже классы и методы меняют имена.

Для восстановления исходной трассировки стека R8 предоставляет инструмент командной строки retrace , который входит в состав пакета инструментов командной строки .

Чтобы поддерживать повторную трассировку стека вашего приложения, вы должны убедиться, что сборка сохраняет достаточно информации для повторной трассировки, добавив следующие правила в файл proguard-rules.pro вашего модуля:

-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile

Атрибут LineNumberTable сохраняет информацию о позиции в методах, так что эти позиции печатаются в трассировках стека. Атрибут SourceFile гарантирует, что все потенциальные среды выполнения действительно распечатают позиционную информацию. Директива -renamesourcefileattribute устанавливает имя исходного файла в трассировках стека просто SourceFile . Фактическое имя исходного исходного файла не требуется при повторной трассировке, поскольку файл сопоставления содержит исходный исходный файл.

R8 создает файл mapping.txt при каждом запуске, который содержит информацию, необходимую для сопоставления трассировок стека с исходными трассировками стека. Android Studio сохраняет файл в каталоге <module-name> /build/outputs/mapping/ <build-type> / .

Публикуя свое приложение в Google Play, вы можете загрузить файл mapping.txt для каждой версии вашего приложения. При публикации с использованием пакетов приложений Android этот файл автоматически включается в состав содержимого пакета приложений. Затем Google Play будет отслеживать входящие трассировки стека по проблемам, о которых сообщили пользователи, чтобы вы могли просмотреть их в Play Console. Дополнительную информацию см. в статье Справочного центра о том, как деобфусцировать трассировки стека сбоев .

Устранение неполадок с помощью R8

В этом разделе описаны некоторые стратегии устранения неполадок при включении сжатия, запутывания и оптимизации с помощью R8. Если вы не нашли решения своей проблемы ниже, прочтите также страницу часто задаваемых вопросов по R8 и руководство по устранению неполадок ProGuard .

Создать отчет об удаленном (или сохраненном) коде

Чтобы помочь вам устранить определенные проблемы с R8, может быть полезно просмотреть отчет обо всем коде, который R8 удалил из вашего приложения. Для каждого модуля, для которого вы хотите создать этот отчет, добавьте -printusage <output-dir>/usage.txt в файл пользовательских правил. Когда вы включаете R8 и создаете свое приложение, R8 выводит отчет с указанным вами путем и именем файла. Отчет об удаленном коде выглядит примерно так:

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
...

Если вместо этого вы хотите просмотреть отчет о точках входа, которые R8 определяет из правил сохранения вашего проекта, включите -printseeds <output-dir>/seeds.txt в свой файл пользовательских правил. Когда вы включите R8 и создаете свое приложение, R8 выводит отчет с указанным вами пути и именем файла. Отчет о поддержанных точках входа выглядит похоже на следующее:

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
...

Устранение неполадок с сокращением ресурсов

Когда вы сокращаете ресурсы, сборка Окно показывает резюме ресурсов, удаленных из приложения. (Вам нужно сначала щелкнуть переключатель На левой стороне окна отобразить подробный текст вывода из Градл.) Например:

:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning

Gradle также создает диагностический файл с именем resources.txt в <module-name>/build/outputs/mapping/release/ (та же папка, что и выходные файлы Proguard). Этот файл включает в себя детали, например, какие ресурсы ссылаются на другие ресурсы и какие ресурсы используются или удаляются.

Например, чтобы выяснить, почему @drawable/ic_plus_anim_016 все еще находится в вашем приложении, откройте файл resources.txt и выполните поиск этого имени файла. Вы можете обнаружить, что это ссылается на другой ресурс, следующим образом:

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

Теперь вам нужно знать, почему @drawable/add_schedule_fab_icon_anim доступен, и если вы ищете вверх, вы обнаружите, что ресурс перечислен в разделе «Корень, достижимые ресурсы:». Это означает, что существует ссылка на код на add_schedule_fab_icon_anim (то есть его R.Drawable ID был обнаружен в достижимом коде).

Если вы не используете строгую проверку, идентификаторы ресурсов могут быть помечены как достижимые, если есть строковые константы, которые выглядят так, как будто они могут использоваться для построения имен ресурсов для динамически загруженных ресурсов. В этом случае, если вы ищете вывод сборки для имени ресурса, вы можете найти подобное сообщение:

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.

Если вы видите одну из этих строк и уверены, что строка не используется для динамической загрузки данного ресурса, вы можете использовать tools:discard атрибут, чтобы информировать систему сборки для удаления его, как описано в разделе о том, как настроить, какие ресурсы сохранить .

,

Чтобы сделать ваше приложение максимально небольшим и быстрым, вы должны оптимизировать и минимизировать сборку выпуска с помощью isMinifyEnabled = true .

Это позволяет сокращать , что удаляет неиспользованный код; запутывание , которое сокращает имена классов и членов вашего приложения; и оптимизация , которая применяет улучшенные стратегии оптимизации кода для дальнейшего сокращения размера и повышения производительности вашего приложения. На этой странице описывается, как R8 выполняет эти задачи по времени компиляции для вашего проекта и как вы можете их настроить.

При создании своего проекта с помощью Android Gradle Plugin 3.4.0 или выше, плагин больше не использует Proguard для выполнения оптимизации кода компиляции. Вместо этого плагин работает с компилятором R8 для выполнения следующих задач по времени компиляции:

  • Сокращение кода (или устранение деревьев): обнаруживает и безопасно удаляет неиспользованные классы, поля, методы и атрибуты из вашего приложения и его библиотечные зависимости (что делает его ценным инструментом для работы вокруг эталонного предела 64K ). Например, если вы используете только несколько API библиотечной зависимости, сокращение может идентифицировать код библиотеки, что ваше приложение не использует и не удаляет только этот код из вашего приложения. Чтобы узнать больше, перейдите в раздел о том, как сократить свой код .
  • Сокращение ресурсов: удаляет неиспользованные ресурсы из вашего упакованного приложения, включая неиспользованные ресурсы в библиотечных зависимостях вашего приложения. Он работает в сочетании с сокращением кода, так что после удаления неиспользованного кода любые ресурсы, которые больше не упоминаются, также могут быть безопасно удалены. Чтобы узнать больше, перейдите в раздел о том, как сократить свои ресурсы .
  • Оптимизация: осматривает и переписывает ваш код, чтобы повысить производительность выполнения и еще больше уменьшить размер файлов DEX вашего приложения. Это улучшает производительность времени выполнения кода до 30%, что значительно улучшило время запуска и время кадров. Например, если R8 обнаруживает, что ветвь else {} для данного оператора if/else никогда не принимается, R8 удаляет код для else {} ветви. Чтобы узнать больше, перейдите в раздел об оптимизации кода .
  • Запутывание (или министерство идентификатора): сокращает имя классов и участников, что приводит к уменьшению размеров файлов DEX. Чтобы узнать больше, перейдите в раздел о том, как запутать свой код .

При создании версии вашего приложения R8 можно настроить для выполнения задач времени компиляции, описанных выше для вас. Вы также можете отключить определенные задачи или настроить поведение R8 через файлы правил прогноза. На самом деле, R8 работает со всеми вашими существующими файлами правил прогика , поэтому обновление плагина Android Gradle для использования R8 не должно потребовать, чтобы вы изменили свои существующие правила.

Включить сокращение, запутывание и оптимизация

Когда вы используете Android Studio 3.4 или Android Gradle Plugin 3.4.0 и выше, R8 является компилятором по умолчанию, который преобразует байт -код Java вашего проекта в формат DEX, который работает на платформе Android. Однако, когда вы создаете новый проект, используя Android Studio, сокращение, запутывание и оптимизация кода не включена по умолчанию. Это связано с тем, что эти оптимизации времени компиляции увеличивают время настройки вашего проекта и могут представить ошибки, если вы недостаточно настраиваете, какой код сохранить .

Таким образом, лучше всего включить эти задачи по времени компиляции при создании окончательной версии вашего приложения, которую вы тестируете перед публикацией. Чтобы включить сокращение, запутывание и оптимизацию, включите следующее в сценарии сборки на уровне проекта.

Котлин

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"
            )
        }
    }
    ...
}

классный

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'
        }
    }
    ...
}

R8 Файлы конфигурации

R8 использует файлы Proguard Rules для изменения своего поведения по умолчанию и лучше понять структуру вашего приложения, например, классы, которые служат точками входа в код вашего приложения. Несмотря на то, что вы можете изменить некоторые из этих файлов правил, некоторые правила могут генерироваться автоматически с помощью инструментов времени компиляции, таких как AAPT2 или унаследовать от зависимостей библиотеки вашего приложения. В таблице ниже описываются источники файлов правил прогноза, которые использует R8.

Источник Расположение Описание
Android Studio <module-dir>/proguard-rules.pro Когда вы создаете новый модуль с использованием Android Studio, IDE создает файл proguard-rules.pro в корневом каталоге этого модуля.

По умолчанию этот файл не применяет никаких правил. Итак, включите здесь свои собственные правила прогноза, такие как ваши правила Custom Hoe .

Плагин Android Gradle Генерируется плагином Android Gradle во время компиляции. Плагин Android Gradle генерирует proguard-android-optimize.txt , который включает в себя правила, которые полезны для большинства проектов Android и позволяет аннотациям @Keep* .

По умолчанию при создании нового модуля с использованием Android Studio скрипт на уровне модуля включает в себя этот файл правил в вашей сборке релиза для вас.

ПРИМЕЧАНИЕ. Плагин Android Gradle включает в себя дополнительные предопределенные файлы правил прогноза, но рекомендуется использовать proguard-android-optimize.txt .

Библиотечные зависимости

В библиотеке AAR:
proguard.txt

В библиотеке банки:
META-INF/proguard/<ProGuard-rules-file>

В дополнение к этим местам, плагин Android Gradle 3.6 или выше также поддерживает целевые правила сокращения .

Если библиотека AAR или JAR публикуется с его собственным файлом правил, и вы включаете эту библиотеку в качестве зависимости во время компиляции, R8 автоматически применяет эти правила при составлении вашего проекта.

В дополнение к традиционным правилам прогноза, плагин Android Gradle 3.6 или выше также поддерживает целевые правила сокращения . Это правила, которые нацелены на конкретные усадки (R8 или прогиар), а также конкретные версии усадки.

Использование файлов правил, которые упакованы с библиотеками, полезно, если необходимы определенные правила, чтобы библиотека функционировала должным образом, то есть разработчик библиотеки выполнил шаги по устранению неполадок для вас.

Тем не менее, вы должны знать, что, поскольку правила являются аддитивными , определенные правила, которые включает библиотечную зависимость, не могут быть удалены и могут повлиять на компиляцию других частей вашего приложения. Например, если библиотека включает правило отключения оптимизации кода, это правило отключает оптимизацию для всего вашего проекта.

Инструмент Android Asset Package 2 (AAPT2) После создания вашего проекта с помощью minifyEnabled true : <module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt AAPT2 генерирует правила на основе ссылок на классы в манерах, макетах вашего приложения и других ресурсах приложения. Например, AAPT2 включает правило сохранения для каждого действия, которое вы регистрируете в манифесте вашего приложения в качестве точки входа.
Пользовательские файлы конфигурации По умолчанию, когда вы создаете новый модуль с использованием Android Studio, IDE создает <module-dir>/proguard-rules.pro чтобы вы могли добавить свои собственные правила. Вы можете включить дополнительные конфигурации , а R8 применяет их во время компиляции.

Когда вы устанавливаете свойство minifyEnabled на true , R8 объединяет правила из всех доступных источников, перечисленных выше. Это важно помнить, когда вы устраняете устранение R8 , потому что другие зависимости от времени компиляции, такие как библиотечные зависимости, могут вводить изменения в поведение R8, о которых вы не знаете.

Чтобы вывести полный отчет обо всех правилах, которые применяется R8 при создании вашего проекта, включите следующее в файл proguard-rules.pro вашего модуля.

// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt

Целевые правила сокращения

Плагин Android Gradle 3.6 или более высокие правила библиотеки поддерживают, которые нацелены на конкретные сокращения (R8 или прогнор), а также конкретные версии усадки. Это позволяет разработчикам библиотеки адаптировать свои правила оптимально работать в проектах, которые используют новые версии усаживания, в то же время позволяя существующим правилам продолжать использоваться в проектах со старыми версиями усадки.

Чтобы указать целевые правила сокращения, разработчики библиотеки должны будут включать их в определенные места в библиотеке AAR или JAR, как описано ниже.

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>

Это означает, что целевые правила сокращения хранятся в каталоге META-INF/com.android.tools JAR или в каталоге META-INF/com.android.tools внутри classes.jar Яр AAR.

В этом каталоге может быть несколько каталогов с именами в форме r8-from-<X>-upto-<Y> или proguard-from-<X>-upto-<Y> чтобы указать, какие версии, которые сжимают правила внутри каталогов. Обратите внимание, что части -from-<X> и -upto-<Y> детали являются необязательными, версия <Y> является эксклюзивной , а диапазоны версии должны быть непрерывными.

Например, r8-upto-8.0.0 , r8-from-8.0.0-upto-8.2.0 и r8-from-8.2.0 образуют действительный набор целевых правил сокращения. Правила в рамках каталога r8-from-8.0.0-upto-8.2.0 будут использоваться R8 из версии 8.0.0 до, но не включая версию 8.2.0.

Учитывая эту информацию, плагин Android Gradle 3.6 или выше выберет правила из соответствующих каталогов R8. Если в библиотеке не указывается целевые правила сокращения, плагин Android Gradle выберет правила из Legacy Locations ( proguard.txt для AAR или META-INF/proguard/<ProGuard-rules-file> для банки).

Разработчики библиотеки могут включить либо целевые правила сокращения, либо правила унаследованного прогноза в свои библиотеки, либо обоих типов, если они хотят поддерживать совместимость с плагином Android Gradle старше 3,6 или других инструментов.

Включите дополнительные конфигурации

Когда вы создаете новый проект или модуль с использованием Android Studio, IDE создает файл <module-dir>/proguard-rules.pro для вас, чтобы включить свои собственные правила. Вы также можете включить дополнительные правила из других файлов, добавив их в свойство proguardFiles в сценарий сборки вашего модуля.

Например, вы можете добавить правила, специфичные для каждого варианта сборки, добавив другое свойство proguardFiles в соответствующий блок productFlavor . Следующий файл Gradle добавляет flavor2-rules.pro к вкусу продукта flavor2 . Теперь flavor2 использует все три правила прогноза, потому что также применяются те, которые из блока release .

Кроме того, вы можете добавить свойство testProguardFiles , которое указывает список файлов прогиарда, которые включены только в тестовый APK:

Котлин

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")
        }
    }
}

классный

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'
        }
    }
}

Сожмите свой код

Сокращение кода с помощью R8 включено по умолчанию, когда вы устанавливаете свойство minifyEnabled на true .

Сокращение кода (также известное как дрожание деревьев) - это процесс удаления кода, который определяет R8, не требуется во время выполнения. Этот процесс может значительно уменьшить размер вашего приложения, если, например, ваше приложение включает в себя множество библиотечных зависимостей, но использует лишь небольшую часть их функциональности.

Чтобы сократить код вашего приложения, R8 First определяет все точки входа в код вашего приложения на основе комбинированного набора файлов конфигурации . Эти точки входа включают все классы, которые платформа Android может использовать для открытия действий или услуг вашего приложения. Начиная с каждой точки входа, R8 осматривает код вашего приложения, чтобы создать график всех методов, переменных участников и других классов, к которым может получить ваше приложение во время выполнения. Код, который не подключен к этому графику, считается недоступным и может быть удален из приложения.

На рисунке 1 показано приложение с зависимостью библиотеки времени выполнения. Во время проверки кода приложения R8 определяет, что методы foo() , faz() и bar() достижимы из точки входа MainActivity.class . Тем не менее, класс OkayApi.class или его метод baz() никогда не используется вашим приложением во время выполнения, а R8 удаляет этот код при сокращении вашего приложения.

Рисунок 1. Во время компиляции R8 создает график на основе объединенных правил вашего проекта, чтобы определить недоступный код.

R8 определяет точки входа в правила -keep в файлах конфигурации проекта R8 . То есть сохраняйте правила Укажите классы, которые R8 не должны отбрасывать при сокращении вашего приложения, и R8 рассматривает эти классы в качестве возможных точек входа в ваше приложение. Плагин Android Gradle и AAPT2 автоматически генерируют правила hope, которые требуются большинством проектов приложений для вас, таких как действия, просмотры и услуги вашего приложения. Однако, если вам нужно настроить это поведение по умолчанию с помощью дополнительных правил сохранения, прочитайте раздел о том, как настроить, какой код сохранить .

Если вместо этого вы заинтересованы только в сокращении размера ресурсов вашего приложения, перейдите к разделу о том, как сократить ваши ресурсы .

Обратите внимание, что если библиотечный проект сократится, приложение, которое зависит от этой библиотеки, включает в себя сокращенные классы библиотеки. Возможно, вам придется отрегулировать правила библиотеки, если в библиотеке не хватает классов. Если вы создаете и публикуете библиотеку в формате AAR, локальные файлы JAR, от которых зависит ваша библиотека , не будут сокращены в файле AAR.

Настраивать какой код, чтобы сохранить

Для большинства ситуаций файл правил Proguard по умолчанию ( proguard-android-optimize.txt ) достаточно для R8, чтобы удалить только неиспользованный код. Тем не менее, некоторые ситуации трудны для правильного анализа R8, и это может удалить код, которое действительно нуждается в вашем приложении. Некоторые примеры того, когда он может неправильно удалить код, включают:

  • Когда ваше приложение вызывает метод из нативного интерфейса Java (JNI)
  • Когда ваше приложение просматривает код выполнения во время выполнения (например, с размышлением)

Тестирование вашего приложения должно выявить любые ошибки, вызванные ненадлежащим образом удаленным кодом, но вы также можете проверить, какой код был удален, генерируя отчет об удаленном коде .

Чтобы исправить ошибки и заставить R8, чтобы сохранить определенное код, добавьте строку -keep в файл правил прогноза. Например:

-keep public class MyClass

В качестве альтернативы, вы можете добавить аннотацию @Keep в код, который вы хотите сохранить. Добавление @Keep в класс сохраняет весь класс как есть. Добавление его в метод или поле будет сохранять метод/поле (и его имя), а также имя класса. Обратите внимание, что эта аннотация доступна только при использовании библиотеки аннотаций Androidx и при включении файла правил прогира, который упакован с плагином Android Gradle, как описано в разделе о том, как включить сокращение .

Есть много соображений, которые вы должны принять при использовании варианта -keep ; Для получения дополнительной информации о настройке файла правил прочитайте руководство по прогнозам . В разделе по устранению неполадок описываются другие общие проблемы, с которыми вы можете столкнуться, когда ваш код разряжается.

Снимите родные библиотеки

По умолчанию библиотеки нативного кода разряжаются в сборках вашего приложения. Эта снятие состоит из удаления таблицы символов и отладки информации, содержащейся в любых местных библиотеках, используемых вашим приложением. Снижение библиотеки нативного кода приводит к значительной экономии размера; Тем не менее, невозможно диагностировать сбои на консоли Google Play из -за недостающей информации (например, имен классов и функций).

Нативная поддержка аварии

Google Play Console сообщает, что нативные сбои подносят Android . С помощью нескольких шагов вы можете генерировать и загружать собственный файл символов отладки для вашего приложения. Этот файл позволяет символическому нативным трассам стека сбоев (которые включают в себя имена классов и функций) в жизненно важных органах Android, чтобы помочь вам отладить ваше приложение в производстве. Эти шаги варьируются в зависимости от версии плагина Android Gradle, используемой в вашем проекте, и вывода сборки вашего проекта.

Плагин Android Gradle Версия 4.1 или позже

Если ваш проект создает пакет приложений Android, вы можете автоматически включить в него файл символов отладки. Чтобы включить этот файл в сборки выпуска, добавьте следующее в файл вашего приложения build.gradle.kts :

android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }

Выберите уровень символа отладки из следующего:

  • Используйте SYMBOL_TABLE , чтобы получить имена функций в символизированных трассах стека Play Console. Этот уровень поддерживает надгробия .
  • Используйте FULL , чтобы получить имена функций, файлы и номера строк в символизированных трассах стека Play Console.

Если ваш проект создает APK, используйте настройку сборки build.gradle.kts . Вручную загрузите файл символов нативного отладки в консоли Google Play. В рамках процесса сборки плагин Android Gradle выводит этот файл в следующем месте проекта:

app/build/outputs/native-debug-symbols/ variant-name /native-debug-symbols.zip

Плагин Android Gradle Версия 4.0 или ранее (и другие системы сборки)

В рамках процесса сборки плагин Android Gradle хранит копию библиотек с нетронутым в каталоге проекта. Эта структура каталогов аналогична следующему:

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
  1. Запишите содержимое этого каталога:

    cd app/build/intermediates/cmake/universal/release/obj
    zip -r symbols.zip .
    
  2. Вручную загрузите файл symbols.zip в консоли Google Play.

Сократите ваши ресурсы

Сокращение ресурсов работает только в сочетании с сокращением кода. После того, как код усаживает удаление всего неиспользованного кода, ресурс усадитель может определить, какие ресурсы все еще используют. Это особенно верно, когда вы добавляете библиотеки кода, которые включают ресурсы - вы должны удалить неиспользованный библиотечный код, чтобы ресурсы библиотеки не стали беззаботными и, таким образом, съемными с помощью ресурса.

Чтобы включить сокращение ресурсов, установите свойство shrinkResources на true в вашем сценарии сборки (наряду с minifyEnabled для сокращения кода). Например:

Котлин

android {
    ...
    buildTypes {
        getByName("release") {
            isShrinkResources = true
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

классный

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

Если вы еще не построили свое приложение, используя minifyEnabled для сокращения кода, попробуйте это, прежде чем включить shrinkResources , потому что вам может потребоваться отредактировать свой файл proguard-rules.pro , чтобы сохранить классы или методы, которые создаются или вызываются динамически, прежде чем вы начнете удалять ресурсы.

Настройка, какие ресурсы сохранить

Если есть конкретные ресурсы, которые вы хотите сохранить или отбросить, создайте файл XML в вашем проекте с помощью тега <resources> и укажите каждый ресурс, чтобы сохранить в tools:keep атрибут и каждый ресурс для отказа в tools:discard атрибут. Оба атрибута принимают отдельный список имен ресурсов. Вы можете использовать символ звездочки в качестве дикой карты.

Например:

<?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" />

Сохраните этот файл в ресурсах вашего проекта, например, в res/raw/my.package.keep.xml . Сборка не упаковывает этот файл в ваше приложение.

ПРИМЕЧАНИЕ. Обязательно используйте уникальное имя для файла keep . Когда разные библиотеки связываются вместе, их правила сохранения будут конфликтуют иначе, вызывая потенциальные проблемы с игнорируемыми правилами или ненужными ресурсами.

Указание, какие ресурсы для сброса могут показаться глупыми, когда вы могли бы вместо этого удалить их, но это может быть полезно при использовании вариантов сборки. Например, вы можете поместить все свои ресурсы в общий каталог проектов, а затем создать другой файл my.package.build.variant.keep.xml для каждого варианта сборки, когда вы знаете, что данный ресурс, по -видимому, используется в коде (и, следовательно, не удаляется с помощью термоусадочного состава), но вы знаете, что он на самом деле не будет использован для данного варианта сборки. Также возможно, что инструменты сборки неправильно определили ресурс по мере необходимости, что возможно, потому что компилятор добавляет встроенные идентификаторы ресурса, а затем анализатор ресурсов может не знать разницу между подлинно упомянутым ресурсом и целочисленным значением в коде, который имеет одинаковое значение.

Включить строгие контрольные проверки

Обычно ресурс может точно определить, используется ли ресурс. Однако, если ваш код делает вызов Resources.getIdentifier() (или, если какая-либо из ваших библиотек делает это-библиотека AppCompat делает), это означает, что ваш код ищет имена ресурсов на основе динамически сгенерированных строк. Когда вы делаете это, ресурс по умолчанию ведет себя в защиту по умолчанию и отмечает все ресурсы с соответствующим форматом имен, как потенциально используемый и недоступный для удаления.

Например, следующий код приводит к отмечению всех ресурсов с префиксом img_ , как используется.

Котлин

val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)

Ява

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

Усаживание ресурса также просматривает все константы строки в вашем коде, а также различные ресурсы res/raw/ , ища URL -адреса ресурсов в формате, аналогичном file:///android_res/drawable//ic_plus_anim_016.png . Если он находит такие струны, как это или другие, которые выглядят так, как будто они могут быть использованы для построения таких URL -адресов, это не удаляет их.

Это примеры режима сжатия безопасного сокращения, который включен по умолчанию. Вы можете, однако, отключить эту обработку «лучше, чем извините», и указать, что ресурс усаживает только ресурсы, которые используются определенными, используются. Чтобы сделать это, установите shrinkMode в strict в файле keep.xml следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:shrinkMode="strict" />

Если вы включите строгий режим сокращения, и ваш код также ссылается на ресурсы с динамически сгенерированными строками, как показано выше, то вы должны вручную сохранить эти ресурсы, используя tools:keep атрибут.

Удалить неиспользованные альтернативные ресурсы

Ресурс Gradle Chroinker удаляет только ресурсы, на которые не ссылается код вашего приложения, что означает, что он не удалит альтернативные ресурсы для различных конфигураций устройства. При необходимости вы можете использовать свойство resConfigs Plugin Android Gradle, чтобы удалить альтернативные файлы ресурсов, которые не нуждается в вашем приложении.

Например, если вы используете библиотеку, которая включает языковые ресурсы (например, AppCompat или Google Play Services), то ваше приложение включает в себя все переведенные языковые строки для сообщений в этих библиотеках, независимо от того, переведены ли остальная часть вашего приложения на одни и те же языки или нет. Если вы хотите сохранить только языки, которые ваше приложение официально поддерживает, вы можете указать эти языки, используя свойство resConfig . Любые ресурсы для не указанных языков удаляются.

Следующий фрагмент показывает, как ограничить ваши языковые ресурсы только английским и французским:

Котлин

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

классный

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

При выпуске приложения с использованием формата пакета приложений Android, по умолчанию только языки, настроенные на устройстве пользователя, загружаются при установке приложения. Точно так же, только ресурсы, соответствующие плотности экрана устройства, и собственные библиотеки, соответствующие ABI устройства, включены в загрузку. Для получения дополнительной информации см. В конфигурации пакета приложений Android .

Для Legacy Apps, выпущенных с помощью APK (созданных до августа 2021 года), вы можете настроить, какую плотность экрана или ресурсы ABI следует включать в ваш APK, создав несколько APK , которые нацелены на различную конфигурацию устройства.

Слияние дубликатов ресурсов

По умолчанию Gradle также объединяется с одинаковыми ресурсами, такие как рисунки с тем же именем, которое может быть в разных папках ресурсов. Такое поведение не контролируется свойством shrinkResources и не может быть отключено, поскольку необходимо избежать ошибок, когда несколько ресурсов соответствуют имени, который искает ваш код.

Слияние ресурсов происходит только тогда, когда два или более файла имеют идентичное имя ресурса, тип и квалификатор. Градл выбирает, какой файл он считает лучшим выбором среди дубликатов (на основе приоритетного порядка, описанного ниже), и передает только один ресурс AAPT для распространения в конечном артефакте.

Gradle ищет дубликаты ресурсов в следующих местах:

  • Основные ресурсы, связанные с основным набором источника, обычно расположенные в src/main/res/ .
  • Варианты накладывают из типа сборки и построить ароматы.
  • Зависимости библиотечного проекта.

Градл объединяет дублирующие ресурсы в следующем каскадном приоритетном порядке:

Зависимости → Главный → Строительский вкус → Тип сборки

Например, если дублированный ресурс появляется как в ваших основных ресурсах, так и в аромате сборки, Gradle выбирает тот, который в стиле сборки.

Если идентичные ресурсы появляются в одном и том же исходном наборе, Gradle не может объединить их и издает ошибку слияния ресурсов. Это может произойти src/main/res/ если вы определите несколько исходных наборов в свойстве sourceSet вашего файла build.gradle.kts src/main/res2/

Запутайте свой код

Цель запускавания состоит в том, чтобы уменьшить размер вашего приложения путем сокращения имен классов, методов и полей вашего приложения. Ниже приведен пример запутывания с использованием 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

В то время как запутывание не удаляет код из вашего приложения, значительную экономию размера можно увидеть в приложениях с файлами DEX, которые индексируют многие классы, методы и поля. Однако, поскольку запутывание переименовает различные части вашего кода, определенные задачи, такие как проверка трассов стека, требуют дополнительных инструментов. Чтобы понять свою Stacktrace после запутывания, прочитайте раздел о том, как декодировать срок с запутанной стеком .

Кроме того, если ваш код опирается на предсказуемое именование для методов и классов вашего приложения - например, используя отражение, вы должны рассматривать эти подписи как точки входа и указать правила сохранения для них, как описано в разделе о том, как настроить какой код . Эти правила говорят, что R8 не только сохраняет этот код в окончательном DEX вашего приложения, но и сохраняет свое первоначальное именование.

Декодировать запутанную трассировку стека

После того, как R8 запутает ваш код, понимание трассировки стека сложно (если не невозможно), потому что имена классов и методов могли быть изменены. Чтобы получить исходную трассу стека, вы должны просматривать трассировку стека .

Оптимизация кода

Чтобы еще больше оптимизировать ваше приложение, R8 осматривает ваш код на более глубоком уровне, чтобы удалить более неиспользованный код или, где это возможно, переписывает ваш код, чтобы сделать его менее словесным. Ниже приведены несколько примеров такой оптимизации:

  • Если ваш код никогда не принимает ветвь else {} для данного оператора, если/else, R8 может удалить код для ветви else {} .
  • Если ваш код вызывает метод только в нескольких местах, R8 может удалить метод и встроить его на нескольких сайтах вызовов.
  • Если R8 определяет, что у класса есть только один уникальный подкласс, а сам класс не создается (например, абстрактный базовый класс, используемый только одним классом конкретной реализации), то R8 может объединить два класса и удалять класс из приложения.
  • Чтобы узнать больше, прочитайте сообщения в блоге R8 «Оптимизация» Джейка Уортона.

R8 не позволяет вам отключить или включать дискретную оптимизацию или изменять поведение оптимизации. Фактически, R8 игнорирует любые правила прогноза, которые пытаются изменить оптимизацию по умолчанию, такие как -optimizations и -optimizationpasses . Это ограничение важно, потому что, поскольку R8 продолжает улучшаться, поддержание стандартного поведения для оптимизации помогает команде Android Studio легко решать и решить любые проблемы, с которыми вы можете столкнуться.

Обратите внимание, что включение оптимизации изменит трассы стека для вашего приложения. Например, INLINING удалит кадры стека. См. Раздел « Посмотреть , чтобы узнать», как получить исходные следы стека.

Влияние на производительность выполнения

Если все включены сокращение, запутывание и оптимизация, R8 улучшит производительность времени выполнения кода (включая время запуска и кадров в потоке пользовательского интерфейса) до 30%. Отключение любого из этих радикально ограничивает набор оптимизаций R8.

Если R8 включен, вы также должны создать профили запуска для еще лучшей производительности запуска.

Включить улучшенные оптимизации

R8 включает в себя набор дополнительных оптимизаций (называемый «полный режим»), что заставляет его вести себя иначе, чем прогиар. Эти оптимизации включены по умолчанию с момента версии Android Gradle Gradle версии 8.0.0 .

Вы можете отключить эти дополнительные оптимизации, включив следующее в файл вашего проекта gradle.properties :

android.enableR8.fullMode=false

Поскольку дополнительные оптимизации заставляют R8 вести себя иначе, чем прогиар, они могут потребовать, чтобы вы включали дополнительные правила прогиба, чтобы избежать проблем с выполнением выполнения, если вы используете правила, предназначенные для прогноза. Например, скажем, что ваш код ссылается на класс через API Java Reflection. Когда вы не используете «полный режим», R8 предполагает, что вы намереваетесь изучить и манипулировать объектами этого класса во время выполнения, даже если ваш код фактически этого не делает, а он автоматически сохраняет класс и его статический инициализатор.

Однако при использовании «Полного режима» R8 не делает это предположение, и, если R8 утверждает, что ваш код в противном случае никогда не использует класс во время выполнения, он удаляет класс из окончательного DEX вашего приложения. То есть, если вы хотите сохранить класс и его статический инициализатор, вам необходимо включить в файл правил сохранения правило в файле правил.

Если вы сталкиваетесь с какими -либо проблемами при использовании R8 «Полный режим», обратитесь к странице FAQ R8 для возможного решения. Если вы не можете решить проблему, сообщите об ошибке .

Поездка на StackTraces

Код, обработанный R8, изменяется различными способами, которые могут усложнять тракции стека, поскольку трассировки стека не будут точно соответствовать исходному коду. Это может иметь место для изменений в номерах строк, когда информация отладки не сохраняется. Это может быть связано с оптимизацией, такими как внедрение и изложение. Самым большим участником является запутывание, когда даже классы и методы изменят имена.

Чтобы восстановить исходную трассу стека, R8 предоставляет инструмент командной строки повреждения , который связан с пакетом инструментов командной строки .

Чтобы поддержать повреждение трассов стека вашего приложения, вы должны убедиться, что сборка сохраняет достаточную информацию для повторного просмотра, добавив следующие правила в файл proguard-rules.pro вашего модуля.

-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile

Атрибут LineNumberTable сохраняет позиционную информацию в методах, так что эти позиции печатаются в трассах стека. Атрибут SourceFile гарантирует, что все потенциальные время забега фактически печатают позиционную информацию. Директива -renamesourcefileattribute устанавливает имя исходного файла в трассах стека на только SourceFile . Фактическое исходное имя исходного файла не требуется при поиске, поскольку файл сопоставления содержит исходный исходный файл.

R8 creates a mapping.txt file each time it runs, which contains the information needed to map stack traces back to the original stack traces. Android Studio saves the file in the <module-name> /build/outputs/mapping/ <build-type> / directory.

When publishing your app on Google Play, you can upload the mapping.txt file for each version of your app. When publishing using Android App Bundles this file is included automatically as part of the app bundle content. Then Google Play will retrace incoming stack traces from user-reported issues so you can review them in the Play Console. For more information, see the Help Center article about how to deobfuscate crash stack traces .

Troubleshoot with R8

This section describes some strategies for troubleshooting issues when enabling shrinking, obfuscation, and optimization using R8. If you do not find a solution to your issue below, also read the R8 FAQ page and ProGuard's troubleshooting guide .

Generate a report of removed (or kept) code

To help you troubleshoot certain R8 issues, it may be useful to see a report of all the code that R8 removed from your app. For each module for which you want to generate this report, add -printusage <output-dir>/usage.txt to your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of removed code looks similar to the following:

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
...

If instead you want to see a report of the entry points that R8 determines from your project's keep rules , include -printseeds <output-dir>/seeds.txt in your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of kept entry points looks similar to the following:

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
...

Troubleshoot resource shrinking

When you shrink resources, the Build window shows a summary of the resources that are removed from the app. (You need to first click Toggle view on the left side of the window to display detailed text output from Gradle.) For example:

:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning

Gradle also creates a diagnostic file named resources.txt in <module-name>/build/outputs/mapping/release/ (the same folder as ProGuard's output files). This file includes details such as which resources reference other resources and which resources are used or removed.

For example, to find out why @drawable/ic_plus_anim_016 is still in your app, open the resources.txt file and search for that file name. You might find that it's referenced from another resource, as follows:

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

You now need to know why @drawable/add_schedule_fab_icon_anim is reachable—and if you search upwards you'll find that resource is listed under "The root reachable resources are:". This means there is a code reference to add_schedule_fab_icon_anim (that is, its R.drawable ID was found in the reachable code).

If you are not using strict checking, resource IDs can be marked as reachable if there are string constants that look like they might be used to construct resource names for dynamically loaded resources. In that case, if you search the build output for the resource name, you might find a message like this:

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.

If you see one of these strings and you are certain that the string is not being used to load the given resource dynamically, you can use the tools:discard attribute to inform the build system to remove it, as described in the section about how to customize which resources to keep .

,

To make your app as small and fast as possible, you should optimize and minify your release build with isMinifyEnabled = true .

Doing so enables shrinking , which removes unused code; obfuscation , which shortens the names of your app's classes and members; and optimization , which applies improved code optimization strategies to further reduce the size and improve the performance of your app. This page describes how R8 performs these compile-time tasks for your project and how you can customize them.

When you build your project using Android Gradle plugin 3.4.0 or higher, the plugin no longer uses ProGuard to perform compile-time code optimization. Instead, the plugin works with the R8 compiler to handle the following compile-time tasks:

  • Code shrinking (or tree-shaking): detects and safely removes unused classes, fields, methods, and attributes from your app and its library dependencies (making it a valuable tool for working around the 64k reference limit ). For example, if you use only a few APIs of a library dependency, shrinking can identify library code that your app is not using and remove only that code from your app. To learn more, go to the section about how to shrink your code .
  • Resource shrinking: removes unused resources from your packaged app, including unused resources in your app's library dependencies. It works in conjunction with code shrinking such that once unused code has been removed, any resources no longer referenced can be safely removed as well. To learn more, go to the section about how to shrink your resources .
  • Optimization: inspects and rewrites your code to improve runtime performance and further reduce the size of your app's DEX files. This improves runtime performance of code by up to 30%, drastically improving startup and frame timing. For example, if R8 detects that the else {} branch for a given if/else statement is never taken, R8 removes the code for the else {} branch. To learn more, go to the section about code optimization .
  • Obfuscation (or identifier minification): shortens the name of classes and members, which results in reduced DEX file sizes. To learn more, go to the section about how to obfuscate your code .

When building the release version of your app, R8 can be configured to perform the compile-time tasks described above for you. You can also disable certain tasks or customize R8's behavior through ProGuard rules files. In fact, R8 works with all of your existing ProGuard rules files , so updating the Android Gradle plugin to use R8 should not require you to change your existing rules.

Enable shrinking, obfuscation, and optimization

When you use Android Studio 3.4 or Android Gradle plugin 3.4.0 and higher, R8 is the default compiler that converts your project's Java bytecode into the DEX format that runs on the Android platform. However, when you create a new project using Android Studio, shrinking, obfuscation, and code optimization is not enabled by default. That's because these compile-time optimizations increase the build time of your project and might introduce bugs if you do not sufficiently customize which code to keep .

So, it's best to enable these compile-time tasks when building the final version of your app that you test prior to publishing. To enable shrinking, obfuscation, and optimization, include the following in your project-level build script.

Котлин

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"
            )
        }
    }
    ...
}

классный

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'
        }
    }
    ...
}

R8 configuration files

R8 uses ProGuard rules files to modify its default behavior and better understand your app's structure, such as the classes that serve as entry points into your app's code. Although you can modify some of these rules files, some rules may be generated automatically by compile-time tools, such as AAPT2, or inherited from your app's library dependencies. The table below describes the sources of ProGuard rules files that R8 uses.

Источник Расположение Описание
Android Studio <module-dir>/proguard-rules.pro When you create a new module using Android Studio, the IDE creates a proguard-rules.pro file in the root directory of that module.

By default, this file does not apply any rules. So, include your own ProGuard rules here, such as your custom keep rules .

Android Gradle plugin Generated by the Android Gradle plugin at compile time. The Android Gradle plugin generates proguard-android-optimize.txt , which includes rules that are useful to most Android projects and enables @Keep* annotations .

By default, when creating a new module using Android Studio, the module-level build script includes this rules file in your release build for you.

Note: The Android Gradle plugin includes additional predefined ProGuard rules files, but it is recommended that you use proguard-android-optimize.txt .

Library dependencies

In an AAR library:
proguard.txt

In a JAR library:
META-INF/proguard/<ProGuard-rules-file>

In addition to these locations, Android Gradle plugin 3.6 or higher also supports targeted shrink rules .

If an AAR or JAR library is published with its own rules file, and you include that library as a compile-time dependency, R8 automatically applies those rules when compiling your project.

In addition to conventional ProGuard rules, Android Gradle plugin 3.6 or higher also supports targeted shrink rules . These are rules that target specific shrinkers (R8 or ProGuard), as well as specific shrinker versions.

Using rules files that are packaged with libraries is useful if certain rules are required for the library to function properly—that is, the library developer has performed the troubleshooting steps for you.

However, you should be aware that, because the rules are additive , certain rules that a library dependency includes cannot be removed and might impact the compilation of other parts of your app. For example, if a library includes a rule to disable code optimizations, that rule disables optimizations for your entire project.

Android Asset Package Tool 2 (AAPT2) After building your project with minifyEnabled true : <module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt AAPT2 generates keep rules based on references to classes in your app's manifest, layouts, and other app resources. For example, AAPT2 includes a keep rule for each Activity that you register in your app's manifest as an entry point.
Custom configuration files By default, when you create a new module using Android Studio, the IDE creates <module-dir>/proguard-rules.pro for you to add your own rules. You can include additional configurations , and R8 applies them at compile-time.

When you set the minifyEnabled property to true , R8 combines rules from all the available sources listed above. This is important to remember when you troubleshoot with R8 , because other compile-time dependencies, such as library dependencies, may introduce changes to the R8 behavior that you do not know about.

To output a full report of all the rules that R8 applies when building your project, include the following in your module's proguard-rules.pro file:

// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt

Targeted shrink rules

Android Gradle plugin 3.6 or higher supports libraries' rules that target specific shrinkers (R8 or ProGuard), as well as specific shrinker versions. This allows library developers to tailor their rules to work optimally in projects that use new shrinker versions, while allowing existing rules to continue to be used in projects with older shrinker versions.

To specify targeted shrink rules, library developers will need to include them at specific locations inside an AAR or JAR library, as described below.

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>

That means targeted shrink rules are stored in the META-INF/com.android.tools directory of a JAR or in the META-INF/com.android.tools directory inside classes.jar of an AAR.

Under that directory, there can be multiple directories with names in the form of r8-from-<X>-upto-<Y> or proguard-from-<X>-upto-<Y> to indicate which versions of which shrinker the rules inside the directories are written for. Note that the -from-<X> and -upto-<Y> parts are optional, the <Y> version is exclusive , and the version ranges must be continuous.

For example, r8-upto-8.0.0 , r8-from-8.0.0-upto-8.2.0 , and r8-from-8.2.0 form a valid set of targeted shrink rules. The rules under the r8-from-8.0.0-upto-8.2.0 directory will be used by R8 from version 8.0.0 up to but not including version 8.2.0.

Given that information, Android Gradle plugin 3.6 or higher will select the rules from the matching R8 directories. If a library does not specify targeted shrink rules, the Android Gradle plugin will select the rules from the legacy locations ( proguard.txt for an AAR or META-INF/proguard/<ProGuard-rules-file> for a JAR).

Library developers can choose to include either targeted shrink rules or legacy ProGuard rules in their libraries, or both types if they want to maintain compatibility with Android Gradle plugin older than 3.6 or other tools.

Include additional configurations

When you create a new project or module using Android Studio, the IDE creates a <module-dir>/proguard-rules.pro file for you to include your own rules. You can also include additional rules from other files by adding them to the proguardFiles property in your module's build script.

For example, you can add rules that are specific to each build variant by adding another proguardFiles property in the corresponding productFlavor block. The following Gradle file adds flavor2-rules.pro to the flavor2 product flavor. Now, flavor2 uses all three ProGuard rules because those from the release block are also applied.

Additionally, you can add the testProguardFiles property, which specifies a list of ProGuard files that are included in the test APK only:

Котлин

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")
        }
    }
}

классный

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'
        }
    }
}

Shrink your code

Code shrinking with R8 is enabled by default when you set the minifyEnabled property to true .

Code shrinking (also known as tree shaking), is the process of removing code that R8 determines is not required at runtime. This process can greatly reduce your app's size if, for example, your app includes many library dependencies but utilizes only a small part of their functionality.

To shrink your app's code, R8 first determines all entry points into your app's code based on the combined set of configuration files . These entry points include all classes that the Android platform may use to open your app's Activities or services. Starting from each entry point, R8 inspects your app's code to build a graph of all methods, member variables, and other classes that your app might access at runtime. Code that is not connected to that graph is considered unreachable and may be removed from the app.

Figure 1 shows an app with a runtime library dependency. While inspecting the app's code, R8 determines that methods foo() , faz() , and bar() are reachable from the MainActivity.class entry point. However, class OkayApi.class or its method baz() is never used by your app at runtime, and R8 removes that code when shrinking your app.

Figure 1. At compile-time, R8 builds a graph based on your project's combined keep rules to determine unreachable code.

R8 determines entry points through -keep rules in the project's R8 configuration files . That is, keep rules specify classes that R8 should not discard when shrinking your app, and R8 considers those classes as possible entry points into your app. The Android Gradle plugin and AAPT2 automatically generate keep rules that are required by most app projects for you, such as your app's activities, views, and services. However, if you need to customize this default behavior with additional keep rules, read the section about how to customize which code to keep .

If instead you are interested only in reducing the size of your app's resources, skip to the section about how to shrink your resources .

Note that if a library project is shrunk, an app that depends on that library includes shrunk library classes. You might need to adjust library keep rules if there are missing classes in the library APK. If you're building and publishing a library in AAR format, local JAR files that your library depends on aren't shrunk in the AAR file.

Customize which code to keep

For most situations, the default ProGuard rules file ( proguard-android-optimize.txt ) is sufficient for R8 to remove only the unused code. However, some situations are difficult for R8 to analyze correctly and it might remove code your app actually needs. Some examples of when it might incorrectly remove code include:

  • When your app calls a method from the Java Native Interface (JNI)
  • When your app looks up code at runtime (such as with reflection)

Testing your app should reveal any errors caused by inappropriately removed code, but you can also inspect what code was removed by generating a report of removed code .

To fix errors and force R8 to keep certain code, add a -keep line in the ProGuard rules file. Например:

-keep public class MyClass

Alternatively, you can add the @Keep annotation to the code you want to keep. Adding @Keep on a class keeps the entire class as-is. Adding it on a method or field will keep the method/field (and its name) as well as the class name intact. Note that this annotation is available only when using the AndroidX Annotations Library and when you include the ProGuard rules file that is packaged with the Android Gradle plugin, as described in the section about how to enable shrinking .

There are many considerations you should make when using the -keep option; for more information about customizing your rules file, read the ProGuard Manual . The Troubleshooting section outlines other common problems you might encounter when your code gets stripped away.

Strip native libraries

By default, native code libraries are stripped in release builds of your app. This stripping consists of removing the symbol table and debugging information contained in any native libraries used by your app. Stripping native code libraries results in significant size savings; however, it's impossible to diagnose crashes on the Google Play Console due to the missing information (such as class and function names).

Native crash support

The Google Play Console reports native crashes under Android vitals . With a few steps, you can generate and upload a native debug symbols file for your app. This file enables symbolicated native crash stack traces (that include class and function names) in Android vitals to help you debug your app in production. These steps vary depending on the version of the Android Gradle plugin used in your project and the build output of your project.

Android Gradle plugin version 4.1 or later

If your project builds an Android App Bundle, you can automatically include the native debug symbols file in it. To include this file in release builds, add the following to your app's build.gradle.kts file:

android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }

Select the debug symbol level from the following:

  • Use SYMBOL_TABLE to get function names in the Play Console's symbolicated stack traces. This level supports tombstones .
  • Use FULL to get function names, files, and line numbers in the Play Console's symbolicated stack traces.

If your project builds an APK, use the build.gradle.kts build setting shown earlier to generate the native debug symbols file separately. Manually upload the native debug symbols file to the Google Play Console. As part of the build process, the Android Gradle plugin outputs this file in the following project location:

app/build/outputs/native-debug-symbols/ variant-name /native-debug-symbols.zip

Android Gradle plugin version 4.0 or earlier (and other build systems)

As part of the build process, the Android Gradle plugin keeps a copy of the unstripped libraries in a project directory. This directory structure is similar to the following:

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
  1. Zip up the contents of this directory:

    cd app/build/intermediates/cmake/universal/release/obj
    zip -r symbols.zip .
    
  2. Manually upload the symbols.zip file to the Google Play Console.

Shrink your resources

Resource shrinking works only in conjunction with code shrinking. After the code shrinker removes all unused code, the resource shrinker can identify which resources the app still uses. This is especially true when you add code libraries that include resources—you must remove unused library code so the library resources become unreferenced and, thus, removable by the resource shrinker.

To enable resource shrinking, set the shrinkResources property to true in your build script (alongside minifyEnabled for code shrinking). Например:

Котлин

android {
    ...
    buildTypes {
        getByName("release") {
            isShrinkResources = true
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

классный

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

If you haven't already built your app using minifyEnabled for code shrinking, then try that before enabling shrinkResources , because you might need to edit your proguard-rules.pro file to keep classes or methods that are created or invoked dynamically before you start removing resources.

Customize which resources to keep

If there are specific resources you wish to keep or discard, create an XML file in your project with a <resources> tag and specify each resource to keep in the tools:keep attribute and each resource to discard in the tools:discard attribute. Both attributes accept a comma-separated list of resource names. You can use the asterisk character as a wild card.

Например:

<?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" />

Save this file in your project resources, for example, at res/raw/my.package.keep.xml . The build does not package this file into your app.

Note: Make sure to use a unique name for the keep file. When different libraries get linked together their keep rules would conflict otherwise, causing potential issues with ignored rules or unneeded kept resources.

Specifying which resources to discard might seem silly when you could instead delete them, but this can be useful when using build variants. For example, you might put all your resources into the common project directory, then create a different my.package.build.variant.keep.xml file for each build variant when you know that a given resource appears to be used in code (and therefore not removed by the shrinker) but you know it actually won't be used for the given build variant. It's also possible that the build tools incorrectly identified a resource as needed, which is possible because the compiler adds the resource IDs inline and then the resource analyzer might not know the difference between a genuinely referenced resource and an integer value in the code that happens to have the same value.

Enable strict reference checks

Normally, the resource shrinker can accurately determine whether a resource is used. However, if your code makes a call to Resources.getIdentifier() (or if any of your libraries do that—the AppCompat library does), that means your code is looking up resource names based on dynamically-generated strings. When you do this, the resource shrinker behaves defensively by default and marks all resources with a matching name format as potentially used and unavailable for removal.

For example, the following code causes all resources with the img_ prefix to be marked as used.

Котлин

val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)

Ява

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

The resource shrinker also looks through all the string constants in your code, as well as various res/raw/ resources, looking for resource URLs in a format similar to file:///android_res/drawable//ic_plus_anim_016.png . If it finds strings like this or others that look like they could be used to construct URLs like this, it doesn't remove them.

These are examples of the safe shrinking mode that is enabled by default. You can, however, turn off this "better safe than sorry" handling, and specify that the resource shrinker keep only resources that it's certain are used. To do this, set shrinkMode to strict in the keep.xml file, as follows:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:shrinkMode="strict" />

If you do enable strict shrinking mode and your code also references resources with dynamically-generated strings, as shown above, then you must manually keep those resources using the tools:keep attribute.

Remove unused alternative resources

The Gradle resource shrinker removes only resources that are not referenced by your app code, which means it will not remove alternative resources for different device configurations. If necessary, you can use the Android Gradle plugin's resConfigs property to remove alternative resource files that your app does not need.

For example, if you are using a library that includes language resources (such as AppCompat or Google Play Services), then your app includes all translated language strings for the messages in those libraries whether the rest of your app is translated to the same languages or not. If you'd like to keep only the languages that your app officially supports, you can specify those languages using the resConfig property. Any resources for languages not specified are removed.

The following snippet shows how to limit your language resources to just English and French:

Котлин

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

классный

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

When releasing an app using the Android App Bundle format, by default only languages configured on a user's device are downloaded when installing the app. Similarly, only resources matching the device's screen density, and native libraries matching the device's ABI are included in the download. For more information refer to the Android App Bundle configuration .

For legacy apps releasing with APKs (created before August 2021), you can customize which screen density or ABI resources to include in your APK by building multiple APKs that each target a different device configuration.

Merge duplicate resources

By default, Gradle also merges identically named resources, such as drawables with the same name that might be in different resource folders. This behavior is not controlled by the shrinkResources property and cannot be disabled, because it is necessary to avoid errors when multiple resources match the name your code is looking up.

Resource merging occurs only when two or more files share an identical resource name, type, and qualifier. Gradle selects which file it considers to be the best choice among the duplicates (based on a priority order described below) and passes only that one resource to the AAPT for distribution in the final artifact.

Gradle looks for duplicate resources in the following locations:

  • The main resources, associated with the main source set, generally located in src/main/res/ .
  • The variant overlays, from the build type and build flavors.
  • The library project dependencies.

Gradle merges duplicate resources in the following cascading priority order:

Dependencies → Main → Build flavor → Build type

For example, if a duplicate resource appears in both your main resources and a build flavor, Gradle selects the one in the build flavor.

If identical resources appear in the same source set, Gradle cannot merge them and emits a resource merge error. This can happen if you define multiple source sets in the sourceSet property of your build.gradle.kts file—for example if both src/main/res/ and src/main/res2/ contain identical resources.

Obfuscate your code

The purpose of obfuscation is to reduce your app size by shortening the names of your app's classes, methods, and fields. The following is an example of obfuscation using 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

While obfuscation does not remove code from your app, significant size savings can be seen in apps with DEX files that index many classes, methods, and fields. However, as obfuscation renames different parts of your code, certain tasks, such as inspecting stack traces, require additional tools. To understand your stacktrace after obfuscation, read the section about how to decode an obfuscated stack trace .

Additionally, if your code relies on predictable naming for your app's methods and classes—when using reflection, for example, you should treat those signatures as entry points and specify keep rules for them, as described in the section about how to customize which code to keep . Those keep rules tell R8 to not only keep that code in your app's final DEX but also retain its original naming.

Decode an obfuscated stack trace

After R8 obfuscates your code, understanding a stack trace is difficult (if not impossible) because names of classes and methods might have been changed. To obtain the original stack trace you should retrace the stack trace .

Code optimization

In order to optimize your app even further, R8 inspects your code at a deeper level to remove more unused code or, where possible, rewrite your code to make it less verbose. The following are a few examples of such optimizations:

  • If your code never takes the else {} branch for a given if/else statement, R8 might remove the code for the else {} branch.
  • If your code calls a method in only a few places, R8 might remove the method and inline it at the few call sites.
  • If R8 determines that a class has only one unique subclass, and the class itself is not instantiated (for example, an abstract base class only used by one concrete implementation class), then R8 can combine the two classes and remove a class from the app.
  • To learn more, read the R8 optimization blog posts by Jake Wharton.

R8 does not allow you to disable or enable discrete optimizations, or modify the behavior of an optimization. In fact, R8 ignores any ProGuard rules that attempt to modify default optimizations, such as -optimizations and -optimizationpasses . This restriction is important because, as R8 continues to improve, maintaining a standard behavior for optimizations helps the Android Studio team easily troubleshoot and resolve any issues that you might encounter.

Note that enabling optimization will change the stack traces for your application. For example, inlining will remove stack frames. See the section on retracing to learn how to obtain the original stack traces.

Impact on runtime performance

If shrinking, obfuscation, and optimization are all enabled, R8 will improve runtime performance of code (including startup and frame time on the UI thread) by up to 30%. Disabling any of these drastically limits the set of optimizations R8 uses.

If R8 is enabled, you should also create Startup Profiles for even better startup performance.

Enable improved optimizations

R8 includes a set of additional optimizations (referred to as "full mode") which makes it behave differently from ProGuard. These optimizations are enabled by default since Android Gradle plugin version 8.0.0 .

You can disable these additional optimizations by including the following in your project's gradle.properties file:

android.enableR8.fullMode=false

Because the additional optimizations make R8 behave differently from ProGuard, they may require you to include additional ProGuard rules to avoid runtime issues if you're using rules designed for ProGuard. For example, say that your code references a class through the Java Reflection API. When not using "full mode," R8 assumes that you intend to examine and manipulate objects of that class at runtime—even if your code actually does not—and it automatically keeps the class and its static initializer.

However, when using "full mode", R8 does not make this assumption and, if R8 asserts that your code otherwise never uses the class at runtime, it removes the class from your app's final DEX. That is, if you want to keep the class and its static initializer, you need to include a keep rule in your rules file to do that.

If you encounter any issues while using R8's "full mode", refer to the R8 FAQ page for a possible solution. If you are unable to resolve the issue, please report a bug .

Retracing stacktraces

Code processed by R8 is changed in various ways that can make stack traces harder to understand because the stack traces won't exactly correspond to the source code. This can be the case for changes to the line numbers when debugging information is not kept. It can be due to optimizations such as inlining and outlining. The largest contributor is obfuscation where even the classes and methods will change names.

To recover the original stack trace, R8 provides the retrace command-line tool, which is bundled with the command-line tools package .

To support retracing of your application's stack traces, you should ensure the build retains sufficient information to retrace with by adding the following rules to your module's proguard-rules.pro file:

-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile

The LineNumberTable attribute retains positional information in methods such that those positions are printed in stack traces. The SourceFile attribute ensures that all potential runtimes actually print the positional info. The -renamesourcefileattribute directive sets the source file name in stack traces to just SourceFile . The actual original source file name is not required when retracing because the mapping file contains the original source file.

R8 creates a mapping.txt file each time it runs, which contains the information needed to map stack traces back to the original stack traces. Android Studio saves the file in the <module-name> /build/outputs/mapping/ <build-type> / directory.

When publishing your app on Google Play, you can upload the mapping.txt file for each version of your app. When publishing using Android App Bundles this file is included automatically as part of the app bundle content. Then Google Play will retrace incoming stack traces from user-reported issues so you can review them in the Play Console. For more information, see the Help Center article about how to deobfuscate crash stack traces .

Troubleshoot with R8

This section describes some strategies for troubleshooting issues when enabling shrinking, obfuscation, and optimization using R8. If you do not find a solution to your issue below, also read the R8 FAQ page and ProGuard's troubleshooting guide .

Generate a report of removed (or kept) code

To help you troubleshoot certain R8 issues, it may be useful to see a report of all the code that R8 removed from your app. For each module for which you want to generate this report, add -printusage <output-dir>/usage.txt to your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of removed code looks similar to the following:

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
...

If instead you want to see a report of the entry points that R8 determines from your project's keep rules , include -printseeds <output-dir>/seeds.txt in your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of kept entry points looks similar to the following:

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
...

Troubleshoot resource shrinking

When you shrink resources, the Build window shows a summary of the resources that are removed from the app. (You need to first click Toggle view on the left side of the window to display detailed text output from Gradle.) For example:

:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning

Gradle also creates a diagnostic file named resources.txt in <module-name>/build/outputs/mapping/release/ (the same folder as ProGuard's output files). This file includes details such as which resources reference other resources and which resources are used or removed.

For example, to find out why @drawable/ic_plus_anim_016 is still in your app, open the resources.txt file and search for that file name. You might find that it's referenced from another resource, as follows:

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

You now need to know why @drawable/add_schedule_fab_icon_anim is reachable—and if you search upwards you'll find that resource is listed under "The root reachable resources are:". This means there is a code reference to add_schedule_fab_icon_anim (that is, its R.drawable ID was found in the reachable code).

If you are not using strict checking, resource IDs can be marked as reachable if there are string constants that look like they might be used to construct resource names for dynamically loaded resources. In that case, if you search the build output for the resource name, you might find a message like this:

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.

If you see one of these strings and you are certain that the string is not being used to load the given resource dynamically, you can use the tools:discard attribute to inform the build system to remove it, as described in the section about how to customize which resources to keep .

,

To make your app as small and fast as possible, you should optimize and minify your release build with isMinifyEnabled = true .

Doing so enables shrinking , which removes unused code; obfuscation , which shortens the names of your app's classes and members; and optimization , which applies improved code optimization strategies to further reduce the size and improve the performance of your app. This page describes how R8 performs these compile-time tasks for your project and how you can customize them.

When you build your project using Android Gradle plugin 3.4.0 or higher, the plugin no longer uses ProGuard to perform compile-time code optimization. Instead, the plugin works with the R8 compiler to handle the following compile-time tasks:

  • Code shrinking (or tree-shaking): detects and safely removes unused classes, fields, methods, and attributes from your app and its library dependencies (making it a valuable tool for working around the 64k reference limit ). For example, if you use only a few APIs of a library dependency, shrinking can identify library code that your app is not using and remove only that code from your app. To learn more, go to the section about how to shrink your code .
  • Resource shrinking: removes unused resources from your packaged app, including unused resources in your app's library dependencies. It works in conjunction with code shrinking such that once unused code has been removed, any resources no longer referenced can be safely removed as well. To learn more, go to the section about how to shrink your resources .
  • Optimization: inspects and rewrites your code to improve runtime performance and further reduce the size of your app's DEX files. This improves runtime performance of code by up to 30%, drastically improving startup and frame timing. For example, if R8 detects that the else {} branch for a given if/else statement is never taken, R8 removes the code for the else {} branch. To learn more, go to the section about code optimization .
  • Obfuscation (or identifier minification): shortens the name of classes and members, which results in reduced DEX file sizes. To learn more, go to the section about how to obfuscate your code .

When building the release version of your app, R8 can be configured to perform the compile-time tasks described above for you. You can also disable certain tasks or customize R8's behavior through ProGuard rules files. In fact, R8 works with all of your existing ProGuard rules files , so updating the Android Gradle plugin to use R8 should not require you to change your existing rules.

Enable shrinking, obfuscation, and optimization

When you use Android Studio 3.4 or Android Gradle plugin 3.4.0 and higher, R8 is the default compiler that converts your project's Java bytecode into the DEX format that runs on the Android platform. However, when you create a new project using Android Studio, shrinking, obfuscation, and code optimization is not enabled by default. That's because these compile-time optimizations increase the build time of your project and might introduce bugs if you do not sufficiently customize which code to keep .

So, it's best to enable these compile-time tasks when building the final version of your app that you test prior to publishing. To enable shrinking, obfuscation, and optimization, include the following in your project-level build script.

Котлин

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"
            )
        }
    }
    ...
}

классный

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'
        }
    }
    ...
}

R8 configuration files

R8 uses ProGuard rules files to modify its default behavior and better understand your app's structure, such as the classes that serve as entry points into your app's code. Although you can modify some of these rules files, some rules may be generated automatically by compile-time tools, such as AAPT2, or inherited from your app's library dependencies. The table below describes the sources of ProGuard rules files that R8 uses.

Источник Расположение Описание
Android Studio <module-dir>/proguard-rules.pro When you create a new module using Android Studio, the IDE creates a proguard-rules.pro file in the root directory of that module.

By default, this file does not apply any rules. So, include your own ProGuard rules here, such as your custom keep rules .

Android Gradle plugin Generated by the Android Gradle plugin at compile time. The Android Gradle plugin generates proguard-android-optimize.txt , which includes rules that are useful to most Android projects and enables @Keep* annotations .

By default, when creating a new module using Android Studio, the module-level build script includes this rules file in your release build for you.

Note: The Android Gradle plugin includes additional predefined ProGuard rules files, but it is recommended that you use proguard-android-optimize.txt .

Library dependencies

In an AAR library:
proguard.txt

In a JAR library:
META-INF/proguard/<ProGuard-rules-file>

In addition to these locations, Android Gradle plugin 3.6 or higher also supports targeted shrink rules .

If an AAR or JAR library is published with its own rules file, and you include that library as a compile-time dependency, R8 automatically applies those rules when compiling your project.

In addition to conventional ProGuard rules, Android Gradle plugin 3.6 or higher also supports targeted shrink rules . These are rules that target specific shrinkers (R8 or ProGuard), as well as specific shrinker versions.

Using rules files that are packaged with libraries is useful if certain rules are required for the library to function properly—that is, the library developer has performed the troubleshooting steps for you.

However, you should be aware that, because the rules are additive , certain rules that a library dependency includes cannot be removed and might impact the compilation of other parts of your app. For example, if a library includes a rule to disable code optimizations, that rule disables optimizations for your entire project.

Android Asset Package Tool 2 (AAPT2) After building your project with minifyEnabled true : <module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt AAPT2 generates keep rules based on references to classes in your app's manifest, layouts, and other app resources. For example, AAPT2 includes a keep rule for each Activity that you register in your app's manifest as an entry point.
Custom configuration files By default, when you create a new module using Android Studio, the IDE creates <module-dir>/proguard-rules.pro for you to add your own rules. You can include additional configurations , and R8 applies them at compile-time.

When you set the minifyEnabled property to true , R8 combines rules from all the available sources listed above. This is important to remember when you troubleshoot with R8 , because other compile-time dependencies, such as library dependencies, may introduce changes to the R8 behavior that you do not know about.

To output a full report of all the rules that R8 applies when building your project, include the following in your module's proguard-rules.pro file:

// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt

Targeted shrink rules

Android Gradle plugin 3.6 or higher supports libraries' rules that target specific shrinkers (R8 or ProGuard), as well as specific shrinker versions. This allows library developers to tailor their rules to work optimally in projects that use new shrinker versions, while allowing existing rules to continue to be used in projects with older shrinker versions.

To specify targeted shrink rules, library developers will need to include them at specific locations inside an AAR or JAR library, as described below.

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>

That means targeted shrink rules are stored in the META-INF/com.android.tools directory of a JAR or in the META-INF/com.android.tools directory inside classes.jar of an AAR.

Under that directory, there can be multiple directories with names in the form of r8-from-<X>-upto-<Y> or proguard-from-<X>-upto-<Y> to indicate which versions of which shrinker the rules inside the directories are written for. Note that the -from-<X> and -upto-<Y> parts are optional, the <Y> version is exclusive , and the version ranges must be continuous.

For example, r8-upto-8.0.0 , r8-from-8.0.0-upto-8.2.0 , and r8-from-8.2.0 form a valid set of targeted shrink rules. The rules under the r8-from-8.0.0-upto-8.2.0 directory will be used by R8 from version 8.0.0 up to but not including version 8.2.0.

Given that information, Android Gradle plugin 3.6 or higher will select the rules from the matching R8 directories. If a library does not specify targeted shrink rules, the Android Gradle plugin will select the rules from the legacy locations ( proguard.txt for an AAR or META-INF/proguard/<ProGuard-rules-file> for a JAR).

Library developers can choose to include either targeted shrink rules or legacy ProGuard rules in their libraries, or both types if they want to maintain compatibility with Android Gradle plugin older than 3.6 or other tools.

Include additional configurations

When you create a new project or module using Android Studio, the IDE creates a <module-dir>/proguard-rules.pro file for you to include your own rules. You can also include additional rules from other files by adding them to the proguardFiles property in your module's build script.

For example, you can add rules that are specific to each build variant by adding another proguardFiles property in the corresponding productFlavor block. The following Gradle file adds flavor2-rules.pro to the flavor2 product flavor. Now, flavor2 uses all three ProGuard rules because those from the release block are also applied.

Additionally, you can add the testProguardFiles property, which specifies a list of ProGuard files that are included in the test APK only:

Котлин

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")
        }
    }
}

классный

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'
        }
    }
}

Shrink your code

Code shrinking with R8 is enabled by default when you set the minifyEnabled property to true .

Code shrinking (also known as tree shaking), is the process of removing code that R8 determines is not required at runtime. This process can greatly reduce your app's size if, for example, your app includes many library dependencies but utilizes only a small part of their functionality.

To shrink your app's code, R8 first determines all entry points into your app's code based on the combined set of configuration files . These entry points include all classes that the Android platform may use to open your app's Activities or services. Starting from each entry point, R8 inspects your app's code to build a graph of all methods, member variables, and other classes that your app might access at runtime. Code that is not connected to that graph is considered unreachable and may be removed from the app.

Figure 1 shows an app with a runtime library dependency. While inspecting the app's code, R8 determines that methods foo() , faz() , and bar() are reachable from the MainActivity.class entry point. However, class OkayApi.class or its method baz() is never used by your app at runtime, and R8 removes that code when shrinking your app.

Figure 1. At compile-time, R8 builds a graph based on your project's combined keep rules to determine unreachable code.

R8 determines entry points through -keep rules in the project's R8 configuration files . That is, keep rules specify classes that R8 should not discard when shrinking your app, and R8 considers those classes as possible entry points into your app. The Android Gradle plugin and AAPT2 automatically generate keep rules that are required by most app projects for you, such as your app's activities, views, and services. However, if you need to customize this default behavior with additional keep rules, read the section about how to customize which code to keep .

If instead you are interested only in reducing the size of your app's resources, skip to the section about how to shrink your resources .

Note that if a library project is shrunk, an app that depends on that library includes shrunk library classes. You might need to adjust library keep rules if there are missing classes in the library APK. If you're building and publishing a library in AAR format, local JAR files that your library depends on aren't shrunk in the AAR file.

Customize which code to keep

For most situations, the default ProGuard rules file ( proguard-android-optimize.txt ) is sufficient for R8 to remove only the unused code. However, some situations are difficult for R8 to analyze correctly and it might remove code your app actually needs. Some examples of when it might incorrectly remove code include:

  • When your app calls a method from the Java Native Interface (JNI)
  • When your app looks up code at runtime (such as with reflection)

Testing your app should reveal any errors caused by inappropriately removed code, but you can also inspect what code was removed by generating a report of removed code .

To fix errors and force R8 to keep certain code, add a -keep line in the ProGuard rules file. Например:

-keep public class MyClass

Alternatively, you can add the @Keep annotation to the code you want to keep. Adding @Keep on a class keeps the entire class as-is. Adding it on a method or field will keep the method/field (and its name) as well as the class name intact. Note that this annotation is available only when using the AndroidX Annotations Library and when you include the ProGuard rules file that is packaged with the Android Gradle plugin, as described in the section about how to enable shrinking .

There are many considerations you should make when using the -keep option; for more information about customizing your rules file, read the ProGuard Manual . The Troubleshooting section outlines other common problems you might encounter when your code gets stripped away.

Strip native libraries

By default, native code libraries are stripped in release builds of your app. This stripping consists of removing the symbol table and debugging information contained in any native libraries used by your app. Stripping native code libraries results in significant size savings; however, it's impossible to diagnose crashes on the Google Play Console due to the missing information (such as class and function names).

Native crash support

The Google Play Console reports native crashes under Android vitals . With a few steps, you can generate and upload a native debug symbols file for your app. This file enables symbolicated native crash stack traces (that include class and function names) in Android vitals to help you debug your app in production. These steps vary depending on the version of the Android Gradle plugin used in your project and the build output of your project.

Android Gradle plugin version 4.1 or later

If your project builds an Android App Bundle, you can automatically include the native debug symbols file in it. To include this file in release builds, add the following to your app's build.gradle.kts file:

android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }

Select the debug symbol level from the following:

  • Use SYMBOL_TABLE to get function names in the Play Console's symbolicated stack traces. This level supports tombstones .
  • Use FULL to get function names, files, and line numbers in the Play Console's symbolicated stack traces.

If your project builds an APK, use the build.gradle.kts build setting shown earlier to generate the native debug symbols file separately. Manually upload the native debug symbols file to the Google Play Console. As part of the build process, the Android Gradle plugin outputs this file in the following project location:

app/build/outputs/native-debug-symbols/ variant-name /native-debug-symbols.zip

Android Gradle plugin version 4.0 or earlier (and other build systems)

As part of the build process, the Android Gradle plugin keeps a copy of the unstripped libraries in a project directory. This directory structure is similar to the following:

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
  1. Zip up the contents of this directory:

    cd app/build/intermediates/cmake/universal/release/obj
    zip -r symbols.zip .
    
  2. Manually upload the symbols.zip file to the Google Play Console.

Shrink your resources

Resource shrinking works only in conjunction with code shrinking. After the code shrinker removes all unused code, the resource shrinker can identify which resources the app still uses. This is especially true when you add code libraries that include resources—you must remove unused library code so the library resources become unreferenced and, thus, removable by the resource shrinker.

To enable resource shrinking, set the shrinkResources property to true in your build script (alongside minifyEnabled for code shrinking). Например:

Котлин

android {
    ...
    buildTypes {
        getByName("release") {
            isShrinkResources = true
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

классный

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

If you haven't already built your app using minifyEnabled for code shrinking, then try that before enabling shrinkResources , because you might need to edit your proguard-rules.pro file to keep classes or methods that are created or invoked dynamically before you start removing resources.

Customize which resources to keep

If there are specific resources you wish to keep or discard, create an XML file in your project with a <resources> tag and specify each resource to keep in the tools:keep attribute and each resource to discard in the tools:discard attribute. Both attributes accept a comma-separated list of resource names. You can use the asterisk character as a wild card.

Например:

<?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" />

Save this file in your project resources, for example, at res/raw/my.package.keep.xml . The build does not package this file into your app.

Note: Make sure to use a unique name for the keep file. When different libraries get linked together their keep rules would conflict otherwise, causing potential issues with ignored rules or unneeded kept resources.

Specifying which resources to discard might seem silly when you could instead delete them, but this can be useful when using build variants. For example, you might put all your resources into the common project directory, then create a different my.package.build.variant.keep.xml file for each build variant when you know that a given resource appears to be used in code (and therefore not removed by the shrinker) but you know it actually won't be used for the given build variant. It's also possible that the build tools incorrectly identified a resource as needed, which is possible because the compiler adds the resource IDs inline and then the resource analyzer might not know the difference between a genuinely referenced resource and an integer value in the code that happens to have the same value.

Enable strict reference checks

Normally, the resource shrinker can accurately determine whether a resource is used. However, if your code makes a call to Resources.getIdentifier() (or if any of your libraries do that—the AppCompat library does), that means your code is looking up resource names based on dynamically-generated strings. When you do this, the resource shrinker behaves defensively by default and marks all resources with a matching name format as potentially used and unavailable for removal.

For example, the following code causes all resources with the img_ prefix to be marked as used.

Котлин

val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)

Ява

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

The resource shrinker also looks through all the string constants in your code, as well as various res/raw/ resources, looking for resource URLs in a format similar to file:///android_res/drawable//ic_plus_anim_016.png . If it finds strings like this or others that look like they could be used to construct URLs like this, it doesn't remove them.

These are examples of the safe shrinking mode that is enabled by default. You can, however, turn off this "better safe than sorry" handling, and specify that the resource shrinker keep only resources that it's certain are used. To do this, set shrinkMode to strict in the keep.xml file, as follows:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:shrinkMode="strict" />

If you do enable strict shrinking mode and your code also references resources with dynamically-generated strings, as shown above, then you must manually keep those resources using the tools:keep attribute.

Remove unused alternative resources

The Gradle resource shrinker removes only resources that are not referenced by your app code, which means it will not remove alternative resources for different device configurations. If necessary, you can use the Android Gradle plugin's resConfigs property to remove alternative resource files that your app does not need.

For example, if you are using a library that includes language resources (such as AppCompat or Google Play Services), then your app includes all translated language strings for the messages in those libraries whether the rest of your app is translated to the same languages or not. If you'd like to keep only the languages that your app officially supports, you can specify those languages using the resConfig property. Any resources for languages not specified are removed.

The following snippet shows how to limit your language resources to just English and French:

Котлин

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

классный

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

When releasing an app using the Android App Bundle format, by default only languages configured on a user's device are downloaded when installing the app. Similarly, only resources matching the device's screen density, and native libraries matching the device's ABI are included in the download. For more information refer to the Android App Bundle configuration .

For legacy apps releasing with APKs (created before August 2021), you can customize which screen density or ABI resources to include in your APK by building multiple APKs that each target a different device configuration.

Merge duplicate resources

By default, Gradle also merges identically named resources, such as drawables with the same name that might be in different resource folders. This behavior is not controlled by the shrinkResources property and cannot be disabled, because it is necessary to avoid errors when multiple resources match the name your code is looking up.

Resource merging occurs only when two or more files share an identical resource name, type, and qualifier. Gradle selects which file it considers to be the best choice among the duplicates (based on a priority order described below) and passes only that one resource to the AAPT for distribution in the final artifact.

Gradle looks for duplicate resources in the following locations:

  • The main resources, associated with the main source set, generally located in src/main/res/ .
  • The variant overlays, from the build type and build flavors.
  • The library project dependencies.

Gradle merges duplicate resources in the following cascading priority order:

Dependencies → Main → Build flavor → Build type

For example, if a duplicate resource appears in both your main resources and a build flavor, Gradle selects the one in the build flavor.

If identical resources appear in the same source set, Gradle cannot merge them and emits a resource merge error. This can happen if you define multiple source sets in the sourceSet property of your build.gradle.kts file—for example if both src/main/res/ and src/main/res2/ contain identical resources.

Obfuscate your code

The purpose of obfuscation is to reduce your app size by shortening the names of your app's classes, methods, and fields. The following is an example of obfuscation using 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

While obfuscation does not remove code from your app, significant size savings can be seen in apps with DEX files that index many classes, methods, and fields. However, as obfuscation renames different parts of your code, certain tasks, such as inspecting stack traces, require additional tools. To understand your stacktrace after obfuscation, read the section about how to decode an obfuscated stack trace .

Additionally, if your code relies on predictable naming for your app's methods and classes—when using reflection, for example, you should treat those signatures as entry points and specify keep rules for them, as described in the section about how to customize which code to keep . Those keep rules tell R8 to not only keep that code in your app's final DEX but also retain its original naming.

Decode an obfuscated stack trace

After R8 obfuscates your code, understanding a stack trace is difficult (if not impossible) because names of classes and methods might have been changed. To obtain the original stack trace you should retrace the stack trace .

Code optimization

In order to optimize your app even further, R8 inspects your code at a deeper level to remove more unused code or, where possible, rewrite your code to make it less verbose. The following are a few examples of such optimizations:

  • If your code never takes the else {} branch for a given if/else statement, R8 might remove the code for the else {} branch.
  • If your code calls a method in only a few places, R8 might remove the method and inline it at the few call sites.
  • If R8 determines that a class has only one unique subclass, and the class itself is not instantiated (for example, an abstract base class only used by one concrete implementation class), then R8 can combine the two classes and remove a class from the app.
  • To learn more, read the R8 optimization blog posts by Jake Wharton.

R8 does not allow you to disable or enable discrete optimizations, or modify the behavior of an optimization. In fact, R8 ignores any ProGuard rules that attempt to modify default optimizations, such as -optimizations and -optimizationpasses . This restriction is important because, as R8 continues to improve, maintaining a standard behavior for optimizations helps the Android Studio team easily troubleshoot and resolve any issues that you might encounter.

Note that enabling optimization will change the stack traces for your application. For example, inlining will remove stack frames. See the section on retracing to learn how to obtain the original stack traces.

Impact on runtime performance

If shrinking, obfuscation, and optimization are all enabled, R8 will improve runtime performance of code (including startup and frame time on the UI thread) by up to 30%. Disabling any of these drastically limits the set of optimizations R8 uses.

If R8 is enabled, you should also create Startup Profiles for even better startup performance.

Enable improved optimizations

R8 includes a set of additional optimizations (referred to as "full mode") which makes it behave differently from ProGuard. These optimizations are enabled by default since Android Gradle plugin version 8.0.0 .

You can disable these additional optimizations by including the following in your project's gradle.properties file:

android.enableR8.fullMode=false

Because the additional optimizations make R8 behave differently from ProGuard, they may require you to include additional ProGuard rules to avoid runtime issues if you're using rules designed for ProGuard. For example, say that your code references a class through the Java Reflection API. When not using "full mode," R8 assumes that you intend to examine and manipulate objects of that class at runtime—even if your code actually does not—and it automatically keeps the class and its static initializer.

However, when using "full mode", R8 does not make this assumption and, if R8 asserts that your code otherwise never uses the class at runtime, it removes the class from your app's final DEX. That is, if you want to keep the class and its static initializer, you need to include a keep rule in your rules file to do that.

If you encounter any issues while using R8's "full mode", refer to the R8 FAQ page for a possible solution. If you are unable to resolve the issue, please report a bug .

Retracing stacktraces

Code processed by R8 is changed in various ways that can make stack traces harder to understand because the stack traces won't exactly correspond to the source code. This can be the case for changes to the line numbers when debugging information is not kept. It can be due to optimizations such as inlining and outlining. The largest contributor is obfuscation where even the classes and methods will change names.

To recover the original stack trace, R8 provides the retrace command-line tool, which is bundled with the command-line tools package .

To support retracing of your application's stack traces, you should ensure the build retains sufficient information to retrace with by adding the following rules to your module's proguard-rules.pro file:

-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile

The LineNumberTable attribute retains positional information in methods such that those positions are printed in stack traces. The SourceFile attribute ensures that all potential runtimes actually print the positional info. The -renamesourcefileattribute directive sets the source file name in stack traces to just SourceFile . The actual original source file name is not required when retracing because the mapping file contains the original source file.

R8 creates a mapping.txt file each time it runs, which contains the information needed to map stack traces back to the original stack traces. Android Studio saves the file in the <module-name> /build/outputs/mapping/ <build-type> / directory.

When publishing your app on Google Play, you can upload the mapping.txt file for each version of your app. When publishing using Android App Bundles this file is included automatically as part of the app bundle content. Then Google Play will retrace incoming stack traces from user-reported issues so you can review them in the Play Console. For more information, see the Help Center article about how to deobfuscate crash stack traces .

Troubleshoot with R8

This section describes some strategies for troubleshooting issues when enabling shrinking, obfuscation, and optimization using R8. If you do not find a solution to your issue below, also read the R8 FAQ page and ProGuard's troubleshooting guide .

Generate a report of removed (or kept) code

To help you troubleshoot certain R8 issues, it may be useful to see a report of all the code that R8 removed from your app. For each module for which you want to generate this report, add -printusage <output-dir>/usage.txt to your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of removed code looks similar to the following:

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
...

If instead you want to see a report of the entry points that R8 determines from your project's keep rules , include -printseeds <output-dir>/seeds.txt in your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of kept entry points looks similar to the following:

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
...

Troubleshoot resource shrinking

When you shrink resources, the Build window shows a summary of the resources that are removed from the app. (You need to first click Toggle view on the left side of the window to display detailed text output from Gradle.) For example:

:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning

Gradle also creates a diagnostic file named resources.txt in <module-name>/build/outputs/mapping/release/ (the same folder as ProGuard's output files). This file includes details such as which resources reference other resources and which resources are used or removed.

For example, to find out why @drawable/ic_plus_anim_016 is still in your app, open the resources.txt file and search for that file name. You might find that it's referenced from another resource, as follows:

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

You now need to know why @drawable/add_schedule_fab_icon_anim is reachable—and if you search upwards you'll find that resource is listed under "The root reachable resources are:". This means there is a code reference to add_schedule_fab_icon_anim (that is, its R.drawable ID was found in the reachable code).

If you are not using strict checking, resource IDs can be marked as reachable if there are string constants that look like they might be used to construct resource names for dynamically loaded resources. In that case, if you search the build output for the resource name, you might find a message like this:

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.

If you see one of these strings and you are certain that the string is not being used to load the given resource dynamically, you can use the tools:discard attribute to inform the build system to remove it, as described in the section about how to customize which resources to keep .