ProGuard/R8: istota, obfuskacja i ochrona aplikacji Android

Autor: IT Sectr Opublikowano: 2026-02-14 Czas czytania: 8 min

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 — narzędzie do obfuskacji i optymalizacji kodu bajtowego Java, standard dla Androida od lat 2000.
  • R8 — następca ProGuard od Google, wbudowany w AGP, wykonuje obfuskację, minifikację i optymalizację w jednym przebiegu
  • Obfuskacja zmienia nazwy klas i metod na krótkie, utrudniając inżynierię wsteczną aplikacji
  • Minifikacja usuwa nieużywane klasy, metody i pola, zmniejszając rozmiar końcowego APK/AAB
  • ProGuard rules (pliki .pro) sterują tym, które części kodu są zachowywane, a które obfuskowane lub usuwane

Czym jest ProGuard?

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.

Architektura ProGuard

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 ProGuardPrzeznaczenieRezultat
ShrinkAnaliza grafu wywołań i usuwanie martwego koduZmniejszenie liczby klas w APK
OptimizeInline'owanie metod, usuwanie nieużywanych parametrówPrzyspieszenie wykonania kodu
ObfuscateZmiana nazw klas, pól i metodOchrona przed inżynierią wsteczną
PreverifyDodawanie atrybutów StackMap dla JVMZgodność z Java 6+

Czym jest R8?

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.

kotlin
// 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 kodu w Android

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.

Po co potrzebna jest obfuskacja

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.

Przykład reguł ProGuard

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.

pro
# =====================
# 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 i ShrinkResources

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.

ShrinkResources w działaniu

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.

xml
<!-- 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.

DyrektywaPrzeznaczeniePrzykład
-keepZachowuje klasę i wszystkie jej elementy-keep class com.example.api.** { *; }
-keepclassmembersZachowuje tylko elementy klasy-keepclassmembers class * { @SerializedName <fields>; }
-keepattributesZachowuje metadane kodu bajtowego-keepattributes *Annotation*, Signature
-assumenosideeffectsUsuwa wywołania bez skutków ubocznych-assumenosideeffects class Log { d(...); }
-dontwarnPomija ostrzeżenia-dontwarn com.example.legacy.**

R8 vs ProGuard: kluczowe różnice

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.

Tabela porównawcza

CechaProGuardR8
TwórcaGuardSquare (Eric Lafourge)Google
Rok wydania20022018 (stabilny w 2019)
Architektura4 oddzielne fazy (shrink → optimize → obfuscate → preverify)Jeden przebieg: shrink + optimize + obfuscate jednocześnie
Integracja w AGPZewnętrzne narzędzie uruchamiane po javacWbudowany w kompilator D8 DEX
Szybkość kompilacjiWolniejszy 2–3 razySzybszy dzięki jednemu przebiegowi i natywnej integracji
Wsparcie KotlinOgraniczone (problemy z inline, lambdas, coroutines)Pełne: coroutines, inline-funkcje, data class
Plik mappingmapping.txt (kompatybilny z retrace)mapping.txt (ten sam format)
Dostosowanie optymalizacji60+ opcji -optimizationpasses, -optimizationsOgraniczone: większość optymalizacji włączona domyślnie
Status wsparciaZastąpiony przez R8 (AGP 7.0+ nie używa)Aktywny rozwój, część AOSP

Kiedy R8 może zepsuć kompilację

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.

pro
# 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ę.

Konfiguracja ProGuard rules

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.

Krok 1: Podstawowa konfiguracja

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.

Krok 2: Biblioteki

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.

Krok 3: Testowanie kompilacji release

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.

Krok 4: Plik mapping i CI

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ł.

pro
# ===========================================
# 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

Czym R8 różni się od ProGuard?

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.

Czy trzeba pisać ProGuard rules przy użyciu 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.

Jak włączyć R8 w projekcie Android?

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.

Co to jest obfuskacja kodu w Android?

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.

Jak debugować crash-log z zobfuskowanej aplikacji?

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

  • ProGuard — klasyczne narzędzie do obfuskacji i optymalizacji kodu bajtowego Java, składające się z czterech następujących po sobie faz
  • R8 — nowoczesny następca od Google, wbudowany w AGP, wykonujący wszystkie fazy w jednym przebiegu z wydajnością 2–3 razy wyższą
  • Obfuskacja zmienia nazwy klas, metod i pól na krótkie, utrudniając inżynierię wsteczną i chroniąc logikę komercyjną aplikacji
  • Minifikacja usuwa nieużywany kod i zasoby, zmniejszając rozmiar APK o 20–50% w typowych projektach
  • ProGuard rules (pliki .pro) sterują zachowaniem obfuskacji — dyrektywy -keep, -keepclassmembers, -assumenosideeffects określają, które elementy są zachowywane, usuwane lub zmieniane
  • Plik mapping (mapping.txt) — krytycznie ważny artefakt kompilacji do dekodowania logów crash z wersji release przez retrace
  • Testowanie kompilacji release na prawdziwym urządzeniu jest obowiązkowe — problemy obfuskacji ujawniają się tylko w runtime i wymagają dodania brakujących reguł -keep

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.

Omów projekt

Przeczytaj również