ProGuard i R8 — narzędzia do obfuskacji, minifikacji i optymalizacji aplikacji Android. ProGuard, stworzony w 2002 roku, przez długi czas był standardem de facto ochrony kodu Java. R8 — jego następca, opracowany przez Google i wbudowany w Android Gradle Plugin od AGP 3.4. Oba narzędzia zmniejszają rozmiar APK, usuwają martwy kod i utrudniają inżynierię wsteczną. Według Android Developers, R8 wykonuje kompilację 2–3 razy szybciej niż ProGuard przy porównywalnej jakości obfuskacji.
Najważniejsze
ProGuard — to narzędzie open source (Apache 2.0) do obfuskacji, minifikacji, optymalizacji i preweryfikacji kodu bajtowego Java. Opracowany przez Erica Lafourge'a w 2002 roku w ramach projektu SourceForge. ProGuard przyjmuje na wejściu skompilowane klasy Java (.class) lub archiwa JAR i zwraca przetworzone klasy tego samego formatu, ale o mniejszym rozmiarze i z przemianowanymi elementami.
Przez długi czas ProGuard był jedynym standardem ochrony aplikacji Android przed inżynierią wsteczną. Google oficjalnie zalecała jego używanie w Android SDK i dostarczała domyślną konfigurację w pliku proguard-android-optimize.txt w SDK tools. ProGuard działał jako oddzielne narzędzie uruchamiane po kompilacji kodu Java do kodu bajtowego, a przed pakowaniem do DEX.
ProGuard składa się z czterech następujących po sobie faz: shrink (usuwanie nieużywanych klas), optimize (optymalizacja kodu bajtowego — inline, usuwanie martwego kodu), obfuscate (zmiana nazw klas, metod i pól na krótkie), preverify (sprawdzenie zgodności z JVM). Każda faza jest sterowana oddzielnymi regułami z plików konfiguracyjnych.
Na etapie obfuskacji ProGuard generuje plik mapping (mapping.txt), który odwzorowuje oryginalne nazwy na zobfuskowane. Ten plik jest krytyczny do dekodowania logów crash z wersji release za pomocą narzędzia retrace. Bez pliku mapping stack trace zamienia się w zestaw liter a(), b(), c() bez możliwości odtworzenia oryginalnego kontekstu.
| Faza ProGuard | Przeznaczenie | Rezultat |
|---|---|---|
| Shrink | Analiza grafu wywołań i usuwanie martwego kodu | Zmniejszenie liczby klas w APK |
| Optimize | Inline'owanie metod, usuwanie nieużywanych parametrów | Przyspieszenie wykonania kodu |
| Obfuscate | Zmiana nazw klas, pól i metod | Ochrona przed inżynierią wsteczną |
| Preverify | Dodawanie atrybutów StackMap dla JVM | Zgodność z Java 6+ |
R8 — to narzędzie obfuskacji i minifikacji nowej generacji od Google, po raz pierwszy zaprezentowane w Android Studio 3.3 (listopad 2018) i stające się standardem w AGP 3.4 (sierpień 2019). W przeciwieństwie do ProGuard, R8 jest częścią kompilatora D8/R8, który przekształca kod bajtowy Java do formatu DEX. R8 wykonuje wszystkie fazy — obfuskację, minifikację i optymalizację — w jednym przebiegu, bez przekazywania plików pośrednich między narzędziami.
Google opracowała R8 z dwoma celami: przyspieszyć kompilację (ProGuard działał jako zewnętrzne narzędzie) i zapewnić bezszwową integrację z nowoczesnym stosem Android (Desugar, Core Library Desugaring, D8). R8 jest napisany w Kotlin i Java i jest częścią repozytorium R8/Desugar w AOSP (Android Open Source Project).
Ważną zaletą R8 jest pełna wsteczna kompatybilność z ProGuard rules. Istniejące pliki .pro działają bez zmian. R8 obsługuje nawet specyficzne dyrektywy ProGuard, w tym -whyareyoukeeping, -printconfiguration i -printmapping. Oznacza to, że przejście z ProGuard na R8 odbywa się transparentnie: wystarczy zaktualizować AGP.
// build.gradle.kts — włączenie R8 przez minifyEnabled
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
// Podstawowa konfiguracja z Android SDK
getDefaultProguardFile("proguard-android-optimize.txt"),
// Niestandardowe reguły projektu
"proguard-rules.pro"
)
}
}
}Kod demonstruje standardową konfigurację kompilacji release. Flaga isMinifyEnabled = true aktywuje R8 do obfuskacji i optymalizacji. isShrinkResources = true dodatkowo usuwa nieużywane zasoby. getDefaultProguardFile ładuje podstawowe reguły z SDK, a proguard-rules.pro zawiera ustawienia specyficzne dla projektu.
Obfuskacja — to proces przekształcania kodu źródłowego w formę trudną do analizy dla człowieka, ale zachowującą pełną funkcjonalność. W kontekście Android obfuskacja oznacza zmianę nazw klas, metod i pól na krótkie, pozbawione znaczenia: com.example.app.auth.LoginManager zamienia się w a.a.a, metoda authenticateUser — w a, pole userToken — w b.
Pliki APK Android to archiwa, które można otworzyć dowolnym archiwizatorem (ZIP, 7z, WinRAR). Bez obfuskacji osoba atakująca otrzymuje pełną mapę aplikacji: nazwy pakietów, klas, metod i pól. Narzędzia takie jak jadx czy Bytecode Viewer przywracają prawie oryginalny kod Java z plików DEX w kilka sekund. Obfuskacja nie czyni kodu niepodatnym na ataki, ale znacząco podnosi próg wejścia: zamiast znaczących nazw czytelnik widzi a(), b(), c().
Typowe cele obfuskacji: ochrona logiki komercyjnej (algorytmy, formuły obliczeniowe), utrudnienie kradzieży kluczy API i tokenów, zapobieganie podmianie klas przez reflection, ochrona przed patchem i modyfikacją APK (repackage attack). W praktyce 70% zadań rozwiązuje właśnie zmiana nazw — po to uruchamia się ProGuard / R8.
Poniżej znajduje się typowy plik proguard-rules.pro dla projektu Android z Retrofit, Gson i Parcelable. Reguły -keep zachowują klasy i metody niezbędne do działania bibliotek przez reflection. Bez tych reguł R8 usunie lub zmieni nazwy klas, do których biblioteka odwołuje się po nazwie string.
# =====================
# Retrofit — zachowanie interfejsów
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions
# =====================
# Gson — serializacja 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 — usuwanie logów z wydania
# =====================
-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(...);
}
# =====================
# Klasy danych Kotlin — zachowujemy konstruktory
# =====================
-keepclassmembers class * {
@kotlin.Metadata <fields>;
}
# =====================
# Activity — punkt wejścia
# =====================
-keep class * extends android.app.Activity {
@android.annotation.SuppressLint <methods>;
}Każda dyrektywa w pliku .pro rozwiązuje konkretne zadanie. -keep zapobiega usunięciu lub zmianie nazwy całej klasy. -keepclassmembers chroni tylko elementy klasy (pola i metody), ale pozwala usunąć samą klasę, jeśli nie jest używana. -assumenosideeffects wskazuje R8, że wywołanie metody nie ma skutków ubocznych i może być bezpiecznie usunięte. Dyrektywa -keepattributes zachowuje metadane w kodzie bajtowym — adnotacje, sygnatury, wyjątki.
Reguła -keep,allowobfuscation,allowshrinking dla Retrofit pozwala R8 zmieniać nazwy interfejsów, ale nie usuwać ich. Jest to konieczne, ponieważ Retrofit odwołuje się do interfejsów przez dynamiczny proxy (java.lang.reflect.Proxy), a usunięcie spowoduje ClassNotFoundException w runtime. Analogicznie Gson używa reflection do dostępu do pól z adnotacją @SerializedName — bez -keepclassmembers pola zostaną usunięte jako nieużywane.
Minifikacja (shrinking) — proces usuwania nieużywanego kodu i zasobów z końcowej kompilacji. ProGuard i R8 analizują graf wywołań, zaczynając od punktów wejścia (Activity, Service, BroadcastReceiver), i usuwają klasy i metody, do których nie można dotrzeć w łańcuchu wywołań. ShrinkResources — dodatkowy etap, który usuwa nieużywane zasoby z res/ (layout, drawable, string, color).
Minifikacja daje największe korzyści w dużych projektach z bibliotekami. Typowy obraz: projekt używa 10% kodu z podłączonej biblioteki (np. Google Play Services). Bez minifikacji cały kod biblioteki trafia do APK. Z minifikacją R8 usuwa 70–90% kodu bibliotek, pozostawiając tylko faktycznie używane klasy i metody. Wpływa to bezpośrednio na rozmiar APK, czas ładowania i zużycie pamięci.
Mechanizm ShrinkResources działa w parze z minifikacją kodu. Po tym jak R8 określił, które klasy są używane, shrinking zasobów analizuje referencje do zasobów z kodu: R.layout.main, R.drawable.icon, getString(R.string.title). Wszystkie zasoby, do których nie ma bezpośredniego lub pośredniego odwołania, są usuwane z końcowego APK lub AAB. W tym celu używany jest plik zasobów resources.arsc i foldery res/.
Ważny niuans: zasoby mogą być wywoływane przez getIdentifier() lub Resources.getResourceName() po nazwie string, z pominięciem klasy R. W takich przypadkach R8 nie widzi bezpośredniego połączenia i może usunąć zasób, który jest faktycznie używany. Do ochrony takich zasobów istnieje dyrektywa -keep class **.R$* { *; } — zachowuje ona wszystkie identyfikatory klasy R.
<!-- Przykład: zasób używany tylko przez getIdentifier() -->
<string name="dynamic_title_welcome">Witamy</string>
<string name="dynamic_title_share">Udostępnij</string>
<!-- Kod Kotlin odwołujący się przez string -->
<!-- val title = getString(resources.getIdentifier( -->
<!-- \"dynamic_title_${type}\", \"string\", packageName)) -->W tym przypadku R8 nie widzi statycznego odwołania do dynamic_title_welcome w klasie R, ponieważ dostęp odbywa się przez getIdentifier z dynamiczną nazwą. Aby zachować takie zasoby, należy dodać w proguard-rules.pro dyrektywę -keepclassmembers class **.R$string { *; } — zabrania ona usuwania dowolnych pól ze wszystkich klas R$string.
| Dyrektywa | Przeznaczenie | Przykład |
|---|---|---|
| -keep | Zachowuje klasę i wszystkie jej elementy | -keep class com.example.api.** { *; } |
| -keepclassmembers | Zachowuje tylko elementy klasy | -keepclassmembers class * { @SerializedName <fields>; } |
| -keepattributes | Zachowuje metadane kodu bajtowego | -keepattributes *Annotation*, Signature |
| -assumenosideeffects | Usuwa wywołania bez skutków ubocznych | -assumenosideeffects class Log { d(...); } |
| -dontwarn | Pomija ostrzeżenia | -dontwarn com.example.legacy.** |
Pomimo tego że R8 jest następcą ProGuard, między narzędziami istnieją zasadnicze różnice w architekturze, wydajności i zachowaniu. Google oficjalnie zakończyła wsparcie ProGuard w Android Gradle Plugin od AGP 7.0, jednak ProGuard nadal jest używany w projektach, gdzie wymagane jest specyficzne zachowanie optymalizacji niedostępne w R8.
| Cecha | ProGuard | R8 |
|---|---|---|
| Twórca | GuardSquare (Eric Lafourge) | |
| Rok wydania | 2002 | 2018 (stabilny w 2019) |
| Architektura | 4 oddzielne fazy (shrink → optimize → obfuscate → preverify) | Jeden przebieg: shrink + optimize + obfuscate jednocześnie |
| Integracja w AGP | Zewnętrzne narzędzie uruchamiane po javac | Wbudowany w kompilator D8 DEX |
| Szybkość kompilacji | Wolniejszy 2–3 razy | Szybszy dzięki jednemu przebiegowi i natywnej integracji |
| Wsparcie Kotlin | Ograniczone (problemy z inline, lambdas, coroutines) | Pełne: coroutines, inline-funkcje, data class |
| Plik mapping | mapping.txt (kompatybilny z retrace) | mapping.txt (ten sam format) |
| Dostosowanie optymalizacji | 60+ opcji -optimizationpasses, -optimizations | Ograniczone: większość optymalizacji włączona domyślnie |
| Status wsparcia | Zastąpiony przez R8 (AGP 7.0+ nie używa) | Aktywny rozwój, część AOSP |
R8 agresywniej niż ProGuard usuwa kod, który uważa za martwy. Prowadzi to do sytuacji, gdy kompilacja w debug działa, a release pada z ClassNotFoundException lub NoSuchMethodException. Typowe przypadki: biblioteki używające reflection po nazwie klasy (Gson, Moshi, Retrofit, Room, Dagger); wywołania ServiceLoader lub java.util.ServiceLoader; dynamiczne proxy (java.lang.reflect.Proxy); metody natywne (JNI). Rozwiązanie — dodać -keep dla wszystkich klas wywoływanych przez reflection.
# Typowe problemy z reflection — R8 nie widzi statycznego powiązania
# Room — zachowujemy DAO i migracje
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }
# Dagger / Hilt — zachowujemy komponenty
-keep class * extends dagger.hilt.android.components.** { *; }
# JNI — nie zmieniamy nazw metod natywnych
-keepclasseswithmembernames class * {
native <methods>;
}
# Data Binding — zachowujemy klasy Binding
-keep class *.databinding.** { *; }Jeśli po dodaniu reguł kompilacja nadal pada, użyj flagi -printconfiguration full-config.txt w proguard-rules.pro. R8 wygeneruje pełny plik konfiguracji, który pokazuje, które reguły zostały zastosowane i które klasy są zachowywane. Przydatna jest także dyrektywa -whyareyoukeeping class com.example.MyClass — wypisuje ona powód, dla którego R8 zdecydował się zachować daną klasę.
Prawidłowa konfiguracja ProGuard rules — klucz do stabilnego działania obfuskacji bez błędów w runtime. Poniżej znajduje się proces konfiguracji krok po kroku dla nowego projektu lub projektu, w którym obfuskacja powoduje błędy.
Zacznij od podłączenia standardowego pliku Android SDK — proguard-android-optimize.txt. Zawiera on reguły dla podstawowych komponentów Android: Activity, Service, BroadcastReceiver, ContentProvider, View, Fragment. Ten plik znajduje się w folderze SDK: $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt. Jeśli używasz AGP, getDefaultProguardFile załaduje go automatycznie.
Każda popularna biblioteka ma zalecane reguły ProGuard. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — wszystkie wymagają specyficznych reguł -keep. Zazwyczaj reguły są dołączone do biblioteki AAR i podłączane automatycznie przez consumer guard rules. Sprawdź, czy biblioteka dostarcza plik proguard.txt wewnątrz AAR — to znak, że reguły są już uwzględnione.
Przed publikacją koniecznie przetestuj kompilację release na prawdziwym urządzeniu lub emulatorze. Problemy obfuskacji ujawniają się tylko w runtime. Sprawdź: autoryzację (logowanie/rejestrację), ładowanie danych z sieci, nawigację między ekranami, aparat i galerię, powiadomienia push, Deeplinks, WebView. Każdy crash w wersji release należy zdekodować przez retrace z plikiem mapping i dodać brakujące reguły -keep.
Plik mapping jest generowany w build/outputs/mapping/release/mapping.txt. Ten plik jest obowiązkowy do zachowania: bez niego nie można zdekodować logów crash z Google Play Console. Dołącz mapping.txt do systemu kontroli wersji lub przesyłaj go jako artefakt CI. Google Play Console przyjmuje plik mapping automatycznie przy przesyłaniu AAB z włączoną opcją uploading mapping.txt.
Poniżej znajduje się pełny workflow konfiguracji obfuskacji w pliku proguard-rules.pro z komentarzami dla każdej grupy reguł.
# ===========================================
# proguard-rules.pro — pełny przykład
# ===========================================
# --- Ustawienia ogólne ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify
# --- Komponenty Androida ---
-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 {}
# --- Serializacja ---
-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();
}
# --- Tylko R8: wymuszone zachowanie ---
# (ProGuard ignoruje tę dyrektywę)
-keep,allowobfuscation class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}Po konfiguracji wykonaj kompilację: ./gradlew assembleRelease. Sprawdź, czy w build/outputs/mapping/release/ pojawiły się pliki: mapping.txt (odpowiedniość oryginalnych i zobfuskowanych nazw), seeds.txt (klasy zachowane przez reguły -keep), usage.txt (klasy usunięte podczas minifikacji). Rozmiar APK po obfuskacji powinien zmniejszyć się o 20–50% w zależności od liczby podłączonych bibliotek.
Często zadawane pytania
R8 — następca ProGuard, opracowany przez Google. R8 wykonuje obfuskację, minifikację i optymalizację w jednym przebiegu, działa 2–3 razy szybciej niż ProGuard i jest zintegrowany bezpośrednio z Android Gradle Plugin. ProGuard używa czterech oddzielnych faz i wymaga zewnętrznego uruchomienia. Od AGP 7.0 ProGuard nie jest używany — domyślnie działa R8.
Tak, R8 używa tych samych reguł ProGuard rules (pliki .pro). Dyrektywy -keep, -keepclassmembers, -keepattributes, -assumenosideeffects działają identycznie. Podstawowe reguły pochodzą z proguard-android-optimize.txt z Android SDK, a specyficzne dla bibliotek (Retrofit, Room, Gson) dodaje się w proguard-rules.pro projektu. Bez tych reguł R8 może usunąć klasy niezbędne do działania bibliotek przez reflection.
R8 jest domyślnie włączony w Android Gradle Plugin od AGP 3.4. Aby aktywować minifikację, ustaw isMinifyEnabled = true w bloku release buildType pliku build.gradle.kts. Dodatkowa flaga isShrinkResources = true włącza usuwanie nieużywanych zasobów. W gradle.properties można wymusić wyłączenie R8 przez android.enableR8=false, ale nie jest to zalecane — R8 jest szybszy i stabilniejszy.
Obfuskacja — zmiana nazw klas, metod i pól na krótkie bez znaczenia (a, b, c). Klasa com.example.app.auth.LoginManager zamienia się w a.a.a, metoda authenticateUser — w a. Utrudnia to inżynierię wsteczną aplikacji, ale nie wpływa na logikę wykonania. ProGuard i R8 zmieniają nazwy tylko tych elementów, które nie są chronione regułami -keep. Plik mapping zachowuje odpowiedniość oryginalnych i zobfuskowanych nazw do dekodowania logów crash.
Do dekodowania stack trace używa się narzędzia retrace (wchodzi w skład ProGuard/R8 SDK). Komenda: retrace mapping.txt crash-stacktrace.txt. Plik mapping znajduje się w build/outputs/mapping/release/mapping.txt. Google Play Console również obsługuje przesyłanie mapping.txt przy publikacji AAB — logi crash są automatycznie dekodowane w konsoli. Bez pliku mapping stack trace będzie zawierał tylko zobfuskowane nazwy a.b.c(), co jest bezużyteczne do debugowania.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również