Как настраивать правила сохранения R8 и устранять неполадки с ними
Время на чтение: 7 минут
Пользователи ожидают, что современные приложения для Android будут небольшими, быстрыми и безопасными. Основной инструмент для этого – оптимизатор R8 . Это компилятор, который удаляет неиспользуемый код и ресурсы, переименовывает или минифицирует код и оптимизирует приложение.
Включение R8 – важный шаг при подготовке приложения к выпуску, но разработчикам необходимо предоставить инструкции в виде правил сохранения.
После прочтения этой статьи посмотрите видео из серии Performance Spotlight Week о включении, отладке и устранении неполадок оптимизатора R8 на YouTube.
Зачем нужны правила хранения
Правила Keep нужны, потому что 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 только предоставляет правила Keep, но не включает оптимизацию R8.
Поскольку это серьезная проблема с производительностью, мы начинаем предупреждать разработчиков об использовании неправильного файла, начиная с Android Studio Narwhal 3 Feature Drop. Начиная с версии 9.0 плагина Android Gradle, мы больше не поддерживаем устаревший файл proguard-android.txt. Поэтому рекомендуем перейти на оптимизированную версию.
Как создавать правила хранения
Правило хранения состоит из трех основных частей:
- Вариант , например
-keepили-keepclassmembers. - Необязательные модификаторы, например
allowshrinking. - Спецификация класса, которая определяет код для сопоставления.
Полный синтаксис и примеры приведены в статье Как добавить правила хранения.
Антипаттерны правил хранения
Важно знать не только о лучших практиках, но и об антипаттернах. Эти антипаттерны часто возникают из-за недопонимания или попыток быстро устранить неполадки и могут катастрофически повлиять на производительность рабочей сборки.
Общие настройки
Эти параметры являются глобальными переключателями, которые никогда не следует использовать в конечной сборке. Они предназначены только для временной отладки, чтобы изолировать проблему.
Использование -dontotptimize отключает оптимизацию производительности R8, что приводит к замедлению работы приложения.
При использовании -dontobfuscate вы отключаете все переименования, а при использовании -dontshrink – удаление неиспользуемого кода. Оба этих глобальных правила увеличивают размер приложения.
По возможности не используйте эти глобальные флаги в рабочей среде, чтобы повысить производительность приложения.
Слишком широкие правила хранения
Самый простой способ свести на нет преимущества R8 – написать слишком широкие правила сохранения. Правила сохранения, подобные приведенному ниже, указывают оптимизатору R8, что не нужно сжимать, обфусцировать и оптимизировать любой класс в этом пакете или любом из его подпакетов. Это полностью отменяет преимущества R8 для всего пакета. Попробуйте создать более узкие и конкретные правила хранения.
-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
Ещё одна распространенная ошибка – вручную добавлять правила сохранения для Activities, Services или BroadcastReceivers приложения. Это необязательно. Файл proguard-android-optimize.txt по умолчанию уже содержит правила, необходимые для работы стандартных компонентов Android.
Кроме того, многие библиотеки используют собственные правила Keep. Поэтому вам не нужно писать собственные правила для них. Если у вас возникли проблемы с правилами сохранения из используемой библиотеки, лучше всего обратиться к ее автору.
Рекомендации по работе с правилами хранения
Теперь, когда вы знаете, чего делать не следует, давайте поговорим о рекомендациях.
Создавайте узкие правила хранения
Хорошие правила хранения должны быть максимально узкими и конкретными. Они должны сохранять только то, что необходимо, позволяя R8 оптимизировать все остальное.
| Правило | Качество |
|---|---|
| Низкий. Сохраняется весь пакет и его подпакеты. |
| Низкая. Сохраняется весь класс, который, скорее всего, слишком широк. |
-keepclassmembers class com.example.MyClass {
private java.lang.String secretMessage;
public void onNativeEvent(java.lang.String);
} | Высокий. Сохраняются только релевантные методы и свойства из определенного класса. |
Используйте общих предков
Вместо того чтобы писать отдельные правила сохранения для нескольких разных моделей данных, напишите одно правило, которое нацелено на общий базовый класс или интерфейс. Приведенное ниже правило указывает 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 Option | Что это дает |
-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, вы можете отфильтровать их в файле 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 больше, чем ожидалось, используйте эти инструменты для диагностики проблемы.
Как найти повторяющиеся и глобальные правила хранения
Поскольку R8 объединяет правила из десятков источников, бывает сложно понять, как выглядит итоговый набор правил. Добавьте этот флаг в файл proguard-rules.pro, чтобы сгенерировать полный отчет:
# Outputs the final, merged set of rules to the specified file -printconfiguration build/outputs/logs/configuration.txt
Вы можете выполнить поиск по этому файлу, чтобы найти лишние правила или отследить проблемное правило (например, -dontoptimize) до конкретной библиотеки, в которую оно включено.
Спросите 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. - Затем оцените эффективность. Не просто чувствуйте разницу, а проверяйте ее. Чтобы оценить, насколько повысилась производительность, адаптируйте код из нашего примера приложения Macrobenchmark на GitHub и измерьте время запуска до и после.
Мы уверены, что вы заметите значительное улучшение эффективности приложения.
Если у вас есть вопросы, задайте их в социальных сетях с хештегом #AskAndroid. Наши специалисты будут следить за обсуждением и отвечать на вопросы в течение недели.
Завтра мы поговорим об оптимизации на основе профилей с помощью профилей Baseline и запуска, расскажем, как улучшилась производительность отрисовки Compose за последние выпуски, и поделимся соображениями по поводу производительности фоновой работы.
-
Новости о продуктахСегодня на Саммите для разработчиков Android мы объявляем о широкой доступности многих инструментов и API, которые помогут вам преодолевать повседневные трудности и дадут больше гибкости при создании отличных приложений для Android.
Matthew McCullough • Время на чтение: 4 минуты -
Новости о продуктахAndroid Studio Rabbit 2 теперь стабильна и готова к использованию в рабочей среде. В этом выпуске представлены ключевые функции, которые помогут вам в разработке, например Bring Your Own Agent, новые функции XR Emulator, обновление LeakCanary, новый инструмент управления навыками агента, код-ревью с помощью ИИ и многое другое.
Matthew Warner • Время на чтение: 3 минуты -
Новости о продуктахПриложения, предназначенные для предстоящего выпуска Android, больше не смогут напрямую записывать данные в контакты с помощью разрешения WRITE_CONTACTS.
Roxanna Aliabadi Walker • Время на чтение: 6 минут
Получайте еженедельную рассылку с последними новостями о разработке для Android.