ProGuard и R8 — инструменты обфускации, минификации и оптимизации для Android-приложений. ProGuard, созданный в 2002 году, долгое время был стандартом де-факто для защиты Java-кода. R8 — его преемник, разработанный Google и встроенный в Android Gradle Plugin начиная с AGP 3.4. Оба инструмента уменьшают размер APK, удаляют мёртвый код и усложняют reverse engineering. По данным Android Developers, R8 выполняет сборку в 2–3 раза быстрее ProGuard при сопоставимом качестве обфускации.
Главное
ProGuard — это инструмент с открытым исходным кодом (Apache 2.0) для обфускации, минификации, оптимизации и преверификации Java-байткода. Разработан Эриком Лафоржем в 2002 году в рамках проекта SourceForge. ProGuard принимает на вход скомпилированные Java-классы (.class) либо JAR-архивы и выдаёт обработанные классы того же формата, но меньшего размера и с переименованными элементами.
Долгое время ProGuard был единственным стандартом для защиты Android-приложений от reverse engineering. Google официально рекомендовала его использование в Android SDK и поставляла конфигурацию по умолчанию в файле proguard-android-optimize.txt внутри SDK tools. ProGuard работал как отдельный инструмент, запускаемый после компиляции Java-кода в байткод и перед упаковкой в DEX.
ProGuard состоит из четырёх последовательных фаз: shrink (удаление неиспользуемых классов), optimize (оптимизация байткода — инлайнинг, удаление мёртвого кода), obfuscate (переименование классов, методов и полей в короткие имена), preverify (проверка совместимости с JVM). Каждая фаза управляется отдельными правилами из конфигурационных файлов.
На этапе обфускации ProGuard генерирует mapping-файл (mapping.txt), который отображает исходные имена на обфусцированные. Этот файл критически важен для декодирования crash-логов из релизных сборок через утилиту retrace. Без mapping-файла stack trace превращается в набор букв a(), b(), c() без возможности восстановления исходного контекста.
| Фаза ProGuard | Назначение | Результат |
|---|---|---|
| Shrink | Анализ графа вызовов и удаление мёртвого кода | Уменьшение количества классов в APK |
| Optimize | Инлайнинг методов, удаление неиспользуемых параметров | Ускорение выполнения кода |
| Obfuscate | Переименование классов, полей и методов | Защита от reverse engineering |
| Preverify | Добавление StackMap-атрибутов для JVM | Совместимость с Java 6+ |
R8 — это инструмент обфускации иминификации следующего поколения от Google, впервые представленный в Android Studio 3.3 (ноябрь 2018) и ставший стандартным в AGP 3.4 (август 2019). В отличие от ProGuard, R8 является частью компилятора D8/R8, который преобразует Java-байткод в DEX-формат. R8 выполняет все фазы — обфускацию, минификацию и оптимизацию — за один проход, без передачи промежуточных файлов между инструментами.
Google разработала R8 с двумя целями: ускорить сборку (ProGuard работал как внешний инструмент) и обеспечить бесшовную интеграцию с современным Android-стеком (Desugar, Core Library Desugaring, D8). R8 написан на Kotlin и Java и является частью репозитория R8/Desugar на AOSP (Android Open Source Project).
Важное преимущество R8 — полная обратная совместимость с ProGuard rules. Существующие .pro-файлы работают без изменений. R8 даже поддерживает специфичные ProGuard-директивы, включая -whyareyoukeeping, -printconfiguration и -printmapping. Это означает, что переход с ProGuard на R8 происходит прозрачно: достаточно обновить AGP.
// build.gradle.kts — включение R8 через minifyEnabled
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
// Базовая конфигурация из Android SDK
getDefaultProguardFile("proguard-android-optimize.txt"),
// Пользовательские правила проекта
"proguard-rules.pro"
)
}
}
}Код демонстрирует стандартную конфигурацию release-сборки. Флаг isMinifyEnabled = true активирует R8 для обфускации и оптимизации. isShrinkResources = true дополнительно удаляет неиспользуемые ресурсы. getDefaultProguardFile загружает базовые правила из SDK, а proguard-rules.pro содержит специфичные для проекта настройки.
Обфускация — это процесс преобразования исходного кода в форму, которую сложно анализировать человеку, но которая сохраняет полную функциональность. В контексте Android обфускация означает переименование классов, методов и полей в короткие, лишённые смысла имена: com.example.app.auth.LoginManager превращается в a.a.a, метод authenticateUser — в a, поле userToken — в b.
APK-файлы Android — это архивы, которые можно открыть любым архиватором (ZIP, 7z, WinRAR). Без обфускации злоумышленник получает полную карту приложения: названия пакетов, классов, методов и полей. Инструменты вроде jadx или Bytecode Viewer восстанавливают почти исходный Java-код из DEX-файлов за секунды. Обфускация не делает код неуязвимым, но существенно повышает порог входа: вместо осмысленных имен читатель видит a(), b(), c().
Типовые цели обфускации: защита коммерческой логики (алгоритмы, формулы расчёта), затруднение кражи API-ключей и токенов, предотвращение подмены классов через reflection, защита от патчинга и модификации APK (repackage attack). На практике 70% задач решает именно переименование — ради этого и запускают ProGuard / R8.
Ниже приведён типовой файл proguard-rules.pro для Android-проекта с Retrofit, Gson и Parcelable. Правила -keep сохраняют классы и методы, необходимые для работы библиотек через reflection. Без этих правил R8 удалит или переименует классы, к которым обращается библиотека по имени строки.
# =====================
# Retrofit — сохранение интерфейсов
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions
# =====================
# Gson — сериализация JSON
# =====================
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
-keep class *.serialization.** {
<fields>;
}
# =====================
# Parcelable — Creator
# =====================
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
# =====================
# Logging — удаление логов из релиза
# =====================
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(String, int);
public static int v(...);
public static int d(...);
public static int i(...);
public static int w(...);
public static int e(...);
}
# =====================
# Дата-классы Kotlin — сохраняем конструторы
# =====================
-keepclassmembers class * {
@kotlin.Metadata <fields>;
}
# =====================
# Activity — точка входа
# =====================
-keep class * extends android.app.Activity {
@android.annotation.SuppressLint <methods>;
}Каждая директива в .pro файле решает конкретную задачу. -keep предотвращает удаление или переименование класса целиком. -keepclassmembers защищает только члены класса (поля и методы), но позволяет удалить сам класс, если он не используется. -assumenosideeffects указывает R8, что вызов метода не имеет побочных эффектов и может быть безопасно удалён. Директива -keepattributes сохраняет метаданные в байткоде — аннотации, сигнатуры, исключения.
Правило -keep,allowobfuscation,allowshrinking для Retrofit разрешает R8 переименовывать интерфейсы, но не удалять их. Это необходимо, так как Retrofit обращается к интерфейсам через динамический прокси (java.lang.reflect.Proxy), и удаление приведёт к ClassNotFoundException в runtime. Аналогично, Gson использует reflection для доступа к полям с аннотацией @SerializedName — без -keepclassmembers поля будут удалены как неиспользуемые.
Минификация (shrinking) — процесс удаления неиспользуемого кода и ресурсов из итоговой сборки. ProGuard и R8 анализируют граф вызовов, начиная с точек входа (Activity, Service, BroadcastReceiver), и удаляют классы и методы, до которых нельзя дойти по цепочке вызовов. ShrinkResources — дополнительный этап, который удаляет неиспользуемые ресурсы из res/ (layout, drawable, string, color).
Минификация даёт наибольший выигрыш в больших проектах с библиотеками. Типовая картина: проект использует 10% кода из подключённой библиотеки (например, Google Play Services). Без минификации весь код библиотеки попадает в APK. С минификацией R8 удаляет 70–90% кода библиотек, оставляя только реально используемые классы методы. Это напрямую влияет на размер APK, время загрузки и потребление памяти.
Механизм ShrinkResources работает в паре с минификацией кода. После того как R8 определил, какие классы используются, ресурсный shrinking анализирует ссылки на ресурсы из кода: R.layout.main, R.drawable.icon, getString(R.string.title). Все ресурсы, на которые нет прямой или косвенной ссылки, удаляются из финального APK или AAB. Для этого используется ресурсный файл resources.arsc и папки res/.
Важный нюанс: ресурсы могут быть вызваны через getIdentifier() или Resources.getResourceName() по строковому имени, минуя R-класс. В таких случаях R8 не видит прямой связи и может удалить ресурс, который фактически используется. Для защиты таких ресурсов существует директива -keep class **.R$* { *; } — она сохраняет все идентификаторы R-класса.
<!-- Пример: ресурс, который используется только через getIdentifier() -->
<string name="dynamic_title_welcome">Добро пожаловать</string>
<string name="dynamic_title_share">Поделиться</string>
<!-- Kotlin-код, который обращается по строке -->
<!-- val title = getString(resources.getIdentifier( -->
<!-- \"dynamic_title_${type}\", \"string\", packageName)) -->В этом случае R8 не видит статической ссылки на dynamic_title_welcome в R-классе, потому что обращение происходит через getIdentifier с динамическим именем. Чтобы сохранить такие ресурсы, нужно добавить в proguard-rules.pro директиву -keepclassmembers class **.R$string { *; } — она запрещает удаление любых полей из всех R$string классов.
| Директива | Назначение | Пример |
|---|---|---|
| -keep | Сохраняет класс и все его члены | -keep class com.example.api.** { *; } |
| -keepclassmembers | Сохраняет только члены класса | -keepclassmembers class * { @SerializedName <fields>; } |
| -keepattributes | Сохраняет метаданные байткода | -keepattributes *Annotation*, Signature |
| -assumenosideeffects | Удаляет вызовы без побочных эффектов | -assumenosideeffects class Log { d(...); } |
| -dontwarn | Подавляет предупреждения | -dontwarn com.example.legacy.** |
Несмотря на то что R8 является преемником ProGuard, между инструментами есть принципиальные различия в архитектуре, производительности и поведении. Google официально прекратила поддержку ProGuard в Android Gradle Plugin начиная с AGP 7.0, однако ProGuard продолжает использоваться в проектах, где требуется специфическое поведение оптимизации, недоступное в R8.
| Характеристика | ProGuard | R8 |
|---|---|---|
| Разработчик | GuardSquare (Эрик Лафорж) | |
| Год выхода | 2002 | 2018 (стабильный в 2019) |
| Архитектура | 4 отдельные фазы (shrink → optimize → obfuscate → preverify) | Один проход: shrink + optimize + obfuscate одновременно |
| Интеграция в AGP | Внешний инструмент, запускается после javac | Встроен в D8 компилятор DEX |
| Скорость сборки | Медленнее в 2–3 раза | Быстрее за счёт одного прохода и нативной интеграции |
| Поддержка Kotlin | Ограниченная (проблемы с inline, lambdas, coroutines) | Полная: coroutines, inline-функции, data class |
| Mapping-файл | mapping.txt (совместим с retrace) | mapping.txt (тот же формат) |
| Кастомизация оптимизации | 60+ опций -optimizationpasses, -optimizations | Ограниченная: большинство оптимизаций включены по умолчанию |
| Статус поддержки | Заменён на R8 (AGP 7.0+ не использует) | Активная разработка, часть AOSP |
R8 агрессивнее ProGuard удаляет код, который считает мёртвым. Это приводит к ситуациям, когда сборка в debug работает, а release падает с ClassNotFoundException или NoSuchMethodException. Типовые случаи: библиотеки, использующие reflection по имени класса (Gson, Moshi, Retrofit, Room, Dagger); вызовы ServiceLoader или java.util.ServiceLoader; динамические прокси (java.lang.reflect.Proxy); нативные методы (JNI). Решение — добавить -keep для всех классов, которые вызываются через reflection.
# Типовые проблемы reflection — R8 не видит статической связи
# Room — сохраняем DAO и миграции
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }
# Dagger / Hilt — сохраняем компоненты
-keep class * extends dagger.hilt.android.components.** { *; }
# JNI — не переименовываем нативные методы
-keepclasseswithmembernames class * {
native <methods>;
}
# Data Binding — сохраняем Binding-классы
-keep class *.databinding.** { *; }Если после добавления правил сборка всё ещё падает, используйте флаг -printconfiguration full-config.txt в proguard-rules.pro. R8 сгенерирует полный файл конфигурации, который показывает, какие правила применены и какие классы сохраняются. Также полезна директива -whyareyoukeeping class com.example.MyClass — она выводит причину, по которой R8 решил сохранить данный класс.
Правильная настройка ProGuard rules — ключ к стабильной работе обфускации без багов в runtime. Ниже приведён пошаговый процесс настройки для нового проекта или для проекта, в котором обфускация вызывает ошибки.
Начните с подключения стандартного файла Android SDK — proguard-android-optimize.txt. Он содержит правила для базовых компонентов Android: Activity, Service, BroadcastReceiver, ContentProvider, View, Fragment. Этот файл находится в папке SDK: $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt. Если вы используете AGP, getDefaultProguardFile загрузит его автоматически.
Каждая популярная библиотека имеет рекомендованные ProGuard rules. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — все они требуют специфических -keep правил. Обычно правила включены в AAR-библиотеку и подключаются автоматически через consumer guard rules. Проверьте, что библиотека предоставляет файл proguard.txt внутри AAR — это признак того, что правила уже учтены.
Перед публикацией обязательно протестируйте релизную сборку на реальном устройстве или эмуляторе. Проблемы обфускации проявляются только в runtime. Проверьте: авторизацию (логин/регистрацию), загрузку данных из сети, навигацию между экранами, камеру и галерею, push-уведомления, Deeplinks, WebView. Каждый краш в релизной сборке нужно декодировать через retrace с mapping-файлом и добавлять недостающие -keep правила.
Mapping-файл генерируется в build/outputs/mapping/release/mapping.txt. Этот файл обязателен для сохранения: без него невозможно декодировать crash-логи из Google Play Console. Включите mapping.txt в систему контроля версий или загружайте его в CI-артефакты. Google Play Console принимает mapping-файл автоматически при загрузке AAB с включённым uploading mapping.txt.
Ниже приведён полный workflow настройки обфускации в файле proguard-rules.pro с комментариями для каждой группы правил.
# ===========================================
# proguard-rules.pro — полный пример
# ===========================================
# --- Общие настройки ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify
# --- Android компоненты ---
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.Fragment
-keep public class * extends androidx.fragment.app.Fragment
-keep public class * extends android.view.View
# --- OkHttp / Retrofit ---
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class retrofit2.** { *; }
-keepattributes Exceptions
# --- Gson / Moshi ---
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
-keep class com.google.gson.** { *; }
# --- Firebase ---
-keep class com.google.firebase.** { *; }
-keep class com.google.android.gms.** { *; }
# --- Kotlin Coroutines ---
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# --- Сериализация ---
-keepclassmembers class * implements java.io.Serializable {
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# --- R8 только: принудительное сохранение ---
# (ProGuard игнорирует эту директиву)
-keep,allowobfuscation class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}После настройки выполните сборку: ./gradlew assembleRelease. Проверьте, что в build/outputs/mapping/release/ появились файлы: mapping.txt (соответствие исходных и обфусцированных имён), seeds.txt (классы, сохранённые правилами -keep), usage.txt (классы, удалённые при минификации). Размер APK после обфускации должен уменьшиться на 20–50% в зависимости от количества подключённых библиотек.
Часто задаваемые вопросы
R8 — преемник ProGuard, разработанный Google. R8 выполняет обфускацию, минификацию и оптимизацию за один проход, работает в 2–3 раза быстрее ProGuard и интегрирован напрямую в Android Gradle Plugin. ProGuard использует четыре отдельные фазы и требует внешнего запуска. Начиная с AGP 7.0 ProGuard не используется — по умолчанию работает R8.
Да, R8 использует те же ProGuard rules (.pro файлы). Директивы -keep, -keepclassmembers, -keepattributes, -assumenosideeffects работают идентично. Базовые правила идут в proguard-android-optimize.txt из Android SDK, а специфичные для библиотек (Retrofit, Room, Gson) добавляются в proguard-rules.pro проекта. Без этих правил R8 может удалить классы, необходимые для работы библиотек через reflection.
R8 включён по умолчанию в Android Gradle Plugin начиная с AGP 3.4. Для активации минификации установите isMinifyEnabled = true в блоке release buildType файла build.gradle.kts. Дополнительный флаг isShrinkResources = true включает удаление неиспользуемых ресурсов. В gradle.properties можно принудительно отключить R8 через android.enableR8=false, но это не рекомендуется — R8 быстрее и стабильнее.
Обфускация — переименование классов, методов и полей в короткие бессмысленные имена (a, b, c). Класс com.example.app.auth.LoginManager превращается в a.a.a, метод authenticateUser — в a. Это усложняет reverse engineering приложения, но не влияет на логику выполнения. ProGuard и R8 переименовывают только те элементы, которые не защищены правилами -keep. Mapping-файл сохраняет соответствие исходных и обфусцированных имён для декодирования crash-логов.
Для декодирования stack trace используется утилита retrace (входит в состав ProGuard/R8 SDK). Команда: retrace mapping.txt crash-stacktrace.txt. Mapping-файл находится в build/outputs/mapping/release/mapping.txt. Google Play Console также поддерживает загрузку mapping.txt при публикации AAB — crash-логи автоматически декодируются в консоли. Без mapping-файла stack trace будет содержать только обфусцированные имена a.b.c(), что бесполезно для отладки.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также