ProGuard en R8 — tools voor obfuscatie, minificatie en optimalisatie voor Android-apps. ProGuard, gemaakt in 2002, was lange tijd de de facto standaard voor de bescherming van Java-code. R8 — zijn opvolger, ontwikkeld door Google en ingebouwd in Android Gradle Plugin vanaf AGP 3.4. Beide tools verkleinen de APK-grootte, verwijderen dode code en bemoeilijken reverse engineering. Volgens Android Developers voert R8 de build 2–3 keer sneller uit dan ProGuard bij vergelijkbare obfuscatiekwaliteit.
Belangrijkste punten
ProGuard — is een open-source tool (Apache 2.0) voor obfuscatie, minificatie, optimalisatie en preverificatie van Java-bytecode. Ontwikkeld door Eric Lafourge in 2002 in het kader van het SourceForge-project. ProGuard neemt gecompileerde Java-klassen (.class) of JAR-archieven als invoer en levert verwerkte klassen van hetzelfde formaat, maar kleiner en met hernoemde elementen.
ProGuard was lange tijd de enige standaard voor de bescherming van Android-apps tegen reverse engineering. Google beval het gebruik ervan officieel aan in Android SDK en leverde de standaardconfiguratie in het bestand proguard-android-optimize.txt in de SDK tools. ProGuard werkte als een aparte tool, gestart na compilatie van Java-code naar bytecode en vóór het verpakken naar DEX.
ProGuard bestaat uit vier opeenvolgende fasen: shrink (verwijderen van ongebruikte klassen), optimize (optimalisatie van bytecode — inlining, verwijderen van dode code), obfuscate (hernoemen van klassen, methoden en velden naar korte namen), preverify (controleren van compatibiliteit met JVM). Elke fase wordt aangestuurd door afzonderlijke regels uit configuratiebestanden.
In de obfuscatie-fase genereert ProGuard een mapping-bestand (mapping.txt) dat de oorspronkelijke namen toewijst aan geobfusceerde namen. Dit bestand is cruciaal voor het decoderen van crashlogs uit release-builds met de tool retrace. Zonder mapping-bestand wordt de stack trace een reeks letters a(), b(), c() zonder mogelijkheid om de oorspronkelijke context te herstellen.
| ProGuard-fase | Doel | Resultaat |
|---|---|---|
| Shrink | Analyse van de aanroepgraaf en verwijderen van dode code | Vermindering van het aantal klassen in APK |
| Optimize | Inlinen van methoden, verwijderen van ongebruikte parameters | Versnelling van code-uitvoering |
| Obfuscate | Hernoemen van klassen, velden en methoden | Bescherming tegen reverse engineering |
| Preverify | Toevoegen van StackMap-attributen voor JVM | Compatibiliteit met Java 6+ |
R8 — is de volgende generatie obfuscatie- en minificatietool van Google, voor het eerst geïntroduceerd in Android Studio 3.3 (november 2018) en standaard geworden in AGP 3.4 (augustus 2019). In tegenstelling tot ProGuard maakt R8 deel uit van de D8/R8-compiler die Java-bytecode omzet naar DEX-formaat. R8 voert alle fasen — obfuscatie, minificatie en optimalisatie — in één keer uit, zonder tussenbestanden tussen tools.
Google heeft R8 ontwikkeld met twee doelen: de build versnellen (ProGuard werkte als externe tool) en naadloze integratie met de moderne Android-stack (Desugar, Core Library Desugaring, D8) garanderen. R8 is geschreven in Kotlin en Java en maakt deel uit van de R8/Desugar-repository in AOSP (Android Open Source Project).
Een belangrijk voordeel van R8 — volledige achterwaartse compatibiliteit met ProGuard rules. Bestaande .pro-bestanden werken zonder wijzigingen. R8 ondersteunt zelfs specifieke ProGuard-directieven, waaronder -whyareyoukeeping, -printconfiguration en -printmapping. Dit betekent dat de overstap van ProGuard naar R8 transparant verloopt: het volstaat om AGP te updaten.
// build.gradle.kts — R8 inschakelen via minifyEnabled
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
// Basisconfiguratie uit Android SDK
getDefaultProguardFile("proguard-android-optimize.txt"),
// Aangepaste projectregels
"proguard-rules.pro"
)
}
}
}De code toont de standaardconfiguratie van een release-build. De vlag isMinifyEnabled = true activeert R8 voor obfuscatie en optimalisatie. isShrinkResources = true verwijdert bovendien ongebruikte bronnen. getDefaultProguardFile laadt de basisregels uit de SDK, en proguard-rules.pro bevat projectspecifieke instellingen.
Obfuscatie — is het proces van het omzetten van broncode naar een vorm die moeilijk te analyseren is voor mensen, maar die volledige functionaliteit behoudt. In de Android-context betekent obfuscatie het hernoemen van klassen, methoden en velden naar korte, betekenisloze namen: com.example.app.auth.LoginManager wordt a.a.a, de methode authenticateUser wordt a, het veld userToken wordt b.
Android APK-bestanden zijn archieven die met elke archiver (ZIP, 7z, WinRAR) kunnen worden geopend. Zonder obfuscatie krijgt een aanvaller de volledige kaart van de app: pakket-, klasse-, methode- en veldnamen. Tools zoals jadx of Bytecode Viewer herstellen bijna de originele Java-code uit DEX-bestanden in enkele seconden. Obfuscatie maakt de code niet onkwetsbaar, maar verhoogt de drempel aanzienlijk: in plaats van betekenisvolle namen ziet de lezer a(), b(), c().
Typische doelen van obfuscatie: bescherming van commerciële logica (algoritmen, berekeningsformules), bemoeilijken van diefstal van API-sleutels en tokens, voorkomen van klassevervanging via reflection, bescherming tegen patching en modificatie van APK (repackage attack). In de praktijk lost 70% van de taken juist het hernoemen op — daarom wordt ProGuard / R8 gestart.
Hieronder staat een typisch proguard-rules.pro-bestand voor een Android-project met Retrofit, Gson en Parcelable. De -keep-regels behouden de klassen en methoden die nodig zijn voor de werking van bibliotheken via reflection. Zonder deze regels verwijdert of hernoemt R8 de klassen waartoe de bibliotheek via een stringnaam toegang krijgt.
# =====================
# Retrofit — interfaces behouden
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions
# =====================
# Gson — JSON-serialisatie
# =====================
-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 — logs verwijderen uit 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 data-klassen — constructors behouden
# =====================
-keepclassmembers class * {
@kotlin.Metadata <fields>;
}
# =====================
# Activity — ingangspunt
# =====================
-keep class * extends android.app.Activity {
@android.annotation.SuppressLint <methods>;
}Elk directive in het .pro-bestand lost een specifieke taak op. -keep voorkomt het verwijderen of hernoemen van een hele klasse. -keepclassmembers beschermt alleen de leden van de klasse (velden en methoden), maar laat verwijdering van de klasse zelf toe als deze niet wordt gebruikt. -assumenosideeffects geeft R8 aan dat een methodeaanroep geen bijwerkingen heeft en veilig kan worden verwijderd. Het directive -keepattributes behoudt metadata in de bytecode — annotaties, handtekeningen, uitzonderingen.
De regel -keep,allowobfuscation,allowshrinking voor Retrofit staat R8 toe interfaces te hernoemen, maar niet te verwijderen. Dit is noodzakelijk omdat Retrofit via dynamische proxy (java.lang.reflect.Proxy) toegang krijgt tot interfaces en verwijdering leidt tot ClassNotFoundException in runtime. Evenzo gebruikt Gson reflection voor toegang tot velden met @SerializedName-annotatie — zonder -keepclassmembers worden velden als ongebruikt verwijderd.
Minificatie (shrinking) — het proces van het verwijderen van ongebruikte code en bronnen uit de uiteindelijke build. ProGuard en R8 analyseren de aanroepgraaf vanaf de ingangspunten (Activity, Service, BroadcastReceiver) en verwijderen klassen en methoden die niet via de aanroepketen bereikt kunnen worden. ShrinkResources — een extra fase die ongebruikte bronnen uit res/ (layout, drawable, string, color) verwijdert.
Minificatie levert de grootste winst op in grote projecten met bibliotheken. Typisch beeld: het project gebruikt 10% van de code uit een aangesloten bibliotheek (bijv. Google Play Services). Zonder minificatie komt alle code van de bibliotheek in de APK terecht. Met minificatie verwijdert R8 70–90% van de bibliotheekcode en laat alleen daadwerkelijk gebruikte klassen en methoden over. Dit heeft directe invloed op de APK-grootte, laadtijd en geheugengebruik.
Het mechanisme ShrinkResources werkt samen met codeminificatie. Nadat R8 heeft bepaald welke klassen worden gebruikt, analyseert de resource-shrinking verwijzingen naar bronnen uit de code: R.layout.main, R.drawable.icon, getString(R.string.title). Alle bronnen waarnaar geen directe of indirecte verwijzing bestaat, worden verwijderd uit de uiteindelijke APK of AAB. Hiervoor wordt het bronnenbestand resources.arsc en de mappen res/ gebruikt.
Een belangrijke nuance: bronnen kunnen via getIdentifier() of Resources.getResourceName() op stringnaam worden aangeroepen, waarbij de R-klasse wordt omzeild. In dergelijke gevallen ziet R8 de directe koppeling niet en kan het een bron verwijderen die daadwerkelijk wordt gebruikt. Voor de bescherming van dergelijke bronnen bestaat het directive -keep class **.R$* { *; } — het behoudt alle identificaties van de R-klasse.
<!-- Voorbeeld: bron die alleen via getIdentifier() wordt gebruikt -->
<string name="dynamic_title_welcome">Welkom</string>
<string name="dynamic_title_share">Delen</string>
<!-- Kotlin-code die via string benadert -->
<!-- val title = getString(resources.getIdentifier( -->
<!-- \"dynamic_title_${type}\", \"string\", packageName)) -->In dit geval ziet R8 geen statische verwijzing naar dynamic_title_welcome in de R-klasse, omdat de toegang via getIdentifier met een dynamische naam plaatsvindt. Om dergelijke bronnen te behouden, moet in proguard-rules.pro het directive -keepclassmembers class **.R$string { *; } worden toegevoegd — het verbiedt het verwijderen van velden uit alle R$string-klassen.
| Directive | Doel | Voorbeeld |
|---|---|---|
| -keep | Behoudt de klasse en al zijn leden | -keep class com.example.api.** { *; } |
| -keepclassmembers | Behoudt alleen de leden van de klasse | -keepclassmembers class * { @SerializedName <fields>; } |
| -keepattributes | Behoudt metadata van bytecode | -keepattributes *Annotation*, Signature |
| -assumenosideeffects | Verwijdert aanroepen zonder bijwerkingen | -assumenosideeffects class Log { d(...); } |
| -dontwarn | Onderdrukt waarschuwingen | -dontwarn com.example.legacy.** |
Hoewel R8 de opvolger is van ProGuard, zijn er fundamentele verschillen in architectuur, prestaties en gedrag tussen de tools. Google heeft de ondersteuning voor ProGuard in Android Gradle Plugin officieel stopgezet vanaf AGP 7.0, maar ProGuard wordt nog steeds gebruikt in projecten waar specifiek optimalisatiegedrag vereist is dat niet beschikbaar is in R8.
| Kenmerk | ProGuard | R8 |
|---|---|---|
| Ontwikkelaar | GuardSquare (Eric Lafourge) | |
| Jaar van uitgave | 2002 | 2018 (stabiel in 2019) |
| Architectuur | 4 aparte fasen (shrink → optimize → obfuscate → preverify) | Eén keer: shrink + optimize + obfuscate gelijktijdig |
| Integratie in AGP | Externe tool, gestart na javac | Ingebouwd in D8 DEX-compiler |
| Buildsnelheid | 2–3 keer langzamer | Sneller door één keer en native integratie |
| Kotlin-ondersteuning | Beperkt (problemen met inline, lambdas, coroutines) | Volledig: coroutines, inline-functies, data class |
| Mapping-bestand | mapping.txt (compatibel met retrace) | mapping.txt (zelfde formaat) |
| Aanpassing optimalisatie | 60+ opties -optimizationpasses, -optimizations | Beperkt: meeste optimalisaties standaard ingeschakeld |
| Ondersteuningsstatus | Vervangen door R8 (AGP 7.0+ gebruikt niet) | Actieve ontwikkeling, onderdeel van AOSP |
R8 verwijdert agressiever dan ProGuard code die het als dood beschouwt. Dit leidt tot situaties waarin de debug-build werkt, maar de release-build crasht met ClassNotFoundException of NoSuchMethodException. Typische gevallen: bibliotheken die reflection op klassenaam gebruiken (Gson, Moshi, Retrofit, Room, Dagger); aanroepen van ServiceLoader of java.util.ServiceLoader; dynamische proxies (java.lang.reflect.Proxy); native methoden (JNI). Oplossing — voeg -keep toe voor alle klassen die via reflection worden aangeroepen.
# Typische reflection-problemen — R8 ziet geen statische koppeling
# Room — DAO en migraties behouden
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }
# Dagger / Hilt — componenten behouden
-keep class * extends dagger.hilt.android.components.** { *; }
# JNI — native methoden niet hernoemen
-keepclasseswithmembernames class * {
native <methods>;
}
# Data Binding — Binding-klassen behouden
-keep class *.databinding.** { *; }Als de build na het toevoegen van regels nog steeds crasht, gebruik dan de vlag -printconfiguration full-config.txt in proguard-rules.pro. R8 genereert een volledig configuratiebestand dat laat zien welke regels zijn toegepast en welke klassen worden behouden. Ook nuttig is het directive -whyareyoukeeping class com.example.MyClass — het geeft de reden waarom R8 heeft besloten de betreffende klasse te behouden.
Het correct instellen van ProGuard rules — de sleutel tot stabiele obfuscatie zonder bugs in runtime. Hieronder staat het stapsgewijze proces voor het instellen voor een nieuw project of voor een project waarin obfuscatie fouten veroorzaakt.
Begin met het aansluiten van het standaard Android SDK-bestand — proguard-android-optimize.txt. Het bevat regels voor basis Android-componenten: Activity, Service, BroadcastReceiver, ContentProvider, View, Fragment. Dit bestand bevindt zich in de SDK-map: $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt. Als u AGP gebruikt, laadt getDefaultProguardFile het automatisch.
Elke populaire bibliotheek heeft aanbevolen ProGuard-regels. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — ze vereisen allemaal specifieke -keep-regels. Meestal zijn de regels opgenomen in de AAR-bibliotheek en worden ze automatisch aangesloten via consumer guard rules. Controleer of de bibliotheek het bestand proguard.txt in de AAR levert — dit is een teken dat de regels al in aanmerking zijn genomen.
Test vóór publicatie de release-build verplicht op een echt apparaat of emulator. Obfuscatieproblemen manifesteren zich alleen in runtime. Controleer: authenticatie (aanmelden/registreren), gegevens laden uit het netwerk, navigatie tussen schermen, camera en galerij, pushmeldingen, Deeplinks, WebView. Elke crash in de release-build moet worden gedecodeerd via retrace met het mapping-bestand en er moeten ontbrekende -keep-regels worden toegevoegd.
Het mapping-bestand wordt gegenereerd in build/outputs/mapping/release/mapping.txt. Dit bestand is verplicht om te bewaren: zonder is het onmogelijk om crashlogs uit Google Play Console te decoderen. Neem mapping.txt op in het versiebeheersysteem of upload het als CI-artefact. Google Play Console accepteert het mapping-bestand automatisch bij het uploaden van AAB met ingeschakelde uploading mapping.txt.
Hieronder staat de volledige workflow voor het instellen van obfuscatie in het bestand proguard-rules.pro met opmerkingen voor elke groep regels.
# ===========================================
# proguard-rules.pro — volledig voorbeeld
# ===========================================
# --- Algemene instellingen ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify
# --- Android-componenten ---
-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 {}
# --- Serialisatie ---
-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();
}
# --- Alleen R8: geforceerd behouden ---
# (ProGuard negeert dit directive)
-keep,allowobfuscation class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}Voer na het instellen de build uit: ./gradlew assembleRelease. Controleer of in build/outputs/mapping/release/ de bestanden zijn verschenen: mapping.txt (overeenkomst van originele en geobfusceerde namen), seeds.txt (klassen behouden door -keep-regels), usage.txt (klassen verwijderd bij minificatie). De APK-grootte na obfuscatie zou met 20–50% moeten afnemen, afhankelijk van het aantal aangesloten bibliotheken.
Veelgestelde vragen
R8 — de opvolger van ProGuard, ontwikkeld door Google. R8 voert obfuscatie, minificatie en optimalisatie in één keer uit, werkt 2–3 keer sneller dan ProGuard en is direct geïntegreerd in Android Gradle Plugin. ProGuard gebruikt vier aparte fasen en vereist externe lancering. Vanaf AGP 7.0 wordt ProGuard niet meer gebruikt — standaard werkt R8.
Ja, R8 gebruikt dezelfde ProGuard rules (.pro-bestanden). De directives -keep, -keepclassmembers, -keepattributes, -assumenosideeffects werken identiek. De basisregels komen uit proguard-android-optimize.txt van Android SDK, en specifieke voor bibliotheken (Retrofit, Room, Gson) worden toegevoegd in proguard-rules.pro van het project. Zonder deze regels kan R8 klassen verwijderen die nodig zijn voor de werking van bibliotheken via reflection.
R8 is standaard ingeschakeld in Android Gradle Plugin vanaf AGP 3.4. Om minificatie te activeren, stelt u isMinifyEnabled = true in in het release buildType-blok van build.gradle.kts. De extra vlag isShrinkResources = true schakelt het verwijderen van ongebruikte bronnen in. In gradle.properties kan R8 worden uitgeschakeld via android.enableR8=false, maar dit wordt niet aanbevolen — R8 is sneller en stabieler.
Obfuscatie — het hernoemen van klassen, methoden en velden naar korte betekenisloze namen (a, b, c). De klasse com.example.app.auth.LoginManager wordt a.a.a, de methode authenticateUser wordt a. Dit bemoeilijkt reverse engineering van de app, maar heeft geen invloed op de uitvoeringslogica. ProGuard en R8 hernoemen alleen elementen die niet worden beschermd door -keep-regels. Het mapping-bestand behoudt de overeenkomst van originele en geobfusceerde namen voor het decoderen van crashlogs.
Voor het decoderen van stack traces wordt de tool retrace (onderdeel van ProGuard/R8 SDK) gebruikt. Commando: retrace mapping.txt crash-stacktrace.txt. Het mapping-bestand bevindt zich in build/outputs/mapping/release/mapping.txt. Google Play Console ondersteunt ook het uploaden van mapping.txt bij publicatie van AAB — crashlogs worden automatisch gedecodeerd in de console. Zonder mapping-bestand bevat de stack trace alleen geobfusceerde namen a.b.c(), wat nutteloos is voor debugging.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook