Как настроить правила сохранения R8 и устранить неполадки с ними
Время на чтение: 7 минут
В современной разработке приложений для Android пользователи ожидают, что они будут небольшими, быстрыми и безопасными. Основной инструмент системы сборки Android для достижения этой цели – оптимизатор R8 , компилятор, который обрабатывает неиспользуемый код и удаляет ресурсы для сжатия, переименовывает код или выполняет его минимизацию и оптимизирует приложение.
Включение R8 – важный шаг при подготовке приложения к выпуску, но для этого разработчикам необходимо предоставить инструкции в виде правил сохранения.
После прочтения этой статьи посмотрите видео о том, как включить, отладить и устранить неполадки оптимизатора R8 на YouTube.
Зачем нужны правила хранения
Необходимость писать правила сохранения обусловлена основным конфликтом: R8 – это инструмент статического анализа, но приложения Android часто полагаются на динамические шаблоны выполнения, такие как отражение или вызовы в нативный код и из него с помощью JNI (Java Native Interface).
R8 создает граф используемого кода, анализируя прямые вызовы. Если код используется динамически, статический анализ R8 не может этого предсказать и определяет код как неиспользуемый, а затем удаляет его, что приводит к сбоям во время выполнения.
Правило сохранения – это явная инструкция для компилятора R8, в которой говорится: "Этот класс, метод или поле является точкой входа, к которой будет динамически обращаться во время выполнения. Вы должны сохранить его, даже если не можете найти прямую ссылку на него".
Подробнее о правилах хранения…
Где писать правила хранения
Специальные правила сохранения для приложения записываются в текстовый файл. По соглашению этот файл называется proguard-rules.pro и находится в корневом каталоге приложения или модуля библиотеки. Затем этот файл указывается в типе сборки release файла build.gradle.kts модуля.
release {
isShrinkResources = true
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}Используйте правильный файл по умолчанию
Метод getDefaultProguardFile импортирует набор правил по умолчанию, предоставленный Android SDK. Если вы используете неправильный файл, приложение может быть не оптимизировано. Используйте proguard-android-optimize.txt. Этот файл содержит правила сохранения по умолчанию для стандартных компонентов Android и включает оптимизацию кода R8. Устаревший код proguard-android.txt содержит только правила сохранения, но не включает оптимизацию R8.
Поскольку это серьезная проблема с производительностью, мы начали предупреждать разработчиков об использовании неправильного файла, начиная с Android Studio Narwhal 3 Feature Drop.Начиная с версии 9.0 плагина Android Gradle, мы больше не поддерживаем устаревший файл proguard-android.txt. Поэтому рекомендуем перейти на оптимизированную версию.
Как писать правила Keep
Правило хранения состоит из трех основных частей:
- Вариант , например
-keepили-keepclassmembers. - Необязательные модификаторы, например
allowshrinking. - Спецификация класса, определяющая код для сопоставления.
Полный синтаксис и примеры можно найти в инструкциях по добавлению правил хранения.
Антипаттерны использования правил Keep
Важно знать не только о лучших практиках, но и об антипаттернах. Эти антипаттерны часто возникают из-за недопонимания или упрощенного подхода к устранению неполадок и могут привести к катастрофическим последствиям для производительности рабочей сборки.
Общие настройки
Эти флаги представляют собой глобальные переключатели, которые никогда не должны использоваться в релизной сборке. Они предназначены только для временной отладки, чтобы выявить проблему.
Использование -dontotptimize фактически отключает оптимизацию производительности R8, что приводит к замедлению работы приложения.
При использовании -dontobfuscate отключается переименование, а при использовании -dontshrink – удаление неиспользуемого кода. Оба этих глобальных правила увеличивают размер приложения.
По возможности не используйте эти глобальные флаги в рабочей среде, чтобы повысить производительность приложения.
Слишком широкие правила хранения
Самый простой способ нивелировать преимущества R8 – написать слишком общие правила хранения. Правила, подобные приведенному ниже, указывают оптимизатору R8 не сжимать, не обфусцировать и не оптимизировать ни один класс в этом пакете или ни один из его подпакетов. Это полностью отключает преимущества R8 для всего пакета. Попробуйте создать более узкие и конкретные правила Keep.
-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMSОператор инверсии (!)
Оператор инверсии (!) позволяет исключить пакет из правила. Но все не так просто. Пример:
-keep class !com.example.my_package.** { *; } // USE WITH CAUTIONВы можете подумать, что это правило означает "не сохранять классы вcom.example.package". Но на самом деле оно означает "сохранять все классы, методы и свойства во всем приложении, которые не находятся в com.example.package". Если это стало для вас неожиданностью, проверьте, нет ли в конфигурации R8 отрицаний.
Избыточные правила для компонентов Android
Ещё одна распространенная ошибка – вручную добавлять правила Keep для файлов Activities, Services или BroadcastReceivers. Это необязательно. В файле proguard-android-optimize.txt по умолчанию уже есть правила, необходимые для работы стандартных компонентов Android.
Кроме того, многие библиотеки используют собственные правила хранения. Поэтому вам не нужно создавать собственные правила для этих случаев. Если у вас возникли проблемы с правилами сохранения из используемой библиотеки, лучше всего обратиться к автору библиотеки.
Рекомендации по использованию правил
Теперь, когда вы знаете, чего делать не следует, давайте поговорим о том, как правильно продвигать канал.
Как создавать точные правила хранения
Правила хранения должны быть максимально точными и узкими. Они должны сохранять только то, что необходимо, позволяя R8 оптимизировать все остальное.
| Правило | Качество |
|---|---|
| Низкий – сохраняет весь пакет и его подпакеты. |
| Низкая. Сохраняет весь класс, который, скорее всего, все ещё слишком широк. |
-keepclassmembers class com.example.MyClass {
private java.lang.String secretMessage;
public void onNativeEvent(java.lang.String);
} | Высокая. Сохраняются только релевантные методы и свойства из определенного класса. |
Используйте общих предков
Вместо того чтобы писать отдельные правила Keep для нескольких разных моделей данных, напишите одно правило, которое будет нацелено на общий базовый класс или интерфейс. Приведенное ниже правило указывает R8 сохранять все элементы классов, реализующих этот интерфейс, и является масштабируемым.
# Keep all fields of any class that implements SerializableModel
-keepclassmembers class * implements com.example.models.SerializableModel {
<fields>;
}Как использовать аннотации для нескольких классов
Создайте специальную аннотацию (например, @Serialize) и используйте ее для пометки классов, поля которых нужно сохранить. Это ещё один простой, декларативный и масштабируемый шаблон. Вы также можете создавать правила сохранения для существующих аннотаций из фреймворков, которые вы используете.
# Keep all fields of any class annotated with @Serialize
-keepclassmembers class * {
@com.example.annotations.Serialize <fields>;
}Как выбрать подходящий вариант хранения
Параметр "Сохранить" – самая важная часть правила. Если выбрать неправильный вариант, оптимизация может быть отключена без необходимости.
| Варианты сохранения | Что делает функция |
-keep | Предотвращает удаление или переименование класса и упомянутых в объявлении членов . |
-keepclassmembers | Запрещает удалять или переименовывать указанных участников, но позволяет удалить сам курс, если он не был удален другим способом. |
-keepclasseswithmembers | Комбинация: сохраняет класс и его члены, только если присутствуют все указанные члены. |
Подробнее о параметре "Сохранить" можно узнать из документации по вариантам хранения.
Разрешить оптимизацию с помощью модификаторов
Модификаторы, такие как allowshrinking и allowobfuscation, ослабляют общее правило -keep, возвращая R8 возможность оптимизации. Например, если устаревшая библиотека вынуждает вас использовать аннотацию -keep для всего класса, вы можете вернуть часть оптимизации, разрешив сжатие и обфускацию:
# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it. -keep,allowshrinking,allowobfuscation class com.example.LegacyClass
Как добавить глобальные параметры для дополнительной оптимизации
Помимо правил сохранения, вы можете добавить в файл конфигурации R8 глобальные флаги, чтобы повысить уровень оптимизации.
-repackageclasses – мощный параметр, который указывает R8 переместить все обфусцированные классы в один пакет. Это позволяет значительно сэкономить место в файле DEX за счет удаления избыточных строк с названиями пакетов.
-allowaccessmodification позволяет R8 расширить доступ (например, с private до public), чтобы включить более агрессивное встраивание. Теперь эта функция включена по умолчанию при использовании proguard-android-optimize.txt.
Предупреждение. Авторы библиотек никогда не должны добавлять эти флаги глобальной оптимизации в правила для потребителей, поскольку они будут принудительно применяться ко всему приложению.
Чтобы сделать это ещё понятнее, в версии 9.0 плагина Android Gradle мы начнем полностью игнорировать глобальные флаги оптимизации из библиотек.
Рекомендации для библиотек
Все приложения для Android так или иначе используют библиотеки. Поэтому давайте поговорим о том, как лучше всего использовать библиотеки.
Для разработчиков библиотек
Если в вашей библиотеке используются отражение или JNI, вы должны предоставить потребителям необходимые правила сохранения. Эти правила помещаются в файл consumer-rules.pro, который затем автоматически добавляется в AAR-файл библиотеки.
android {
defaultConfig {
consumerProguardFiles("consumer-rules.pro")
}
...
}Для потребителей библиотеки
Как отфильтровать проблемные правила хранения
Если вам нужно использовать библиотеку, в которой есть проблемные правила Keep Rules, вы можете отфильтровать их в файле build.gradle.kts, начиная с AGP 9.0. Это позволит R8 игнорировать правила, поступающие из определенной зависимости.
release {
optimization.keepRules {
// Ignore all consumer rules from this specific library
it.ignoreFrom("com.somelibrary:somelibrary")
}
}Лучшее правило хранения – это отсутствие правила хранения
Лучшая стратегия конфигурации R8 – избежать необходимости писать правила сохранения. Во многих приложениях этого можно добиться, выбрав современные библиотеки, в которых генерация кода предпочтительнее рефлексии. Благодаря генерации кода оптимизатор может проще определить, какой код используется во время выполнения, а какой можно удалить. Если вы не используете динамическое отражение, то у вас нет "скрытых" точек входа, а значит, правила сохранения не нужны. При выборе новой библиотеки всегда отдавайте предпочтение решению, которое использует генерацию кода, а не рефлексию.
Подробнее о том, как выбрать библиотеку…
Отладка и устранение неполадок в конфигурации R8
Если R8 удаляет код, который должен был остаться, или размер APK больше, чем ожидалось, используйте эти инструменты для диагностики проблемы.
Как найти повторяющиеся и глобальные правила Keep
Поскольку R8 объединяет правила из десятков источников, бывает сложно понять, каким будет окончательный набор правил. Добавьте этот флаг в файл proguard-rules.pro, чтобы создать полный отчет:
# Outputs the final, merged set of rules to the specified file -printconfiguration build/outputs/logs/configuration.txt
Вы можете выполнить поиск в этом файле, чтобы найти лишние правила или отследить проблемное правило (например, -dontoptimize) до определенной библиотеки, в которой оно было включено.
Ask R8: Why are you keeping this? (Спросите у R8: почему вы сохраняете это?)
Если класс, который должен быть удален, по-прежнему есть в приложении, R8 может объяснить, почему это произошло. Добавьте следующее правило:
# Asks R8 to explain why it's keeping a specific class class com.example.MyUnusedClass -whyareyoukeeping
Во время сборки R8 выводит точную цепочку ссылок, из-за которых класс был сохранен. Это позволяет отследить ссылку и изменить правила.
Полное руководство можно найти в разделе Устранение неполадок с R8.
Дальнейшие действия
R8 – это мощный инструмент для повышения производительности приложений Android. Его эффективность зависит от правильного понимания его работы как движка статического анализа.
Чтобы сохранить только нужные данные, создавайте правила на уровне участников, используйте предков и аннотации, а также тщательно выбирайте варианты сохранения. Самый современный подход – отказаться от правил, используя современные библиотеки на основе генерации кода вместо библиотек на основе отражения.
Посмотрите видео о неделе эффективности на YouTube и примите участие в челлендже R8. Если у вас возникли вопросы о том, как включить R8 или устранить неполадки, используйте тег #optimizationEnabled. Мы всегда рады помочь!
Пора узнать, какие преимущества дает подписка.
Мы рекомендуем уже сегодня включить полный режим R8 для своего приложения.
- Чтобы начать работу, ознакомьтесь с руководством для разработчиков: Как включить оптимизацию приложений.
- Проверьте, используете ли вы
proguard-android.txt, и замените его наproguard-android-optimize.txt. - Затем оцените результаты. Не просто чувствуйте разницу, а проверяйте ее. Чтобы оценить прирост производительности, адаптируйте код из нашего примера приложения для макротестирования на GitHub, чтобы измерить время запуска до и после.
Мы уверены, что это поможет вам повысить эффективность приложения.
Вы также можете задать вопросы в социальных сетях, используя тег #AskAndroid. Наши эксперты будут следить за форумом и отвечать на ваши вопросы в течение недели.
Завтра мы расскажем об оптимизации на основе профиля с помощью профилей базовой конфигурации и запуска, а также о том, как улучшилась производительность отрисовки Compose в последних версиях и на что следует обратить внимание при работе в фоновом режиме.
-
Новости продуктовУ разработчиков Android есть множество вариантов выбора агентов, больших языковых моделей (LLM), инструментов и интерфейсов командной строки (CLI), которые можно использовать для разработки приложений. Наша цель – помочь вам создавать красивые и качественные приложения для Android, независимо от того, как вы это делаете.
Simona Milanovic • Время на чтение: 4 минуты -
Новости продуктовМы постоянно расширяем возможности платформы подписок в Google Play, чтобы помочь вам развивать бизнес, адаптироваться к новым бизнес-моделям и привлекать пользователей.
Sheenam Mittal • Время на чтение: 4 минуты -
Новости продуктовВ прошлом году мы открыли Android Studio для любых моделей ИИ. Сегодня мы делаем следующий шаг и добавляем поддержку агентов для написания кода.
Matthew Warner • Время на чтение: 3 минуты
Получайте свежие новости о разработке приложений для Android на свою электронную почту каждую неделю.