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

Чтобы сделать ваше приложение максимально маленьким и быстрым, вам следует оптимизировать и минимизировать сборку выпуска с помощью 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: <library-dir>/proguard.txt

JAR-библиотеки: <library-dir>/META-INF/proguard/

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

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

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

Инструмент пакета ресурсов Android 2 (AAPT2) После сборки проекта с помощью minifyEnabled true : <module-dir>/build/intermediates/proguard-rules/debug/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 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-адресов, он не удаляет их.

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

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

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

Когда вы сокращаете ресурсы, Build В окне отображается сводная информация о ресурсах, удаленных из приложения. (Сначала вам нужно нажать «Переключить вид» в левой части окна для отображения подробного текстового вывода из Gradle.) Например:

:android:shrinkDebugResources
Removed unused resources: Binary 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 был найден в доступном коде).

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

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 , чтобы сообщить системе сборки о необходимости удалить ее, как описано в разделе о том, как это сделать. настроить, какие ресурсы следует сохранить .