Система сборки платформы Android (Soong) запускает компилятор R8 для целей, которые компилируют байт-код DEX, включая приложения Android (android_app), тесты (android_test) и устанавливаемые библиотеки Java с compile_dex: true (например, services.jar), чтобы уменьшить, оптимизировать и удалить неиспользуемый код и ресурсы. В статических библиотеках (android_library и статической java_library) Soong не запускает R8 напрямую, а использует блок свойств optimize, чтобы прикреплять и распространять правила сохранения потребителей (export_proguard_flags_files: true) на целевые объекты DEX, которые статически связывают их.
Для разработчиков платформ, создающих образы систем или пакеты поставщиков, устранение слишком широких правил сохранения дает прямые преимущества для работоспособности системы:
- Меньший размер системного раздела. R8 удаляет неиспользуемые классы, методы и ресурсы перед упаковкой APK- и JAR-файлов в
/system,/system_ext,/productи/vendor. - Меньший размер скомпилированных артефактов. Чем меньше методов DEX, тем меньше размер файлов
.odexи.vdex, создаваемыхdex2oatво время компиляции на устройстве или при сборке. - Снижение нагрузки на оперативную память во время выполнения. Поскольку Android сопоставляет
.odex,.vdexи таблицы ресурсов APK с памятью процесса, используя вызовыmmapс подкачкой страниц по запросу, меньшие по размеру двоичные файлы снижают количество ошибок отсутствия страниц при запуске приложения и уменьшают объем резидентного кода и памяти ресурсов в процессах. Подробнее о том, как скомпилированный код приложения влияет на память устройства, рассказывается в разделе Код приложения – это память. - Более эффективная оптимизация всей программы. Ограничения на сохранение позволяют R8 встраивать методы, девиртуализировать интерфейсы и виртуальные вызовы, удалять неиспользуемые поля и распространять константы между классами.
Как настроить оптимизацию R8 в Soong
В файлах Android.bp настройте оптимизацию R8 с помощью блока свойств optimize для целевых объектов android_app и устанавливаемых объектов java_library (или для модулей android_library и статических модулей java_library, чтобы экспортировать правила сохранения для пользователей):
android_app {
name: "MySystemApp",
srcs: ["src/**/*.java"],
optimize: {
obfuscate: true,
shrink_resources: true,
},
}
В таблице ниже приведены наиболее распространенные свойства optimize в Soong (определены в build/soong/java/dex.go).
| Свойство | Описание |
|---|---|
enabled |
Определяет, запускается ли R8 на целевом устройстве. Значение по умолчанию для всех целей android_app – true.
|
shrink |
Управляет удалением недоступных классов, полей и методов. По умолчанию используется true для целей android_app (false для отдельных модулей java_library и тестовых модулей, которые передают -dontshrink, если не задано иное).
|
optimize |
Управляет оптимизацией байт-кода, например встраиванием методов, объединением классов, распространением констант и удалением неиспользуемых ветвей. По умолчанию имеет значение true для целей android_app (управляется флагом конечной сборки RELEASE_R8_OPTIMIZE_BY_DEFAULT).
|
obfuscate |
Управляет минификацией и переименованием идентификаторов. По умолчанию используется значение false для совместимости с предыдущими версиями, но по возможности задайте значение true, чтобы уменьшить размер DEX-файла и получить доступ к более глубокой оптимизации (см. раздел По возможности включите обфускацию).
|
shrink_resources |
Удаляет неиспользуемые ресурсы (res/ записей) из упакованного APK-файла после сокращения кода. Значение по умолчанию – false.
|
optimized_shrink_resources |
Запускает интегрированный конвейер R8 для сокращения кода и ресурсов, чтобы R8 отслеживал ссылки на код и ресурсы за один проход. Если задано значение shrink_resources: true, по умолчанию используется RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT (true в стандартных сборках платформы).
|
proguard_flags_files |
Перечисляет файлы .flags или .pro, относящиеся к модулю и содержащие специальные правила сохранения. Не добавляйте собственные файлы, если достаточно стандартных аннотаций или значений по умолчанию.
|
export_proguard_flags_files |
Передает proguard_flags_files этой библиотеки в дочерние модули, которые зависят от нее статически.
|
trace_references_from |
Перечисляет прямые зависимые цели компиляции DEX (подключенные через instrumentation_for или libs), ссылки на байт-код которых в этой цели R8 автоматически отслеживает и сохраняет (также распространяется на варианты override_android_app и override_android_test и поддерживается для независимых сопутствующих библиотек Java в паре с разделением .impl).
|
allow_traced_references |
Позволяет зависимым целям, которые компилируются напрямую в DEX (подключены через instrumentation_for или libs, например тесты поставщиков или тесты, которые выполняются в разных хранилищах и не могут быть указаны в trace_references_from), включить трассировку ссылок на байт-код в этой цели, если задано значение trace_references: true.
|
trace_references |
Если для этого параметра задано значение true в зависимом модуле, который компилируется напрямую в DEX и относится к целевому модулю с параметром allow_traced_references: true, то в этом модуле запускается R8 tracereferences, а сгенерированные правила сохранения передаются в целевой модуль.
|
protect_api_surface |
Передает --protect-api-surface в R8. Если задать это свойство для java_sdk_library, оно также автоматически сгенерирует и прикрепит правила сохранения Metalava API во всех включенных областях API (общедоступной, системной, module-lib, system-server и тестовой) к библиотеке реализации.
|
Статические библиотеки (android_library, java_library и импортированные файлы AAR) также могут задавать required_downstream_optimization: { enabled: true, optimize: true } в Android.bp, чтобы требовать от любой последующей цели компиляции DEX, которая статически связывает их (напрямую или транзитивно), включать сокращение R8 и оптимизацию байт-кода. Soong автоматически включает эту функцию в androidx.compose.runtime_runtime, поэтому цели, связанные с Jetpack Compose, оптимизируются с помощью R8.
Включите обфускацию везде, где это возможно.
В Soong для obfuscate по умолчанию задано значение false для обратной совместимости, но вы должны явно задать obfuscate: true для android_app и отдельных целей DEX, где это возможно:
- Уменьшение размера DEX и
.vdex. Переименование пакетов, классов, полей и методов в короткие идентификаторы (a,b) уменьшает пул строк DEX (string_idsиstring_data_item) и дескрипторы типов. Поскольку Android сопоставляет файлы.vdexи.odexс памятью процесса, уменьшение таблиц символов напрямую сокращает как размер системного раздела, так и объем потребляемой памяти во время выполнения. - Более глубокая оптимизация всей программы. Если разрешить R8 переименовывать идентификаторы, это позволит объединять классы, выравнивать пакеты и дедуплицировать синтетические классы в пакетах, которые R8 в противном случае пришлось бы пропустить, чтобы избежать конфликтов имен.
- Полная символизация трассировки стека. Включение обфускации в Soong не снижает возможности отладки. Для каждой цели, скомпилированной с помощью R8, Soong создает файл сопоставления
proguard_dictionaryв промежуточном каталоге модуля, объединяет все словари модулей в артефакт сборкиproguard-dict.zip, встраивает хеш сопоставления (--map-id-template) в заголовок DEX и перезаписывает атрибутыSourceFileкласса (--source-file-template), чтобыretraceмог автоматически добавлять символы в трассировки стека.
Сохранять obfuscate: false (или явно сохранять имена общедоступных API) только для:
- Общие библиотеки в bootclasspath или
system_serverclasspath. Модули (java_libraryилиjava_sdk_library), которые предоставляют внешний API, динамически связанный с другими модулями во время выполнения (например, целиframework.jar,services.jarили<uses-library>), должны либо сохранятьobfuscate: false, либо явно сохранять свой API с помощью правилprotect_api_surface: true,@KeepForApiили-keep. Это позволит вызывающим объектам, скомпилированным с использованием заглушек, разрешать имена классов и элементов во время выполнения. В модуляхjava_sdk_libraryнастройкаprotect_api_surface: trueавтоматически генерирует и прикрепляет правила сохранения Metalava ProGuard ко всем включенным областям API (общедоступным, системным, module-lib, system-server и тестовым) в библиотеке реализации. Когда обобщенная оптимизация R8 включена для необновляемых целей bootclasspath (framework-minus-apex,extиframework-location), они также включаютframework-hiddenapi-proguard-rules(созданныйhiddenapi_to_proguard), чтобы сохранить элементы, перечисленные в активных файлахboot/hiddenapi/*.txt(безhiddenapi-max-target-o.txt).
Прежде чем включать obfuscate: true в приложении, убедитесь, что выполнены следующие условия:
- Зависимости внешних тестовых APK. Если внешний APK-файл
android_testзадаетinstrumentation_for: "MyApp"и напрямую вызывает внутренние классы или методыMyApp, переименование этих символов приводит к ошибкеNoSuchMethodErrorилиNoClassDefFoundErrorво время выполнения теста. Настройтеtrace_references_from(илиallow_traced_referencesсtrace_references: true), структурируйте тест как самоинструментируемый APK, статически связывающийMyApp.impl, или добавьте аннотации к тестовым хукам с помощью@VisibleForTesting(см. Шаг 2. Перенесите правила сохранения, управляемые тестами), чтобы внутренние символы, необходимые для тестов, сохранялись при обфускации остальной части приложения. - Рефлексия на основе строк и JNI. Если модуль ищет классы, методы или поля по строковым литералам (
Class.forName,getDeclaredMethodили JNIFindClassиGetMethodID) без правил сохранения или аннотаций, обфускация переименовывает эти цели и нарушает поиск во время выполнения. (Обратите внимание, что неаннотированная рефлексия также не работает вshrink: trueиoptimize: true). Добавьте к этим точкам входа аннотации, следуя руководству по сохранению аннотаций (@UsesReflection,@UsedByReflectionили@UsedByNative), чтобы R8 сохранил их имена и обфусцировал остальную часть модуля.
Принцип нулевого количества специальных пометок
В идеальном модуле платформы Android нет пользовательских файлов proguard.flags или keep.xml. В сборке платформы Android подавляющее большинство пользовательских правил сохранения избыточны:
- Базовые правила платформы и AAPT2. Большинство точек входа уже автоматически сохраняются базовыми правилами платформы (например,
@Keep, методы JNInativeи@VisibleForTesting) или генерируются AAPT2 изAndroidManifest.xmlи ресурсов макета (см. раздел Правила сохранения платформы по умолчанию). - Целевые альтернативы. Если точки входа необходимо сохранить, используйте аннотации на уровне объявления (
keepanno,@VisibleForTesting), экспортированные правила библиотеки (export_proguard_flags_files: true) или статическое связывание тестов вместо отдельных файлов.flags(см. раздел Проверка и перенос существующих правил сохранения).
Прежде чем добавлять или сохранять собственный файл proguard.flags или keep.xml, проверьте, не обрабатывается ли правило базовыми конфигурациями по умолчанию и можно ли перенести его в аннотации кода.
Правила хранения по умолчанию для платформы
Soong автоматически передает следующие глобальные базовые правила каждому вызову R8 для целевых объектов компиляции DEX (android_app, android_test и устанавливаемые модули java_library), настроенных в build/soong/java/dex.go:
build/make/core/proguard.flags. Сохраняет классы и элементы, аннотированные с помощью@com.android.internal.annotations.VisibleForTestingво всех пакетах и с помощью@VisibleForTesting(androidx.annotation.VisibleForTestingилиcom.google.common.annotations.VisibleForTesting) в пакетахandroid.**,com.android.**иcom.google.android.**. Также сохраняет:@TestApi,@Keep(androidx.annotation,android.support.annotation,com.android.internal.annotations),@KeepForWeakReference,@WeaklyReferencedCallbackи@dalvik.annotation.optimization.**.build/make/core/proguard_basic_keeps.flags: сохраняет атрибутыSourceFileдля трассировки стека, аннотации видимости среды выполнения (RuntimeVisible*Annotations), атрибутыExceptionsиAnnotationDefault, методыnative, элементыSerializable, методы@JavascriptInterface, конструкторыThrowable(String), поляParcelable$CREATORи поля protobufMessageLite.build/make/core/proguard/kotlin.flags. Отключает безвредные предупреждения для определенных метааннотаций Kotlin (kotlin.Metadataиkotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) и удаляет аннотации KotlinDebugMetadataв конечных сборках.build/make/core/proguard/checknotnull.flags. Заменяет распространенные вызовы вспомогательных функций для проверки на null (com.google.common.base.Preconditions.checkNotNullиdagger.internal.Preconditions.checkNotNull*) на краткие проверки на null в байт-коде. В этом файле намеренно отсутствуетObjects.requireNonNull, чтобы сохранить явные сообщения об исключениях в границах API фреймворка.build/make/core/proguard/enumvalues.flags. Сохраняет методыvaluesиvalueOfдля типовenum, если они не отключены в конфигурации сборки.- Автоматически созданные правила AAPT2. AAPT2 проверяет объединенные файлы
AndroidManifest.xml, XML-файлы макетов и XML-файлы настроек, чтобы создать точные правила сохранения для каждого зарегистрированного классаActivity,Service,BroadcastReceiver,ContentProvider,BackupAgent,Application, пользовательского подклассаView, подклассаPreferenceи метода XMLandroid:onClick.
Проверьте и перенесите существующие правила хранения
При аудите существующих файлов proguard.flags или keep.xml в репозитории платформы оценивайте каждое правило по порядку в соответствии со следующей четырехуровневой иерархией:
Шаг 1. Удалите лишние или устаревшие правила
Удалите правила, которые уже охвачены глобальными базовыми показателями, манифестом AAPT2 и правилами макета или настройками R8 по умолчанию, а также правила, ссылающиеся на классы или пакеты, которых больше не существует.
Если все правила в файле .flags избыточны, удалите его и уберите proguard_flags_files из Android.bp. Если оставшийся блок optimize только повторяет настройки по умолчанию, удалите его и отформатируйте файл сборки с помощью bpfmt -w Android.bp. При очистке пакета проверьте, ссылаются ли сопутствующие модули в подкаталогах (например, целевые варианты Kotlin) на один и тот же файл флагов, и обновите их вместе.
Шаг 2. Перенесите правила хранения, созданные в тестовом режиме
Перенесите правила сохранения, основанные на тестах, из рабочих файлов .flags, используя один из следующих шаблонов:
Отслеживание ссылок из тестовых или сопутствующих целей (
trace_references_fromилиallow_traced_references). Если цель и напрямую зависимый модуль компилируют собственный DEX (android_app,android_testилиjava_libraryс DEX) и связаны черезinstrumentation_forилиlibs, Soong может запустить R8tracereferencesв зависимом модуле и передать сгенерированные правила сохранения в компиляцию R8 цели, не создавая цикл зависимостей и не требуя разделения библиотеки.implвручную.Тесты в том же хранилище (
trace_references_from). Перечислите прямые зависимостиinstrumentation_forилиlibsот целевого модуля. Это также автоматически распространяется на вариантыoverride_android_appиoverride_android_testи продолжает поддерживать независимые сопутствующие библиотеки Java в паре с разделением.impl.android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { obfuscate: true, shrink_resources: true, trace_references_from: ["MyAppTests"], }, } android_test { name: "MyAppTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], }Тесты, относящиеся к разным хранилищам или поставщикам (
allow_traced_referencesиtrace_references). Если целевой модуль не может указать зависимый модуль непосредственно вtrace_references_from(например, тесты находятся в другом хранилище или дереве поставщика), задайтеallow_traced_references: trueв целевом модуле и включите его из каждого зависимого модуля с помощьюtrace_references: true:android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { allow_traced_references: true, }, } android_test { name: "MyAppVendorTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], optimize: { trace_references: true, }, }Рефлексия на основе строк в тестах.
tracereferencesпроверяет ссылки на пул констант байт-кода и не может обнаружить рефлексию на основе строк в тестах (Class.forName,getDeclaredField,getDeclaredMethodили вспомогательные средства внедрения через поле). Добавьте аннотацию@VisibleForTesting(или@UsedByReflection) к полям и методам рабочей версии, к которым осуществляется доступ через рефлексию.
Свяжите библиотеку реализации с тестами (
static_libs). Если вы не хотите, чтобы в рабочем APK-файле оставались точки входа, предназначенные только для тестирования, и чтобы изменения в тестах повторно запускали R8 для рабочей цели, поместите исходные файлы приложения вandroid_library(MyApp.impl) и статически свяжитеMyApp.implсMyAppиMyAppTests.android_library { name: "MyApp.impl", srcs: ["src/**/*.java"], manifest: "AndroidManifest.xml", } android_app { name: "MyApp", static_libs: ["MyApp.impl"], optimize: { obfuscate: true, shrink_resources: true, }, } android_test { name: "MyAppTests", srcs: ["tests/**/*.java"], static_libs: ["MyApp.impl"], }Такая схема из трех модулей позволяет запускать
MyAppTestsкак самоинструментируемый тест с полным доступом к внутренним классам, аMyApp– сжимать и обфусцировать только точки входа рабочей версии.Добавьте к тестовым хукам аннотацию
@VisibleForTesting. Если внешний тестовый APK-файл вызывает только несколько внутренних методов или конструкторов в целевом объектеandroid_app, вы можете добавить к этим объявлениям аннотацию@VisibleForTesting. Глобальный базовый планbuild/make/core/proguard.flagsавтоматически сохраняет@VisibleForTestingобъектов в пакетахandroid.**,com.android.**иcom.google.android.**без специальных файлов.flags. Для модулей поставщиков за пределами этих пространств имен используйте правилаtrace_references_from,@UsedByReflectionили экспортируемой библиотеки. Если вы вручную добавляете аннотации@VisibleForTestingвместоtrace_references_fromилиtrace_references, также добавляйте аннотации к классам, переданным в Mockitomock(...)илиspy(...)(а не только к методам-заглушкам), потому чтоmockito-target(dexmaker-mockito) на устройстве генерирует подклассы среды выполнения, а R8 может помечать классы без подклассов какfinal(MockitoException: Can't mock/spy class ... - final class).Избегайте пересекающихся статических библиотек или перепаковывайте их (
jarjar_rules). Если иandroid_app, и тестовый APK-файлinstrumentation_forстатически связывают одну и ту же библиотеку (напрямую, напримерguava, или транзитивно),tracereferencesвидит эти классы, определенные в тесте, и не выдает правил сохранения для приложения. Во время выполнения родительский загрузчик классов приложения затеняет копию теста своими классами, сжатыми с помощью R8, что приводит к ошибкеIllegalAccessErrorилиNoSuchMethodError. Если для теста не требуется собственная копия, удалите дубликат изstatic_libsтеста (или свяжите его черезlibs), чтобы избежать конфликта. В противном случае перепакуйте пересекающиеся пакеты статических библиотек приложения с префиксом модуля, используяjarjar_rules.
Шаг 3. Экспортируйте правила из библиотеки-владельца
Если для общей библиотеки java_library или android_library требуются правила сохранения для собственной внутренней рефлексии или обратных вызовов JNI, определите правила для целевой библиотеки и задайте export_proguard_flags_files: true:
java_library {
name: "my-shared-library",
srcs: ["src/**/*.java"],
optimize: {
proguard_flags_files: ["proguard.flags"],
export_proguard_flags_files: true,
},
}
Все последующие цели android_app и библиотеки, которые статически связывают my-shared-library, автоматически наследуют эти правила, поэтому последующим приложениям не нужно их дублировать. Если несколько модулей в разных каталогах должны использовать один набор правил, не привязанный к одной библиотеке кода, опубликуйте файл .flags с помощью явного filegroup в Android.bp, чтобы модули могли ссылаться на :my-shared-flags за пределами пакета. Общие рекомендации по созданию правил сохранения для потребителей библиотек, не ограничивающих оптимизацию приложений, приведены в разделе Оптимизация для авторов библиотек.
Шаг 4. Добавьте аннотации к объявлениям в исходном коде
Если доступ к классу, методу, конструктору или полю осуществляется через рефлексию или JNI и не охватывается AAPT2 или глобальными базовыми показателями, замените отдельные правила -keep в файлах .flags аннотациями источника из руководства по сохранению аннотаций (см. справочник по Javadoc для keepanno):
@UsesReflection. Используйте эту аннотацию, если вы управляете кодом, выполняющим рефлексию. Разместите ее в месте вызова рефлексии, чтобы объявить, к каким целевым классам, методам или полям осуществляется динамический доступ. Поскольку@UsesReflectionавтоматически кодирует предварительное условие, согласно которому само аннотированное место вызова доступно, R8 удаляет как вызывающий объект, так и целевой объект отражения, если место вызова не используется.@UsedByReflection. Используйте для классов, методов, полей или конструкторов, которые создаются или вызываются рефлексивно внешним кодом или библиотеками, например для классов, загруженных с помощьюClass.forNameиз ключейBundleилиSettings, или классов плагинов, загруженных через динамические загрузчики классов. Укажитеkind(например,KeepItemKind.CLASS_AND_METHODS),preconditions(@KeepCondition) и ограничения параметров, чтобы R8 сохранял только точный контракт рефлексии и мог оптимизировать или удалять неиспользуемые элементы класса. Не добавляйте@UsedByReflectionв компоненты, зарегистрированные в манифесте, такие какJobServiceилиBroadcastReceiver, потому что AAPT2 уже сохраняет их.@UsedByNative– применяется к методам или полям, к которым обращается код JNI на C или C++ с помощьюGetMethodID,GetStaticMethodIDилиGetFieldID.@KeepForApi. Применяется к классам или элементам API библиотеки, которые должны оставаться неизменными, когда библиотека сжимается перед распространением.@Keep(androidx.annotation.Keep): используйте как резервный вариант, еслиkeepannoне подходит. Применение@Keepк классу сохраняет класс и все его элементы без условий. Применение@Keepк методу или полю действует как безусловная точка входа, которая сохраняет элемент и содержащий его класс, даже если экземпляр класса никогда не создается, тогда какkeepannoвыражает условную доступность.
Чтобы использовать аннотации R8 keepanno в модуле Soong:
Добавить
"keepanno-annotations"вlibs(Android.bp):libs: [ "keepanno-annotations", ],Импортируйте
com.android.tools.r8.keepanno.annotations.*и добавьте аннотацию к объявлению или месту вызова в исходном коде Java или Kotlin:import com.android.tools.r8.keepanno.annotations.KeepItemKind; import com.android.tools.r8.keepanno.annotations.UsedByReflection; public final class CustomPluginController { @UsedByReflection( description = "Instantiated via Class.forName from plugin config", kind = KeepItemKind.CLASS_AND_METHODS) public CustomPluginController(Context context) { // ... } }R8 преобразует аннотации
keepannoнепосредственно во внутреннюю модель правил сохранения и удаляет аннотации из конечного вывода DEX, не добавляя накладных расходов на байт-код во время выполнения.
Как избежать распространенных ошибок
В следующих разделах описаны распространенные ошибки, связанные с proguard.flags и keep.xml в коде платформы, и способы их устранения.
Общие правила сохранения компонентов
# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
- Почему это снижает производительность. Это правило полностью дублирует AAPT2.
Поскольку в нем используется подстановочный знак без проверки того, зарегистрирован ли компонент в окончательном объединенном файле
AndroidManifest.xml, R8 сохраняет каждый подклассActivity,ServiceилиBroadcastReceiver, найденный в пути к классам, включая неиспользуемые компоненты библиотеки и отключенные действия отладки. - Рекомендуемое решение. Удалите правило. AAPT2 проверяет объединенный манифест и генерирует точные правила
-keepдля зарегистрированных компонентов.
Правила сохранения обработчика кликов Broad XML
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- Почему это снижает производительность. Сохранение каждого метода
public void *(View)во всех классах модуля не позволяет R8 удалять или встраивать методы, которые принимают один параметрView. - Рекомендуемое решение. Удалите правило. AAPT2 сканирует XML-файлы макета и генерирует целевые правила сохранения для методов, на которые ссылаются атрибуты
android:onClick.
Правила сохранения методов получения и установки Broad View
# Don't do this:
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
public *** get*();
}
- Почему это снижает производительность. Это правило заставляет R8 сохранять все методы получения и установки для каждого подкласса
Viewв приложении и всех связанных библиотеках (включая библиотеки AndroidX и Material), блокируя удаление неиспользуемых методов и встраивание в код пользовательского интерфейса. - Рекомендуемое решение. Удалите правило. AAPT2 уже сохраняет конструкторы для пользовательских классов
View, созданных из XML-файлов макета. Если код анимирует свойство представления, используя рефлексивные строковые имена, напримерObjectAnimator.ofFloat(view, "translationZ", ...), замените строковое имя ссылкой на типизированное свойство (View.TRANSLATION_Zили пользовательская реализацияFloatPropertyилиIntProperty), чтобы полностью избежать рефлексии. Если избежать рефлексивного доступа к свойству невозможно, добавьте к соответствующему методу получения или установки аннотацию@UsedByReflection.
Полное отключение оптимизации или обфускации
# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
- Почему это снижает производительность. Размещение
-dontoptimizeили-dontshrinkвнутри файла.flagsавтоматически переопределяет настройкиAndroid.bpмодуля и отключает оптимизацию для всей цели. Хуже того, если библиотека экспортирует файл.flags, содержащий-dontoptimize, это отключает оптимизацию R8 для каждого последующегоandroid_app, который ссылается на библиотеку. - Рекомендуемое исправление. Удалите
-dontoptimize,-dontshrinkи-dontobfuscateиз файлов.flags. Явно задайте поведение оптимизации вAndroid.bp, используя блокoptimize(shrink,optimize,obfuscate) в конечной цели.
Флаги диагностики в зафиксированных правилах
# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
- Почему это мешает сборке. Флаги диагностики переполняют журналы сборки или пытаются записать выходные файлы в локальные пути во время изолированных сборок Soong.
- Рекомендуемое исправление. Удалите эти флаги из зафиксированных файлов
.flags. Soong автоматически записывает выходные данные сопоставления и использования R8, такие какproguard_dictionaryиproguard_usage.zip, в промежуточный каталог модуля вout/soong/.intermediates/.
Правила сохранения рабочей версии для тестового кода
- Почему это снижает производительность. Если вы добавите специальные правила
-keepтолько для доступа к тестам, код останется необфусцированным и будет сохранен в рабочих сборках. Настройкаtrace_references_from(илиallow_traced_referencesсtrace_references: true) или аннотирование методов с помощью@VisibleForTestingпозволяет избежать поддержки правил-keepвручную и сохранить только то, на что ссылаются тесты. Однако эти символы по-прежнему поставляются в рабочем двоичном файле. - Рекомендуемое исправление. Удалите специальные правила
-keepдля инструментального теста, включивtrace_references_from(илиallow_traced_referencesиtrace_references: true), или структурируйте приложение в библиотеку.impl, связанную с самоинструментируемым тестовым APK-файлом, используяstatic_libs, чтобы полностью исключить точки входа, предназначенные только для тестирования, из рабочего APK-файла, как описано в разделе Шаг 2. Перенесите правила сохранения, управляемые тестированием.
Общие подстановочные знаки ресурсов в файле keep.xml
<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/*,@drawable/*,@string/*" />
- Почему это снижает производительность. Подстановочные знаки сохраняют все ресурсы соответствующего типа в модуле и его зависимостях, что мешает удалению неиспользуемых ресурсов (
shrink_resources: true) и увеличивает размер APK и сопоставленной таблицыresources.arsc. - Рекомендуемое исправление:
- Используйте ссылки на статические ресурсы вместо
keep.xml. Не применяйте методResources.getIdentifierдля динамического поиска ограниченного набора ресурсов, например пронумерованных строк эксперимента или тематических графических объектов. Вместо этого используйте операторswitchвремени компиляции или сопоставьте со статическими константамиR.id,R.stringилиR.drawable. Статические ссылки позволяют R8 и AAPT2 отслеживать точные активные ресурсы, устранять накладные расходы на поиск строк во время выполнения и полностью исключать необходимость вkeep.xml. - Укажите названия конкретных ресурсов. Если требуется динамический поиск, например сторонним средством чтения лицензий, укажите точные идентификаторы ресурсов в
tools:keep(например,tools:keep="@raw/third_party_licenses"). - Удалите лишние файлы
keep.xml. Если на перечисленные ресурсы уже есть статические ссылки в коде (R.raw.foo) или XML (@raw/foo), инструмент сжатия сохранит их автоматически, поэтому вы можете удалитьres/raw/keep.xml.
- Используйте ссылки на статические ресурсы вместо
Как проверить и проанализировать изменения правил
Каждый раз, когда вы удаляете или сужаете правила сохранения в proguard.flags или keep.xml, убедитесь, что модуль собирается без ошибок, проходит модульные и инструментальные тесты и сохраняет все необходимые точки входа.
Создание и тестирование модулей
Выполните чистую сборку модуля, чтобы убедиться, что R8 и AAPT2 завершают работу без предупреждений об отсутствующих ссылках:
m <MODULE_NAME>Запустите модульные и инструментальные тесты для модуля с помощью
atest:atest <TEST_MODULE_NAME>Для системных приложений, привилегированных сервисов и модулей, зависящих от аппаратного обеспечения, запустите инструментальные тесты и тесты пользовательского интерфейса на целевых физических устройствах или в лаборатории тестирования устройств. Это позволит проверить пути рефлексии во время выполнения, привязки IPC и инфляцию ресурсов.
Просматривайте различия в DEX-файлах и ресурсах.
Сравните скомпилированный APK- или JAR-файл до и после изменения правил, чтобы убедиться, что R8 удаляет неиспользуемый код и ресурсы, не затрагивая ожидаемые точки входа.
Чтобы проверить, какие классы, методы и поля остались в выходных файлах APK или DEX, используйте
apkanalyzerилиdexdump.apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkПри изменении
keep.xmlили включенииshrink_resourcesиспользуйтеaapt2 dump resources, чтобы убедиться, что сборка удаляет неиспользуемые ресурсы и сохраняет необходимые:aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkПри запуске
prebuilts/r8/r8.jarнепосредственно в файлеclasses.jarмодуля до R8 для проверки изменений флагов в офлайн-режиме без полной сборки платформы передайте JAR-файлы пути к классам--libмодуля (android.jar,framework.jarи общийlibs) и правилаproguard.options, сгенерированные AAPT2, изAndroidManifest.xmlи XML-файлов макета. Без этих входных данных R8 не может разрешить супертипы фреймворка (Activity,Service,Phone,Handler) и недостаточно сохраняет методы иерархии.
Анализ радиуса сохранения и поглощения правил с помощью R8
В компилятор R8 с открытым исходным кодом встроен анализатор Keep Radius (в Android Studio и Gradle он называется анализатором конфигурации R8), который оценивает точное влияние каждого правила сохранения во время компиляции. Soong интегрирует этот анализатор непосредственно в сборку платформы Android (настраивается в build/soong/java/dex.go).
Чтобы проанализировать правила сохранения для модуля платформы или всей сборки:
Запустите сборку с переменной среды
R8_DUMP_KEEP_RADIUS=true:R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>Если задано значение
R8_DUMP_KEEP_RADIUS=true, Soong указывает R8 записывать показатели правил сохранения в промежуточный файлr8keepradius.pbдля каждого скомпилированного модуля вout/soong/.intermediates/. Для каждого правила сохранения и аннотацииkeepannoR8 записывает:- Непосредственный радиус сохранения. Точные классы, поля и методы, сохраняемые правилом, а также конкретные ограничения, которые оно накладывает на сжатие, оптимизацию или обфускацию.
- Поглощение правил. Какие другие правила хранения или аннотации уже применяются к тем же объектам. Если пользовательское правило полностью заменяется правилом AAPT2 или глобальным базовым правилом, его можно удалить.
- Правила, действующие на уровне пакета или глобально. Правила, в которых используются подстановочные знаки на уровне пакета или глобальные директивы конфигурации.
Преобразуйте выходные данные
r8keepradius.pbв интерактивный HTML-отчет с помощьюKeepRadiusHtmlReportGenerator(входит в составprebuilts/r8/r8.jar):# Generate an HTML report for a single module INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME> java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ $INTERMEDIATES/android_common/r8keepradius.pb \ keep_radius_report.html # Scan all built modules under out/soong/.intermediates and generate # per-module HTML reports plus an aggregate keepradius.html summary java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ out/soong/.intermediates \ out/keep_radius_reportsЕсли указан каталог,
KeepRadiusHtmlReportGeneratorобходит все файлы*keepradius*.pb, создает отчет в формате HTML для каждого модуля и файлout/keep_radius_reports/keepradius.html, в котором суммируется количество активных и сохраненных элементов, а также правила сохранения с наибольшим радиусом для всей сборки. Подробнее о том, как интерпретировать оценки сжатия, оптимизации и обфускации в созданном отчете…
Soong также поддерживает дополнительные переменные среды для диагностики R8 и внутреннего цикла в build/soong/java/dex.go:
R8_DUMP_INPUT=true– записываетr8inputs.zipв промежуточный каталог модуля, содержащий все входные JAR-файлы, JAR-файлы библиотек и объединенные конфигурации ProGuard для автономного воспроизведения R8.R8_DUMP_PERFETTO_TRACE=true– записываетr8trace.ptraceв промежуточный каталог модуля для проверки проходов компиляции R8 в Perfetto.SOONG_SKIP_TRACE_REFERENCES=true– пропускает выполнение правилtracereferences, настроенных с помощьюtrace_references_fromилиtrace_references, чтобы ускорить локальные сборки внутреннего цикла при итерации тестового кода.
Проверка правил для потребителей библиотеки
Когда java_library или android_library экспортирует правила сохранения в последующие потребители (export_proguard_flags_files: true), эти правила не должны включать глобальные флаги, которые изменяют или отключают оптимизацию для приложения-потребителя.
В R8 есть анализатор правил сохранения с открытым исходным кодом (ProcessKeepRules), который представлен в сборке платформы Android как инструмент хоста process-keep-rules (определен в prebuilts/r8/Android.bp):
# Build the R8 keep rules validator host binary
m process-keep-rules
# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>
Инструмент process-keep-rules анализирует каждый файл конфигурации и сообщает об ошибке, если обнаруживает директивы, запрещенные в правилах для потребителей библиотек. К запрещенным директивам относятся глобальная оптимизация, сжатие, отключение обфускации (например, -dontoptimize или -dontshrink), перепаковка пакетов, флаги изменения доступа, флаги диагностики или сопоставления, а также -keepattributes на уровне приложения.
Как проверить деревья источников с помощью pgaudit.py
Чтобы проверять каталоги с исходными файлами, которые не были собраны, вместе с анализаторами времени сборки R8, в дерево платформы Android включен скрипт pgaudit.py, который находится в каталоге build/make/core/proguard/tools/. Этот инструмент статического анализа сканирует файлы Android.bp, proguard.flags и keep.xml в репозитории, чтобы выявить правила, которые уже охвачены AAPT2 или базовыми показателями глобальной платформы, широкие подстановочные знаки, переопределения -dontoptimize и правила сохранения только для тестирования.
# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/
# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json
Команды разработчиков платформы и OEM-производителей могут использовать pgaudit.py для быстрой сортировки дерева исходного кода, process-keep-rules для проверки экспортированных правил библиотеки и R8_DUMP_KEEP_RADIUS=true с KeepRadiusHtmlReportGenerator для измерения точного радиуса удержания класса, поля и метода для каждого правила в сборке.