ProGuard и R8 — инструменти за объркване, минификация и оптимизация за Android приложения. ProGuard, създаден през 2002 г., дълго време беше стандарт де факто за защита на Java код. R8 — неговият наследник, разработен от Google и вграден в Android Gradle Plugin от AGP 3.4 насетне. И двата инструмента намаляват размера на APK, премахват мъртъв код и усложняват обратното инженерство. Според Android Developers, R8 изпълнява изграждането 2–3 пъти по-бързо от ProGuard при сравнимо качество на объркване.
Основни моменти
ProGuard — е инструмент с отворен код (Apache 2.0) за объркване, минификация, оптимизация и предварителна проверка на Java байткод. Разработен от Ерик Лафорж през 2002 г. в рамките на проекта SourceForge. ProGuard приема като вход компилирани Java класове (.class) или JAR архиви и произвежда обработени класове от същия формат, но с по-малък размер и с преименувани елементи.
Дълго време ProGuard беше единственият стандарт за защита на Android приложения от обратно инженерство. Google официално препоръчваше използването му в Android SDK и доставяше конфигурация по подразбиране във файла proguard-android-optimize.txt в SDK tools. ProGuard работеше като отделен инструмент, стартиран след компилиране на Java код в байткод и преди опаковане в DEX.
ProGuard се състои от четири последователни фази: shrink (премахване на неизползвани класове), optimize (оптимизация на байткод — инлайнване, премахване на мъртъв код), obfuscate (преименуване на класове, методи и полета в кратки имена), preverify (проверка на съвместимост с JVM). Всяка фаза се управлява от отделни правила от конфигурационни файлове.
На етапа на объркване ProGuard генерира mapping файл (mapping.txt), който съпоставя оригиналните имена с обърканите. Този файл е критичен за декодиране на crash логове от release компилации чрез инструмента retrace. Без mapping файл, stack trace се превръща в набор от букви a(), b(), c() без възможност за възстановяване на оригиналния контекст.
| Фаза на ProGuard | Предназначение | Резултат |
|---|---|---|
| Shrink | Анализ на графа на извиквания и премахване на мъртъв код | Намаляване броя на класовете в APK |
| Optimize | Инлайнване на методи, премахване на неизползвани параметри | Ускоряване изпълнението на кода |
| Obfuscate | Преименуване на класове, полета и методи | Защита от обратно инженерство |
| 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 — премахване на логове от release
# =====================
-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 по време на изпълнение. По подобен начин 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 определи кои класове се използват, свиването на ресурси анализира препратките към ресурси от кода: 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 — ключът към стабилна работа на объркването без грешки по време на изпълнение. По-долу е стъпка по стъпка процесът на настройка за нов проект или за проект, в който объркването причинява грешки.
Започнете със свързване на стандартния файл на 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 правила. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — всички изискват специфични -keep правила. Обикновено правилата са включени в AAR библиотеката и се свързват автоматично чрез consumer guard rules. Проверете дали библиотеката предоставя файл proguard.txt вътре в AAR — това е знак, че правилата вече са взети предвид.
Преди публикуване, задължително тествайте release компилацията на реално устройство или емулатор. Проблемите с объркването се проявяват само по време на изпълнение. Проверете: оторизация (вход/регистрация), зареждане на данни от мрежата, навигация между екраните, камера и галерия, push известия, Deeplinks, WebView. Всеки срив в release компилацията трябва да бъде декодиран чрез 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.
По-долу е пълният работен процес за настройка на объркването във файла 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. Това усложнява обратното инженерство на приложението, но не влияе на логиката на изпълнение. 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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също