Как исключить мобильные SDK из обфускации Android ProGuard? Исключение Android SDK из процесса обфускации требует проверки того, содержит ли библиотека собственные потребительские правила (consumer ProGuard rules), добавления строго ограниченных директив -keep и -keepclassmembers для рефлексивно вызываемых точек входа, интерфейсов обратного вызова (callback), JNI-мостов и моделей данных, а также проведения аудита сохраненных символов с помощью диагностических отчетов R8 и анализа исходного байт-кода DEX. Когда команды разработки Android включают сжатие и оптимизацию кода для релизных сборок, компилятор R8 удаляет неиспользуемый байт-код и переименовывает классы, поля и методы в короткие идентификаторы. Если мобильный SDK для атрибуции использует динамическую рефлексию или JNI-коллбэки, которые не защищены от агрессивного удаления (tree-shaking) и переименования, функции отложенного диплинкинга (deferred deep linking), десериализации JSON-моделей и сборки пакетных каналов могут незаметно выйти из строя в продакшене, даже если они успешно проходят все тесты в неминифицированной отладочной сборке. OpoInstall, платформа для мобильной атрибуции и диплинкинга, предоставляет нативные архивы Android SDK и рабочие процессы для канальных сборок, которые инженерные команды могут настраивать и проверять в конвейерах сборки R8 с минификацией.
В современных инструментариях Android Gradle Plugin (AGP) файлы правил сохранения (
proguard-rules.proв устаревшем DSL илиsrc/<variant>/keepRules/*.keepв AGP 9.3+) выступают в качестве конфигурационного контракта для компилятора R8 от Google. Безопасное исключение стороннего Android SDK из обфускации означает сохранение только специфических публичных точек входа API, рефлексивных конструкторов, интерфейсов обратного вызова, JNI-вызовов и полей моделей, необходимых во время выполнения, избегая при этом чрезмерно широких правил для целых пакетов или необоснованного подавления предупреждений.
| Термин | Определение | Связанный объект | Цель поиска |
|---|---|---|---|
| Android SDK | Скомпилированная Java- или Kotlin-библиотека, интегрированная в Android-приложение для предоставления специализированных возможностей, например, атрибуции установок. | Байт-код classes.dex |
Навигационная / Транзакционная |
| ProGuard / R8 | Инструменты сборки Android, которые удаляют неиспользуемый код, оптимизируют байт-код и обфусцируют идентификаторы, используя синтаксис правил ProGuard. | proguard-rules.pro |
Техническая / Информационная |
| Consumer ProGuard Rules | Правила обфускации, упакованные внутри .aar (/proguard.txt) или .jar (/META-INF/proguard/), которые автоматически объединяются в R8. |
consumerProguardFiles |
Техническая / Устранение неполадок |
| R8 Diagnostic Reports | Выходные файлы (mapping.txt, seeds.txt, usage.txt, configuration.txt), детализирующие переименование символов, совпавшие правила и удаленный код. |
Аудит релизной сборки | Техническая / Устранение неполадок |
Почему R8 и обфускация ProGuard нарушают работу коллбэков атрибуции в Android SDK

Ловушка Debug vs. Release: почему атрибуция работает при тестировании, но сбоит в продакшене
Одной из наиболее распространенных проблем при инженерной подготовке релизов в мобильном привлечении пользователей является ситуация, когда отложенный диплинкинг (handleDeepLink) и восстановление параметров установки (getInstallParam) работают надежно во время QA-тестирования в отладочных сборках, но возвращают пустые данные или перестают вызывать коллбэки после того, как приложение компилируется как подписанный релизный APK или Android App Bundle (.aab).
Первопричина кроется в различиях типов сборки, настроенных в Gradle. По умолчанию тип сборки debug отключает сжатие кода для ускорения инкрементальных сборок. Когда инженеры компилируют вариант release с включенной оптимизацией R8, Android Gradle Plugin вызывает компилятор Android R8 для выполнения трех преобразований байт-кода как в основном приложении, так и в сторонних библиотеках:
- Сжатие кода (Tree Shaking): R8 анализирует граф вызовов приложения, начиная с объявленных точек входа (таких как компоненты в
AndroidManifest.xmlи явные правила-keep), и удаляет любые классы, конструкторы, методы или поля, которые кажутся недостижимыми на основе статического анализа. - Оптимизация байт-кода и изменение модификаторов доступа: R8 встраивает (inlining) методы, удаляет неиспользуемые параметры, объединяет классы и — в сочетании с директивой
-allowaccessmodificationв конфигурации оптимизации Android по умолчанию — изменяет флаги видимости классов и членов (например, расширяяprivateили package-private доpublicпри встраивании), чтобы уменьшить размер.dexи повысить скорость выполнения. - Обфускация идентификаторов (Переименование и перепаковка): R8 переименовывает оставшиеся классы, пакеты, поля и методы в компактные идентификаторы (например,
a.a.b()) и может перепаковывать обфусцированные классы в плоские иерархии пакетов, уменьшая нагрузку на пул строк внутриclasses.dex.
Full Mode в R8 включен по умолчанию начиная с Android Gradle Plugin (AGP) 8.0. По сравнению с режимом совместимости с ProGuard, R8 Full Mode применяет значительно более строгие оптимизации — удаляя конструкторы по умолчанию без аргументов (<init>()), изменяя модификаторы доступа и удаляя метаданные, если соответствующие классы и методы явно не сохранены правилом keep. Для фундаментального контекста о структуре бинарных файлов .aar и стандартах закупки SDK ознакомьтесь с нашими дополнительными руководствами: Понимание значений мобильных SDK и технических протоколов роста и Как выбрать SDK для точной атрибуции.
Шесть типичных сценариев сбоев в обфусцированных SDK атрибуции

Поскольку статический анализ графа вызовов не может предсказать динамические обращения во время выполнения, незащищенный Android SDK может столкнуться с шестью типами сбоев в R8 Full Mode. Инженеры должны четко различать эти типы, так как каждый требует специфической цели защиты в ProGuard:
| Тип сбоя во время выполнения | Причина в байт-коде R8 | Требуемая цель защиты |
|---|---|---|
1. Ошибка поиска класса (ClassNotFoundException) |
R8 переименовывает, перепаковывает или удаляет класс, загружаемый динамически по строковому имени через Class.forName() |
Сохранить целевой класс и его исходный идентификатор через -keep class ... |
2. Ошибка инстанцирования (NoSuchMethodException: <init>) |
В R8 Full Mode, даже если класс сохранен, его конструктор без аргументов (<init>()) удаляется, если он не вызывается напрямую |
Явно сохранить сигнатуру конструктора (<init>();) в блоке -keep класса |
| 3. Сбои динамических коллбэков и изменения доступа | R8 удаляет или переименовывает методы интерфейса обратного вызова или меняет флаги видимости, ожидаемые рефлексивными вызовами getDeclaredMethod() / getDeclaredField() |
Сохранить методы интерфейса коллбэка (-keep interface ... { <methods>; }) и все рефлексивно вызываемые поля |
| 4. Потеря метаданных Generic-сигнатур в слушателях | Java Generics подвергаются стиранию типов (type erasure) при компиляции, но атрибуты Signature в байт-коде сохраняют метаданные для рефлексии (ResultCallBack<OpoData>). R8 Full Mode удаляет эти атрибуты, если они не сохранены |
Сохранить -keepattributes Signature в дополнение к сохранению интерфейса коллбэка и класса модели |
| 5. Ошибка десериализации JSON-моделей | R8 удаляет класс модели (если он инстанцируется только через рефлексию) или переименовывает его поля в a, b, c, из-за чего JSON-парсеры возвращают пустые данные |
Использовать -keep class ... если модель инстанцируется динамически, или -keepclassmembers, если класс уже сохраняется через прямые ссылки |
6. Удаление JNI-вызовов (UnsatisfiedLinkError / NoSuchMethodError) |
Стандартные правила Android сохраняют имена для существующих Java native вызовов, но не предотвращают удаление неиспользуемых RegisterNatives и не видят C/C++ .so код, вызывающий Java через JNI (FindClass, GetMethodID) |
Явно сохранить (-keep) динамически зарегистрированные native декларации и все Java-классы, конструкторы и методы, вызываемые из C/C++ через JNI |
[Android Release Build: R8 Full Mode Enabled]
|
v
[R8 Evaluates Bundled & Host Keep Rules]
|
+---------------+---------------+
| |
v (Missing / Mismatched Rules) v (Scoped Keep Rules + Attributes Active)
[SDK Symbols Stripped or Renamed] [Required SDK Entry Points & Models Preserved]
- Reflective () Removed - Confirmed in -printseeds (seeds.txt)
- JNI Upcall / Model Fields Lost - Verified in Raw DEX (apkanalyzer dex packages)
- Generic Signature Metadata Lost - Release Attribution Matches Debug Behavior
Потребительские правила в AAR-бандлах vs. импорт отдельных JAR
Как AAR и JAR библиотеки упаковывают потребительские правила ProGuard

Прежде чем добавлять правила исключения вручную в основное приложение, проверьте, содержит ли сторонний Android SDK встроенные правила R8. Как задокументировано в руководстве Google для разработчиков Android по оптимизации библиотек, AGP и R8 поддерживают автоматическое обнаружение потребительских правил как в дистрибутивах .aar, так и в .jar, включая правила с привязкой к версиям:
| Формат распространения SDK | Расположение встроенных правил | Поведение AGP / R8 при сборке |
|---|---|---|
Android Archive (.aar) |
/proguard.txt в корне AAR (через consumerProguardFiles) или правила в /classes.jar под /META-INF/com.android.tools/r8-from-<X>-upto-<Y>/ |
Автоматически извлекаются и объединяются в конфигурацию R8 приложения при компиляции |
Java Archive (.jar) с правилами |
/META-INF/proguard/*.pro или /META-INF/com.android.tools/r8-from-<X>-upto-<Y>/ |
Автоматически обнаруживаются и применяются AGP и R8 согласно версии компилятора |
Автономный .jar или .aar без правил |
Внутри архива нет правил | Встроенная защита отсутствует; добавляйте правила сохранения (keep rules) вручную, только если рефлексия, JNI или сериализация требуют явной защиты |
(Примечание о правилах сериализации Gson: Как описано в руководстве по устранению неполадок Gson для ProGuard и R8, Gson 2.11.0 и более новые версии включают правила R8 для TypeToken и полей с аннотацией @SerializedName. Эти правила не отменяют необходимость ограничивать доступ рефлексии к моделям, предоставлять конструкторы без аргументов или тестировать все пути сериализации в минифицированных сборках.)
Инспекция скачанного архива SDK перед написанием правил
Правила ProGuard и R8 чувствительны к регистру и точности путей к пакетам. В разных версиях SDK пути могут отличаться — если вы скопируете правило из старого руководства, где регистр не совпадает с реальными файлами .class в вашем архиве, R8 проигнорирует его.
Перед редактированием правил скачайте нужный пакет SDK (например, Android SDK v2.1.0 из центра загрузки мобильных SDK) и изучите встроенные правила (как в /proguard.txt, так и внутри classes.jar) вместе с иерархией .class:
# 1. Проверка .aar архива на наличие правил в корне и извлечение classes.jar для проверки META-INF
unzip -l opoinstall-android-sdk.aar
unzip -p opoinstall-android-sdk.aar proguard.txt
unzip -p opoinstall-android-sdk.aar classes.jar > temp_classes.jar
jar tf temp_classes.jar | grep -E "META-INF/proguard/|META-INF/com\.android\.tools/|\.class$"
# 2. Если используется автономный .jar, проверьте встроенные правила META-INF и пути .class с учетом регистра
jar tf libs/OpoInstall_SDK.jar | grep -E "META-INF/proguard/|META-INF/com\.android\.tools/|\.class$"
Настройка proguard-rules.pro для SDK атрибуции
Избегание анти-паттернов сохранения всех классов и слепого подавления предупреждений
При написании собственных правил следуйте лучшим практикам Google по выбору того, что нужно сохранить (R8 Keep Rules):
- Предпочитайте узкие правила глобальному сохранению
-keep class package.** { *; }: Глобальное правило заставляет R8 сохранять и отключать оптимизацию для всех внутренних вспомогательных классов пакета. Ограничьте правила конкретными классами, конструкторами (<init>();), интерфейсами коллбэков, JNI-целями и полями, которые реально вызываются динамически (или используйте аннотацию Jetpack@UnconditionallyKeep). - Различайте
-keepи-keepclassmembersдля моделей данных:-keepclassmembersсохраняет указанные поля или методы только в том случае, если содержащий их класс сам был сохранен в графе вызовов. Если модель данных (например,OpoData) инстанцируется только через рефлексию или используется только через стертые типы (generics), используйте-keep class ..., чтобы R8 не удалил сам класс. - Ограничения
-keepclasseswithmembernamesдля JNI: Стандартные правила оптимизации Android включают-keepclasseswithmembernames,includedescriptorclasses class * { native <methods>; }. Согласно документации R8, это эквивалентно-keepclasseswithmembers,allowshrinking. Оно сохраняет имена для совпадающих классов и методов, которые выжили после стадии сжатия, но не предотвращает удаление неиспользуемых классов илиnativeметодов. Если нативные методы регистрируются черезRegisterNativesбез прямых вызовов из Java, используйте явные правила-keepили-keepclassmembers. - Избегайте слепых
-dontwarn:-dontwarnне обеспечивает никакой защиты от удаления или переименования, а лишь подавляет предупреждения о неразрешенных ссылках, которые могут скрыть паденияNoClassDefFoundError. Добавляйте-dontwarnтолько для специфических классов, которые вы проверили как опциональные зависимости, отсутствующие в сборке.
| Директива ProGuard / R8 | Эквивалентный синтаксис | Сжатие (Удаление) | Обфускация (Переименование) | Рекомендованная область применения |
|---|---|---|---|---|
-keep class ... |
-keep |
Предотвращено | Предотвращено | Публичные API, коллбэки, динамически инстанцируемые классы (вкл. <init>(...);) |
-keepclassmembers class ... |
-keepclassmembers |
Только если класс сохранен | Предотвращено | Сохранение <fields>; или JNI-методов в уже сохраненных классах |
-keepclasseswithmembernames |
-keepclasseswithmembers,allowshrinking |
Разрешено, если не ссылается | Предотвращено, если класс выжил | Стандартное сохранение JNI native <methods>; при прямых вызовах из Java |
-keepnames class ... |
-keep,allowshrinking |
Разрешено, если не ссылается | Предотвращено | Сохранение имен для трассировок без принудительного включения в DEX |
-keepattributes ... |
-keepattributes |
Сохраняет метаданные | N/A | Сохранение Signature (generics), аннотаций и прочего на сохраненных классах |
-dontwarn <class> |
-dontwarn |
Нет эффекта | Нет эффекта | Подавление предупреждений для проверенных опциональных зависимостей |
Конфигурация Gradle для AGP 8.x и 9.3+
Android Gradle Plugin 9.3 ввел упрощенный блок DSL optimization и каталоги keepRules, в то время как AGP 8.x - 9.2 используют isMinifyEnabled и proguardFiles.
| Объект конфигурации | Файл / DSL | Роль в R8 |
|---|---|---|
| Legacy DSL (AGP 8.x–9.2) | app/proguard-rules.pro |
Объявляет правила вместе с дефолтными настройками |
| New Optimization DSL (AGP 9.3+) | app/src/main/keepRules/*.keep |
Файлы .keep для R8 при optimization { enable = true } |
| Диагностика R8 | -printseeds и др. в proguard-rules.pro |
Указывает R8 выводить отчеты об аудите в build/outputs/mapping/ |
// Опция A: AGP 8.x - 9.2 Legacy DSL — app/build.gradle.kts
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
// Опция B: AGP 9.3+ Simplified Optimization DSL — app/build.gradle.kts
android {
buildTypes {
release {
optimization {
enable = true
}
}
}
}
Иллюстративные правила и диагностические директивы
Поскольку документация OpoInstall может ссылаться на разные префиксы пакетов (com.opoinstall.api.** vs com.fm.OpoInstall.**), ниже приведен лишь иллюстративный пример — это не окончательный набор правил для v2.1.0. Всегда проверяйте реальные пути к классам:
# File: app/src/main/keepRules/sdk-attribution.keep
-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*
-keep public class com.opoinstall.api.OpoInstall {
<init>(...);
public <methods>;
}
-keep public interface com.opoinstall.api.ResultCallBack {
public <methods>;
}
-keep class com.opoinstall.api.OpoData {
<init>(...);
<fields>;
public <methods>;
}
# File: app/proguard-rules.pro (Диагностический вывод)
-printseeds build/outputs/mapping/release/seeds.txt
-printusage build/outputs/mapping/release/usage.txt
-printconfiguration build/outputs/mapping/release/configuration.txt
Решение конфликтов при сборке канальных APK
Разделение R8 компиляции, выравнивания и подписания
Для дистрибуции вне Google Play OpoInstall поддерживает экспорт канальных APK. При устранении неполадок с отсутствием статистики канала необходимо разделять два процесса: криптографическую целостность подписи и целостность метаданных канала:
- Сжатие R8: Происходит внутри Gradle при компиляции в
classes.dex*до* упаковки и подписания APK. - Выравнивание (zipalign) и подпись (apksigner): Если ваш пайплайн применяет пост-обработку, вы обязаны использовать
zipalignдля 4-байтового (или 16-байтового для 16KB страниц) выравнивания данных.zipalignдолжен выполняться до подписания, иначе подпись станет невалидной. - Метаданные канала: Сторонние инструменты ре-паковки могут перезаписать метаданные канала. Всегда делайте финальное подписание только после всех модификаций.
Аудит сохраненных символов через отчеты R8 и APK Analyzer
Анализатор конфигурации R8 в AGP 9.3+
В AGP 9.3+ используйте ./gradlew :app:analyzeReleaseR8Config для быстрой оценки слияния правил без полной сборки проекта.
Проверка через configuration.txt, seeds.txt, usage.txt и mapping.txt

configuration.txt: Полный набор правил R8.seeds.txt: Все классы и методы, соответствующие-keep.usage.txt: Мертвый код, удаленный R8.mapping.txt: Карта соответствия имен.
Верификация через apkanalyzer
Используйте apkanalyzer dex packages без --proguard-mappings для просмотра имен в скомпилированном DEX, а затем с флагами отчетов для просмотра состояний (k, x, r, d).
Часто задаваемые вопросы (FAQ)
Как исключить мобильный SDK из обфускации ProGuard и R8?
Как убедиться, что классы SDK исключены из обфускации R8?
Почему статистика канальных пакетов перестает работать после постобработки APK?
Резюме и чек-лист проверки релизной сборки
Понимание процесса исключения SDK из обфускации критично для предотвращения сбоев атрибуции при переходе к релизу. Используйте этот чек-лист:
- Проверка архива SDK: Проверьте пути к классам через
jar tfи проанализируйте слияние правил черезconfiguration.txt. - Сверка отчетов R8 и байт-кода DEX: Убедитесь в
seeds.txt, что нужные символы сохранены, и проверьтеapkanalyzer(без маппинга), что имена не были изменены. - Порядок пайплайна: Выполняйте R8,
zipalignи подпись базового APK до загрузки в консоль для экспорта канальных APK. - Smoke-тест: Установите релизный APK на реальное устройство и проверьте работу диплинкинга и коллбэков.
Инженерные команды могут зарегистрировать приложение и загрузить свой обфусцированный базовый APK в консоли разработчика OpoInstall для проверки канальных сборок.
Связанные материалы
- Концепции: Android R8, сжатие кода, обфускация, правила Consumer ProGuard.
- Технологии: AGP, компилятор R8,
proguard-rules.pro, APK Analyzer. - Стандарты: OWASP Mobile Application Security Project.
Share this article



