Как настраивать правила сохранения R8 и устранять неполадки с ними
Время на чтение: 7 минут
Современные пользователи ожидают, что приложения для Android будут небольшими, быстрыми и безопасными. Основной инструмент для этого в системе сборки Android – оптимизатор R8 . Это компилятор, который удаляет неиспользуемый код и ресурсы, переименовывает или минифицирует код и оптимизирует приложение.
Включение R8 – важный шаг при подготовке приложения к выпуску, но разработчикам необходимо предоставить инструкции в виде правил сохранения.
Прочитав эту статью, посмотрите видео о включении, отладке и устранении неполадок оптимизатора R8 на YouTube.
Зачем нужны правила хранения
Необходимость в правилах Keep возникает из-за фундаментального противоречия: R8 – это инструмент статического анализа, но приложения Android часто используют динамические шаблоны выполнения, такие как рефлексия или вызовы нативного кода и из него с помощью JNI (Java Native Interface).
R8 строит граф используемого кода, анализируя прямые вызовы. Если доступ к коду осуществляется динамически, статический анализ R8 не может это предсказать и идентифицирует такой код как неиспользуемый, а затем удаляет его, что приводит к сбоям во время выполнения.
Правило сохранения – это явная инструкция для компилятора R8, которая сообщает: "Этот класс, метод или поле – точка входа, к которой будет осуществляться динамический доступ во время выполнения. Вы должны сохранить его, даже если не можете найти прямую ссылку на него".
Подробную информацию о правилах сохранения можно найти в официальном руководстве.
Где создавать правила хранения
Специальные правила Keep для приложения записываются в текстовый файл. По умолчанию этот файл называется 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или-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.
Кроме того, многие библиотеки содержат собственные правила сохранения. Поэтому вам не нужно писать собственные правила для них. Если у вас возникли проблемы с правилами сохранения из используемой библиотеки, лучше всего обратиться к ее автору.
Рекомендации по использованию правил хранения
Теперь, когда вы знаете, чего делать не следует, давайте поговорим о рекомендациях.
Создавайте узкие правила хранения
Правила хранения должны быть максимально узкими и конкретными. Они должны сохранять только то, что необходимо, позволяя 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 | Что это дает |
-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, вы должны предоставить пользователям необходимые правила Keep. Эти правила помещаются в файл 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 выходит на новый уровень: от вспомогательного автозаполнения кода до агентов, выполняющих сквозные задачи под вашим руководством. Вы можете выбирать любые модели и агентов.
Jason Buchel • Время на чтение: 3 минуты -
Новости о продуктахСегодня мы рассказали, как планируем развивать концепцию "больше, чем магазин". Google Play доступен пользователям на разных платформах. Например, вкладка "Вы" – это персональный центр управления, которым ежемесячно пользуются более 250 миллионов активных пользователей за месяц. А наша единая игровая платформа объединяет более 700 миллионов игровых профилей.
Paul Feng • Время на чтение: 3 минуты -
Новости о продуктахМы рады, что Googlebook помогает пользователям выражать себя и получать доступ к этим инструментам.
Cyrus Stoller • Время на чтение: 2 минуты
Получайте еженедельную рассылку с последними новостями о разработке для Android.