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.
Android APK датотеке су архиве које се могу отворити било којим архиватором (ZIP, 7z, WinRAR). Без обфускације, нападач добија потпуну мапу апликације: називе пакета, класа, метода и поља. Алати попут jadx или Bytecode Viewer обнављају готово оригинални Java код из DEX датотека за неколико секунди. Обфускација не чини код нерањивим, али значајно подиже праг уласка: уместо смислених имена, читалац види a(), b(), c().
Типични циљеви обфускације: заштита комерцијалне логике (алгоритми, формуле прорачуна), отежавање крађе API кључева и токена, спречавање замене класа путем reflection, заштита од patching и модификације 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 у 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 одреди које класе се користе, смањивање ресурса анализира референце на ресурсе из кода: 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 правила. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — све захтевају специфична -keep правила. Обично су правила укључена у AAR библиотеку и повезују се аутоматски путем consumer guard rules. Проверите да ли библиотека пружа датотеку proguard.txt унутар AAR — то је знак да су правила већ узета у обзир.
Пре објављивања обавезно тестирајте release изградњу на стварном уређају или емулатору. Проблеми обфускације се појављују само у runtime. Проверите: ауторизацију (пријава/регистрација), учитавање података из мреже, навигацију између екрана, камеру и галерију, push обавештења, Deeplinks, WebView. Сваки crash у 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.
Испод је потпуни 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. То отежава реверзни инжењеринг апликације, али не утиче на логику извршења. 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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође