ProGuard és R8 — obfuszkációs, minifikációs és optimalizációs eszközök Android-alkalmazásokhoz. A ProGuard, amelyet 2002-ben hoztak létre, hosszú ideig a de facto szabvány volt a Java kód védelmére. Az R8 — az utódja, amelyet a Google fejlesztett ki és az AGP 3.4-től kezdve beépült az Android Gradle Plugin-be. Mindkét eszköz csökkenti az APK méretét, eltávolítja a holt kódot és megnehezíti a reverse engineering-et. A Android Developers szerint az R8 2–3-szor gyorsabban hajtja végre a build-et, mint a ProGuard, összehasonlítható obfuszkációs minőség mellett.
Főbb pontok
ProGuard — egy nyílt forráskódú eszköz (Apache 2.0) Java bájtkód obfuszkálására, minifikálására, optimalizálására és előzetes ellenőrzésére. Eric Lafourge fejlesztette ki 2002-ben a SourceForge projekt keretében. A ProGuard bemenetként lefordított Java osztályokat (.class) vagy JAR archívumokat fogad, és ugyanolyan formátumú, de kisebb méretű és átnevezett elemekkel rendelkező feldolgozott osztályokat állít elő.
A ProGuard hosszú ideig az egyetlen szabvány volt az Android-alkalmazások reverse engineering elleni védelmére. A Google hivatalosan ajánlotta a használatát az Android SDK-ban, és alapértelmezett konfigurációt szállított a proguard-android-optimize.txt fájlban az SDK tools-on belül. A ProGuard különálló eszközként működött, amely a Java kód bájtkóddá fordítását követően, a DEX-be csomagolás előtt futott.
A ProGuard négy egymást követő fázisból áll: shrink (nem használt osztályok eltávolítása), optimize (bájtkód optimalizálása — inlining, holt kód eltávolítása), obfuscate (osztályok, metódusok és mezők átnevezése rövid nevekre), preverify (JVM kompatibilitás ellenőrzése). Minden fázist külön szabályok irányítanak a konfigurációs fájlokból.
Az obfuszkációs fázisban a ProGuard létrehoz egy mapping fájlt (mapping.txt), amely leképezi az eredeti neveket az obfuszkált nevekre. Ez a fájl kritikus fontosságú a release build-ekből származó crash naplók dekódolásához a retrace segédprogrammal. Mapping fájl nélkül a stack trace a(), b(), c() betűk halmazává válik, anélkül hogy az eredeti kontextus visszaállítható lenne.
| ProGuard fázis | Cél | Eredmény |
|---|---|---|
| Shrink | Hívásgráf elemzése és holt kód eltávolítása | Osztályok számának csökkentése az APK-ban |
| Optimize | Metódusok inlinelése, nem használt paraméterek eltávolítása | Kód végrehajtásának gyorsítása |
| Obfuscate | Osztályok, mezők és metódusok átnevezése | Védelem a reverse engineering ellen |
| Preverify | StackMap attribútumok hozzáadása JVM-hez | Java 6+ kompatibilitás |
R8 — a Google következő generációs obfuszkációs és minifikációs eszköze, amelyet először az Android Studio 3.3-ban (2018 november) mutattak be, és az AGP 3.4-ben (2019 augusztus) vált szabvánnyá. A ProGuard-tól eltérően az R8 a D8/R8 fordító része, amely a Java bájtkódot DEX formátumba alakítja. Az R8 minden fázist — obfuszkációt, minifikációt és optimalizációt — egy menetben hajt végre, anélkül hogy köztes fájlokat továbbítana az eszközök között.
A Google két céllal fejlesztette ki az R8-at: a build felgyorsítása (a ProGuard külső eszközként működött) és a zökkenőmentes integráció biztosítása a modern Android stack-kel (Desugar, Core Library Desugaring, D8). Az R8 Kotlin és Java nyelven íródott, és az AOSP (Android Open Source Project) R8/Desugar repozitóriumának része.
Az R8 fontos előnye — a teljes visszafelé kompatibilitás a ProGuard rules-szal. A meglévő .pro fájlok változtatás nélkül működnek. Az R8 még a ProGuard-specifikus irányelveket is támogatja, beleértve a -whyareyoukeeping, -printconfiguration és -printmapping parancsokat. Ez azt jelenti, hogy a ProGuard-ról R8-ra való áttérés transzparens: elég frissíteni az AGP-t.
// build.gradle.kts — R8 bekapcsolása a minifyEnabled segítségével
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
// Alap konfiguráció az Android SDK-ból
getDefaultProguardFile("proguard-android-optimize.txt"),
// Projekt egyedi szabályai
"proguard-rules.pro"
)
}
}
}A kód a release build szabványos konfigurációját mutatja. Az isMinifyEnabled = true zászló aktiválja az R8-at obfuszkációra és optimalizációra. Az isShrinkResources = true emellett eltávolítja a nem használt erőforrásokat. A getDefaultProguardFile betölti az alapszabályokat az SDK-ból, a proguard-rules.pro pedig a projektre jellemző beállításokat tartalmazza.
Obfuszkáció — a forráskód olyan formává alakításának folyamata, amelyet az ember nehezen elemez, de amely megtartja a teljes funkcionalitást. Android kontextusban az obfuszkáció az osztályok, metódusok és mezők rövid, jelentés nélküli nevekre való átnevezését jelenti: a com.example.app.auth.LoginManager a.a.a-vá, az authenticateUser metódus a-vá, a userToken mező b-vé válik.
Az Android APK fájlok archívumok, amelyek bármely archiválóval (ZIP, 7z, WinRAR) megnyithatók. Obfuszkáció nélkül a támadó megkapja az alkalmazás teljes térképét: a csomagok, osztályok, metódusok és mezők neveit. Az olyan eszközök, mint a jadx vagy a Bytecode Viewer, néhány másodperc alatt visszaállítják a majdnem eredeti Java kódot a DEX fájlokból. Az obfuszkáció nem teszi a kódot sebezhetetlenné, de jelentősen megemeli a belépési küszöböt: értelmes nevek helyett az olvasó a(), b(), c() függvényeket lát.
Az obfuszkáció tipikus céljai: kereskedelmi logika védelme (algoritmusok, számítási képletek), API-kulcsok és tokenek ellopásának megnehezítése, osztályok reflection általi helyettesítésének megakadályozása, védelem az APK patch-elése és módosítása ellen (repackage attack). A gyakorlatban a feladatok 70%-át éppen az átnevezés oldja meg — ezért indítják el a ProGuard / R8-at.
Az alábbiakban egy tipikus proguard-rules.pro fájl látható egy Retrofit, Gson és Parcelable könyvtárakat használó Android projekthez. A -keep szabályok megőrzik a könyvtárak reflection általi működéséhez szükséges osztályokat és metódusokat. E szabályok nélkül az R8 eltávolítja vagy átnevezi azokat az osztályokat, amelyeket a könyvtár sztring név alapján ér el.
# =====================
# Retrofit — interfészek megőrzése
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions
# =====================
# Gson — JSON szerializáció
# =====================
-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 — naplók eltávolítása a release-ből
# =====================
-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 adatosztályok — konstruktorok megőrzése
# =====================
-keepclassmembers class * {
@kotlin.Metadata <fields>;
}
# =====================
# Activity — belépési pont
# =====================
-keep class * extends android.app.Activity {
@android.annotation.SuppressLint <methods>;
}A .pro fájlban minden irányelv egy konkrét feladatot old meg. A -keep megakadályozza a teljes osztály eltávolítását vagy átnevezését. A -keepclassmembers csak az osztály tagjait (mezők és metódusok) védi, de lehetővé teszi magának az osztálynak az eltávolítását, ha nem használatos. A -assumenosideeffects jelzi az R8-nak, hogy a metódushívásnak nincsenek mellékhatásai, és biztonságosan eltávolítható. A -keepattributes irányelv megőrzi a metaadatokat a bájtkódban — annotációkat, aláírásokat, kivételeket.
A -keep,allowobfuscation,allowshrinking szabály a Retrofit számára lehetővé teszi az R8-nak, hogy átnevezze az interfészeket, de ne távolítsa el azokat. Ez azért szükséges, mert a Retrofit dinamikus proxy-n (java.lang.reflect.Proxy) keresztül éri el az interfészeket, és az eltávolítás ClassNotFoundException-hez vezetne futásidőben. Hasonlóképpen, a Gson reflection-t használ a @SerializedName annotációval rendelkező mezők eléréséhez — -keepclassmembers nélkül a mezők nem használtként eltávolításra kerülnek.
Minifikáció (shrinking) — a nem használt kód és erőforrások eltávolításának folyamata a végső build-ből. A ProGuard és az R8 elemzi a hívásgráfot, a belépési pontoktól (Activity, Service, BroadcastReceiver) kezdve, és eltávolítja azokat az osztályokat és metódusokat, amelyek a hívási láncon keresztül nem érhetők el. ShrinkResources — egy további szakasz, amely eltávolítja a nem használt erőforrásokat a res/-ből (layout, drawable, string, color).
A minifikáció a legnagyobb nyereséget a könyvtárakat használó nagy projektekben biztosítja. Tipikus kép: a projekt a csatlakoztatott könyvtár (pl. Google Play Services) kódjának 10%-át használja. Minifikáció nélkül a könyvtár teljes kódja bekerül az APK-ba. Minifikációval az R8 eltávolítja a könyvtárkód 70–90%-át, csak a ténylegesen használt osztályokat és metódusokat hagyva meg. Ez közvetlenül befolyásolja az APK méretét, a betöltési időt és a memóriafogyasztást.
A ShrinkResources mechanizmus a kód minifikációjával párhuzamosan működik. Miután az R8 meghatározta, hogy mely osztályok használatosak, az erőforrás-shrinking elemzi a kódból az erőforrásokra mutató hivatkozásokat: R.layout.main, R.drawable.icon, getString(R.string.title). Minden olyan erőforrás, amelyre nincs közvetlen vagy közvetett hivatkozás, eltávolításra kerül a végső APK-ból vagy AAB-ból. Ehhez a resources.arsc erőforrásfájlt és a res/ mappákat használják.
Fontos árnyalat: az erőforrások meghívhatók a getIdentifier() vagy Resources.getResourceName() segítségével sztring néven, megkerülve az R osztályt. Ilyen esetekben az R8 nem látja a közvetlen kapcsolatot, és eltávolíthat egy ténylegesen használt erőforrást. Az ilyen erőforrások védelmére létezik a -keep class **.R$* { *; } irányelv — ez megőrzi az R osztály összes azonosítóját.
<!-- Példa: erőforrás, amely csak getIdentifier()-en keresztül használt -->
<string name="dynamic_title_welcome">Üdvözöljük</string>
<string name="dynamic_title_share">Megosztás</string>
<!-- Kotlin kód, amely sztringen keresztül ér el -->
<!-- val title = getString(resources.getIdentifier( -->
<!-- \"dynamic_title_${type}\", \"string\", packageName)) -->Ebben az esetben az R8 nem lát statikus hivatkozást a dynamic_title_welcome-ra az R osztályban, mert a hozzáférés a getIdentifier-en keresztül dinamikus névvel történik. Az ilyen erőforrások megőrzéséhez hozzá kell adni a proguard-rules.pro fájlhoz a -keepclassmembers class **.R$string { *; } irányelvet — ez megtiltja bármely mező eltávolítását az összes R$string osztályból.
| Irányelv | Cél | Példa |
|---|---|---|
| -keep | Megőrzi az osztályt és annak összes tagját | -keep class com.example.api.** { *; } |
| -keepclassmembers | Csak az osztály tagjait őrzi meg | -keepclassmembers class * { @SerializedName <fields>; } |
| -keepattributes | Megőrzi a bájtkód metaadatait | -keepattributes *Annotation*, Signature |
| -assumenosideeffects | Eltávolítja a mellékhatások nélküli hívásokat | -assumenosideeffects class Log { d(...); } |
| -dontwarn | Elnyomja a figyelmeztetéseket | -dontwarn com.example.legacy.** |
Bár az R8 a ProGuard utódja, alapvető különbségek vannak az eszközök között az architektúrában, a teljesítményben és a viselkedésben. A Google hivatalosan megszüntette a ProGuard támogatását az Android Gradle Plugin-ben az AGP 7.0-tól kezdve, azonban a ProGuard továbbra is használatos olyan projektekben, ahol speciális optimalizációs viselkedésre van szükség, amely az R8-ban nem érhető el.
| Jellemző | ProGuard | R8 |
|---|---|---|
| Fejlesztő | GuardSquare (Eric Lafourge) | |
| Megjelenés éve | 2002 | 2018 (stabil 2019-ben) |
| Architektúra | 4 külön fázis (shrink → optimize → obfuscate → preverify) | Egy menet: shrink + optimize + obfuscate egyidejűleg |
| Integráció AGP-be | Külső eszköz, a javac után fut | Beépítve a D8 DEX fordítóba |
| Build sebesség | 2–3-szor lassabb | Gyorsabb az egy menet és a natív integráció miatt |
| Kotlin támogatás | Korlátozott (problémák inline, lambdas, coroutines) | Teljes: coroutines, inline függvények, data class |
| Mapping fájl | mapping.txt (kompatibilis a retrace-szel) | mapping.txt (ugyanaz a formátum) |
| Optimalizáció testreszabása | 60+ opció -optimizationpasses, -optimizations | Korlátozott: a legtöbb optimalizáció alapértelmezetten be van kapcsolva |
| Támogatás állapota | Felváltotta az R8 (AGP 7.0+ nem használja) | Aktív fejlesztés, az AOSP része |
Az R8 agresszívebben távolítja el a holtnak vélt kódot, mint a ProGuard. Ez olyan helyzetekhez vezet, amikor a debug build működik, de a release ClassNotFoundException vagy NoSuchMethodException hibával összeomlik. Tipikus esetek: reflection-t használó könyvtárak osztálynév alapján (Gson, Moshi, Retrofit, Room, Dagger); ServiceLoader vagy java.util.ServiceLoader hívások; dinamikus proxy-k (java.lang.reflect.Proxy); natív metódusok (JNI). Megoldás — adjon hozzá -keep szabályt minden olyan osztályhoz, amelyet reflection segítségével hívnak meg.
# Tipikus reflection problémák — R8 nem lát statikus kapcsolatot
# Room — DAO és migrációk megőrzése
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }
# Dagger / Hilt — komponensek megőrzése
-keep class * extends dagger.hilt.android.components.** { *; }
# JNI — natív metódusok átnevezésének mellőzése
-keepclasseswithmembernames class * {
native <methods>;
}
# Data Binding — Binding osztályok megőrzése
-keep class *.databinding.** { *; }Ha a szabályok hozzáadása után a build továbbra is összeomlik, használja a -printconfiguration full-config.txt zászlót a proguard-rules.pro fájlban. Az R8 létrehoz egy teljes konfigurációs fájlt, amely megmutatja, hogy mely szabályok kerültek alkalmazásra és mely osztályok kerülnek megőrzésre. Szintén hasznos a -whyareyoukeeping class com.example.MyClass irányelv — kiírja annak okát, hogy az R8 miért döntött az adott osztály megőrzése mellett.
A ProGuard rules helyes beállítása — a kulcs a stabil obfuszkációhoz futásidejű hibák nélkül. Az alábbiakban lépésről lépésre bemutatjuk a beállítási folyamatot egy új projekthez vagy egy olyan projekthez, ahol az obfuszkáció hibákat okoz.
Kezdje az Android SDK szabványos fájljának — proguard-android-optimize.txt csatlakoztatásával. Ez tartalmazza az alap Android komponensek szabályait: Activity, Service, BroadcastReceiver, ContentProvider, View, Fragment. Ez a fájl az SDK mappában található: $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt. Ha AGP-t használ, a getDefaultProguardFile automatikusan betölti.
Minden népszerű könyvtárnak vannak ajánlott ProGuard szabályai. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — mindegyik specifikus -keep szabályokat igényel. Általában a szabályok az AAR könyvtárban találhatók, és automatikusan csatlakoznak a consumer guard rules-on keresztül. Ellenőrizze, hogy a könyvtár biztosít-e proguard.txt fájlt az AAR-on belül — ez annak a jele, hogy a szabályok már figyelembe vannak véve.
Közzététel előtt feltétlenül tesztelje a release build-et valós eszközön vagy emulátoron. Az obfuszkációs problémák csak futásidőben jelentkeznek. Ellenőrizze: hitelesítés (bejelentkezés/regisztráció), adatok betöltése a hálózatról, navigáció a képernyők között, kamera és galéria, push értesítések, Deeplinkek, WebView. Minden összeomlást a release build-ben dekódolni kell a retrace segítségével a mapping fájllal, és hozzá kell adni a hiányzó -keep szabályokat.
A mapping fájl a build/outputs/mapping/release/mapping.txt címen jön létre. Ezt a fájlt kötelező megőrizni: nélküle nem lehet dekódolni a crash naplókat a Google Play Console-ból. Illessze be a mapping.txt-t a verziókezelő rendszerbe, vagy töltse fel CI-artefaktumként. A Google Play Console automatikusan elfogadja a mapping fájlt az AAB feltöltésekor, ha az uploading mapping.txt be van kapcsolva.
Az alábbiakban a proguard-rules.pro fájlban az obfuszkáció beállításának teljes munkafolyamata látható, az egyes szabálycsoportokhoz tartozó megjegyzésekkel.
# ===========================================
# proguard-rules.pro — teljes példa
# ===========================================
# --- Általános beállítások ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify
# --- Android komponensek ---
-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 {}
# --- Szerializáció ---
-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();
}
# --- Csak R8: kényszerített megőrzés ---
# (A ProGuard figyelmen kívül hagyja ezt az irányelvet)
-keep,allowobfuscation class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}Beállítás után hajtsa végre a build-et: ./gradlew assembleRelease. Ellenőrizze, hogy a build/outputs/mapping/release/ mappában megjelentek-e a fájlok: mapping.txt (az eredeti és obfuszkált nevek megfeleltetése), seeds.txt (a -keep szabályok által megőrzött osztályok), usage.txt (a minifikáció során eltávolított osztályok). Az APK méretének az obfuszkáció után 20–50%-kal kell csökkennie a csatlakoztatott könyvtárak számától függően.
Gyakran Ismételt Kérdések
R8 — a ProGuard utódja, amelyet a Google fejlesztett. Az R8 obfuszkációt, minifikációt és optimalizációt egy menetben hajt végre, 2–3-szor gyorsabban működik, mint a ProGuard, és közvetlenül az Android Gradle Plugin-be van integrálva. A ProGuard négy külön fázist használ, és külső indítást igényel. Az AGP 7.0-tól kezdve a ProGuard nem használatos — alapértelmezetten az R8 működik.
Igen, az R8 ugyanazokat a ProGuard rules-t (.pro fájlokat) használja. A -keep, -keepclassmembers, -keepattributes, -assumenosideeffects irányelvek azonosan működnek. Az alapszabályok az Android SDK-ból, a proguard-android-optimize.txt-ből származnak, a könyvtárakra (Retrofit, Room, Gson) jellemzők pedig a projekt proguard-rules.pro fájljába kerülnek. E szabályok nélkül az R8 eltávolíthatja a reflection útján működő könyvtárak működéséhez szükséges osztályokat.
Az R8 alapértelmezetten be van kapcsolva az Android Gradle Plugin-ben az AGP 3.4-től kezdve. A minifikáció aktiválásához állítsa be az isMinifyEnabled = true értéket a build.gradle.kts fájl release buildType blokkjában. A további isShrinkResources = true zászló bekapcsolja a nem használt erőforrások eltávolítását. A gradle.properties fájlban az android.enableR8=false segítségével kényszeríthető az R8 kikapcsolása, de ez nem ajánlott — az R8 gyorsabb és stabilabb.
Obfuszkáció — az osztályok, metódusok és mezők rövid, jelentés nélküli nevekre (a, b, c) való átnevezése. A com.example.app.auth.LoginManager osztály a.a.a-vá, az authenticateUser metódus a-vá alakul. Ez megnehezíti az alkalmazás reverse engineering-jét, de nem befolyásolja a végrehajtási logikát. A ProGuard és az R8 csak azokat az elemeket nevezi át, amelyeket nem védenek a -keep szabályok. A mapping fájl megőrzi az eredeti és obfuszkált nevek megfeleltetését a crash naplók dekódolásához.
A stack trace dekódolásához a retrace segédprogramot (a ProGuard/R8 SDK része) használják. Parancs: retrace mapping.txt crash-stacktrace.txt. A mapping fájl a build/outputs/mapping/release/mapping.txt címen található. A Google Play Console is támogatja a mapping.txt feltöltését az AAB közzétételekor — a crash naplók automatikusan dekódolásra kerülnek a konzolban. Mapping fájl nélkül a stack trace csak obfuszkált a.b.c() neveket fog tartalmazni, ami haszontalan a debug-hoz.
Összefoglaló
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is