ProGuard și R8 — instrumente de ofuscare, minificare și optimizare pentru aplicațiile Android. ProGuard, creat în 2002, a fost mult timp standardul de facto pentru protejarea codului Java. R8 — succesorul său, dezvoltat de Google și integrat în Android Gradle Plugin începând cu AGP 3.4. Ambele instrumente reduc dimensiunea APK, elimină codul mort și complică ingineria inversă. Conform Android Developers, R8 execută compilarea de 2-3 ori mai rapid decât ProGuard la o calitate comparabilă a ofuscării.
Principalele puncte
ProGuard — este un instrument open-source (Apache 2.0) pentru ofuscarea, minificarea, optimizarea și preverificarea bytecodului Java. Dezvoltat de Eric Lafourge în 2002 în cadrul proiectului SourceForge. ProGuard primește la intrare clase Java compilate (.class) sau arhive JAR și produce clase procesate de același format, dar de dimensiune mai mică și cu elemente redenumite.
Mult timp ProGuard a fost singurul standard pentru protejarea aplicațiilor Android împotriva ingineriei inverse. Google recomanda oficial utilizarea sa în Android SDK și livra configurația implicită în fișierul proguard-android-optimize.txt din SDK tools. ProGuard funcționa ca un instrument separat, lansat după compilarea codului Java în bytecod și înainte de ambalarea în DEX.
ProGuard constă din patru faze consecutive: shrink (eliminarea claselor neutilizate), optimize (optimizarea bytecodului — inline, eliminarea codului mort), obfuscate (redenumirea claselor, metodelor și câmpurilor în nume scurte), preverify (verificarea compatibilității cu JVM). Fiecare fază este controlată de reguli separate din fișierele de configurare.
În faza de ofuscare, ProGuard generează un fișier mapping (mapping.txt) care mapă numele originale la cele ofuscate. Acest fișier este critic pentru decodarea logurilor de crash din versiunile release prin utilitarul retrace. Fără fișierul mapping, stack trace se transformă într-un set de litere a(), b(), c() fără posibilitatea de a restabili contextul original.
| Faza ProGuard | Scop | Rezultat |
|---|---|---|
| Shrink | Analiza grafului de apeluri și eliminarea codului mort | Reducerea numărului de clase în APK |
| Optimize | Inlinarea metodelor, eliminarea parametrilor neutilizați | Accelerarea execuției codului |
| Obfuscate | Redenumirea claselor, câmpurilor și metodelor | Protecție împotriva ingineriei inverse |
| Preverify | Adăugarea atributelor StackMap pentru JVM | Compatibilitate cu Java 6+ |
R8 — este instrumentul de ofuscare și minificare de generație următoare de la Google, prezentat pentru prima dată în Android Studio 3.3 (noiembrie 2018) și devenit standard în AGP 3.4 (august 2019). Spre deosebire de ProGuard, R8 face parte din compilatorul D8/R8 care transformă bytecodul Java în format DEX. R8 execută toate fazele — ofuscarea, minificarea și optimizarea — într-o singură trecere, fără transmiterea fișierelor intermediare între instrumente.
Google a dezvoltat R8 cu două scopuri: accelerarea compilării (ProGuard funcționa ca instrument extern) și asigurarea unei integrări perfecte cu stiva modernă Android (Desugar, Core Library Desugaring, D8). R8 este scris în Kotlin și Java și face parte din depozitul R8/Desugar din AOSP (Android Open Source Project).
Un avantaj important al R8 — compatibilitatea completă retroactivă cu ProGuard rules. Fișierele .pro existente funcționează fără modificări. R8 suportă chiar directive specifice ProGuard, inclusiv -whyareyoukeeping, -printconfiguration și -printmapping. Aceasta înseamnă că trecerea de la ProGuard la R8 are loc transparent: este suficient să actualizați AGP.
// build.gradle.kts — activarea R8 prin minifyEnabled
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
// Configurarea de bază din Android SDK
getDefaultProguardFile("proguard-android-optimize.txt"),
// Reguli personalizate ale proiectului
"proguard-rules.pro"
)
}
}
}Codul demonstrează configurația standard a compilării release. Flagul isMinifyEnabled = true activează R8 pentru ofuscare și optimizare. isShrinkResources = true elimină suplimentar resursele neutilizate. getDefaultProguardFile încarcă regulile de bază din SDK, iar proguard-rules.pro conține setări specifice proiectului.
Ofuscarea — este procesul de transformare a codului sursă într-o formă dificil de analizat de către om, dar care păstrează funcționalitatea completă. În contextul Android, ofuscarea înseamnă redenumirea claselor, metodelor și câmpurilor în nume scurte, lipsite de sens: com.example.app.auth.LoginManager se transformă în a.a.a, metoda authenticateUser — în a, câmpul userToken — în b.
Fișierele APK Android sunt arhive care pot fi deschise cu orice arhivator (ZIP, 7z, WinRAR). Fără ofuscare, atacatorul obține harta completă a aplicației: numele pachetelor, claselor, metodelor și câmpurilor. Instrumente precum jadx sau Bytecode Viewer restaurează aproape codul Java original din fișierele DEX în câteva secunde. Ofuscarea nu face codul invulnerabil, dar crește semnificativ pragul de intrare: în loc de nume semnificative, cititorul vede a(), b(), c().
Obiective tipice ale ofuscării: protejarea logicii comerciale (algoritmi, formule de calcul), îngreunarea furtului de chei API și token-uri, prevenirea înlocuirii claselor prin reflection, protecția împotriva patching-ului și modificării APK (repackage attack). În practică, 70% din sarcini le rezolvă tocmai redenumirea — de aceea se lansează ProGuard / R8.
Mai jos este un fișier tipic proguard-rules.pro pentru un proiect Android cu Retrofit, Gson și Parcelable. Regulile -keep păstrează clasele și metodele necesare pentru funcționarea bibliotecilor prin reflection. Fără aceste reguli, R8 va elimina sau redenumi clasele la care biblioteca accesează după numele de string.
# =====================
# Retrofit — păstrarea interfețelor
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions
# =====================
# Gson — serializare 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 — eliminarea logurilor din 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(...);
}
# =====================
# Clase de date Kotlin — păstrăm constructorii
# =====================
-keepclassmembers class * {
@kotlin.Metadata <fields>;
}
# =====================
# Activity — punct de intrare
# =====================
-keep class * extends android.app.Activity {
@android.annotation.SuppressLint <methods>;
}Fiecare directivă în fișierul .pro rezolvă o sarcină concretă. -keep previne eliminarea sau redenumirea întregii clase. -keepclassmembers protejează doar membrii clasei (câmpuri și metode), dar permite eliminarea clasei în sine dacă nu este utilizată. -assumenosideeffects indică R8 că apelul metodei nu are efecte secundare și poate fi eliminat în siguranță. Directiva -keepattributes păstrează metadatele în bytecod — adnotări, semnături, excepții.
Regula -keep,allowobfuscation,allowshrinking pentru Retrofit permite R8 să redenumească interfețele, dar nu să le elimine. Acest lucru este necesar deoarece Retrofit accesează interfețele prin proxy dinamic (java.lang.reflect.Proxy), iar eliminarea va duce la ClassNotFoundException în runtime. În mod similar, Gson folosește reflection pentru accesul la câmpurile cu adnotarea @SerializedName — fără -keepclassmembers câmpurile vor fi eliminate ca neutilizate.
Minificarea (shrinking) — procesul de eliminare a codului și resurselor neutilizate din compilarea finală. ProGuard și R8 analizează graful de apeluri, începând de la punctele de intrare (Activity, Service, BroadcastReceiver), și elimină clasele și metodele la care nu se poate ajunge prin lanțul de apeluri. ShrinkResources — etapa suplimentară care elimină resursele neutilizate din res/ (layout, drawable, string, color).
Minificarea oferă cel mai mare câștig în proiectele mari cu biblioteci. Tabloul tipic: proiectul folosește 10% din codul bibliotecii conectate (de exemplu, Google Play Services). Fără minificare, întreg codul bibliotecii ajunge în APK. Cu minificare, R8 elimină 70–90% din codul bibliotecilor, lăsând doar clasele și metodele efectiv utilizate. Acest lucru afectează direct dimensiunea APK, timpul de încărcare și consumul de memorie.
Mecanismul ShrinkResources funcționează împreună cu minificarea codului. După ce R8 a determinat ce clase sunt utilizate, shrinking-ul resurselor analizează referințele la resurse din cod: R.layout.main, R.drawable.icon, getString(R.string.title). Toate resursele care nu au o referință directă sau indirectă sunt eliminate din APK sau AAB final. Pentru aceasta se utilizează fișierul de resurse resources.arsc și folderele res/.
Un aspect important: resursele pot fi apelate prin getIdentifier() sau Resources.getResourceName() după numele de string, ocolind clasa R. În astfel de cazuri, R8 nu vede legătura directă și poate elimina o resursă care este efectiv utilizată. Pentru protejarea acestor resurse există directiva -keep class **.R$* { *; } — ea păstrează toți identificatorii clasei R.
<!-- Exemplu: resursă utilizată doar prin getIdentifier() -->
<string name="dynamic_title_welcome">Bun venit</string>
<string name="dynamic_title_share">Distribuie</string>
<!-- Cod Kotlin care accesează prin string -->
<!-- val title = getString(resources.getIdentifier( -->
<!-- \"dynamic_title_${type}\", \"string\", packageName)) -->În acest caz, R8 nu vede referința statică la dynamic_title_welcome în clasa R, deoarece accesul are loc prin getIdentifier cu un nume dinamic. Pentru a păstra astfel de resurse, trebuie adăugată în proguard-rules.pro directiva -keepclassmembers class **.R$string { *; } — ea interzice eliminarea oricăror câmpuri din toate clasele R$string.
| Directivă | Scop | Exemplu |
|---|---|---|
| -keep | Păstrează clasa și toți membrii săi | -keep class com.example.api.** { *; } |
| -keepclassmembers | Păstrează doar membrii clasei | -keepclassmembers class * { @SerializedName <fields>; } |
| -keepattributes | Păstrează metadatele bytecodului | -keepattributes *Annotation*, Signature |
| -assumenosideeffects | Elimină apelurile fără efecte secundare | -assumenosideeffects class Log { d(...); } |
| -dontwarn | Suprimă avertismentele | -dontwarn com.example.legacy.** |
Deși R8 este succesorul ProGuard, există diferențe principiale în arhitectură, performanță și comportament între instrumente. Google a încetat oficial suportul pentru ProGuard în Android Gradle Plugin începând cu AGP 7.0, însă ProGuard continuă să fie utilizat în proiecte unde este necesar un comportament specific de optimizare indisponibil în R8.
| Caracteristică | ProGuard | R8 |
|---|---|---|
| Dezvoltator | GuardSquare (Eric Lafourge) | |
| Anul lansării | 2002 | 2018 (stabil în 2019) |
| Arhitectură | 4 faze separate (shrink → optimize → obfuscate → preverify) | O singură trecere: shrink + optimize + obfuscate simultan |
| Integrare în AGP | Instrument extern, lansat după javac | Integrat în compilatorul D8 DEX |
| Viteza de compilare | De 2-3 ori mai lent | Mai rapid datorită unei singure treceri și integrării native |
| Suport Kotlin | Limitat (probleme cu inline, lambdas, coroutines) | Complet: coroutines, funcții inline, data class |
| Fișier mapping | mapping.txt (compatibil cu retrace) | mapping.txt (același format) |
| Personalizare optimizare | 60+ opțiuni -optimizationpasses, -optimizations | Limitată: majoritatea optimizărilor sunt activate implicit |
| Starea suportului | Înlocuit cu R8 (AGP 7.0+ nu utilizează) | Dezvoltare activă, parte a AOSP |
R8 elimină mai agresiv decât ProGuard codul pe care îl consideră mort. Aceasta duce la situații în care compilarea debug funcționează, iar release cade cu ClassNotFoundException sau NoSuchMethodException. Cazuri tipice: biblioteci care folosesc reflection după numele clasei (Gson, Moshi, Retrofit, Room, Dagger); apeluri ServiceLoader sau java.util.ServiceLoader; proxy dinamice (java.lang.reflect.Proxy); metode native (JNI). Soluție — adăugați -keep pentru toate clasele care sunt apelate prin reflection.
# Probleme tipice de reflection — R8 nu vede legătura statică
# Room — păstrăm DAO și migrările
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }
# Dagger / Hilt — păstrăm componentele
-keep class * extends dagger.hilt.android.components.** { *; }
# JNI — nu redenumim metodele native
-keepclasseswithmembernames class * {
native <methods>;
}
# Data Binding — păstrăm clasele Binding
-keep class *.databinding.** { *; }Dacă după adăugarea regulilor compilarea încă cade, utilizați flagul -printconfiguration full-config.txt în proguard-rules.pro. R8 va genera un fișier complet de configurare care arată ce reguli au fost aplicate și ce clase sunt păstrate. De asemenea, este utilă directiva -whyareyoukeeping class com.example.MyClass — ea afișează motivul pentru care R8 a decis să păstreze clasa respectivă.
Configurarea corectă a ProGuard rules — cheia pentru o funcționare stabilă a ofuscării fără bug-uri în runtime. Mai jos este procesul pas cu pas de configurare pentru un proiect nou sau pentru un proiect în care ofuscarea cauzează erori.
Începeți prin conectarea fișierului standard Android SDK — proguard-android-optimize.txt. Acesta conține reguli pentru componentele de bază Android: Activity, Service, BroadcastReceiver, ContentProvider, View, Fragment. Acest fișier se află în folderul SDK: $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt. Dacă utilizați AGP, getDefaultProguardFile îl va încărca automat.
Fiecare bibliotecă populară are reguli ProGuard recomandate. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — toate necesită reguli -keep specifice. De obicei, regulile sunt incluse în biblioteca AAR și se conectează automat prin consumer guard rules. Verificați dacă biblioteca furnizează fișierul proguard.txt în interiorul AAR — acesta este un semn că regulile sunt deja luate în considerare.
Înainte de publicare, testați obligatoriu compilarea release pe un dispozitiv real sau emulator. Problemele de ofuscare se manifestă doar în runtime. Verificați: autorizarea (autentificare/înregistrare), încărcarea datelor din rețea, navigarea între ecrane, camera și galeria, notificările push, Deeplinks, WebView. Fiecare crash în versiunea release trebuie decodat prin retrace cu fișierul mapping și trebuie adăugate reguli -keep lipsă.
Fișierul mapping este generat în build/outputs/mapping/release/mapping.txt. Acest fișier este obligatoriu de păstrat: fără el nu se pot decoda logurile de crash din Google Play Console. Includeți mapping.txt în sistemul de control al versiunilor sau încărcați-l ca artefact CI. Google Play Console acceptă fișierul mapping automat la încărcarea AAB cu uploading mapping.txt activat.
Mai jos este fluxul complet de configurare a ofuscării în fișierul proguard-rules.pro cu comentarii pentru fiecare grup de reguli.
# ===========================================
# proguard-rules.pro — exemplu complet
# ===========================================
# --- Setări generale ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify
# --- Componente 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 {}
# --- Serializare ---
-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();
}
# --- Doar R8: păstrare forțată ---
# (ProGuard ignoră această directivă)
-keep,allowobfuscation class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}După configurare, executați compilarea: ./gradlew assembleRelease. Verificați că în build/outputs/mapping/release/ au apărut fișierele: mapping.txt (corespondența numelor originale cu cele ofuscate), seeds.txt (clasele păstrate de regulile -keep), usage.txt (clasele eliminate la minificare). Dimensiunea APK după ofuscare ar trebui să se reducă cu 20–50% în funcție de numărul de biblioteci conectate.
Întrebări frecvente
R8 — succesorul ProGuard, dezvoltat de Google. R8 execută ofuscarea, minificarea și optimizarea într-o singură trecere, funcționează de 2-3 ori mai rapid decât ProGuard și este integrat direct în Android Gradle Plugin. ProGuard folosește patru faze separate și necesită lansare externă. Începând cu AGP 7.0, ProGuard nu se mai utilizează — implicit funcționează R8.
Da, R8 folosește aceleași ProGuard rules (fișiere .pro). Directivele -keep, -keepclassmembers, -keepattributes, -assumenosideeffects funcționează identic. Regulile de bază vin în proguard-android-optimize.txt din Android SDK, iar cele specifice bibliotecilor (Retrofit, Room, Gson) se adaugă în proguard-rules.pro al proiectului. Fără aceste reguli, R8 poate elimina clasele necesare pentru funcționarea bibliotecilor prin reflection.
R8 este activat implicit în Android Gradle Plugin începând cu AGP 3.4. Pentru a activa minificarea, setați isMinifyEnabled = true în blocul release buildType al fișierului build.gradle.kts. Flagul suplimentar isShrinkResources = true activează eliminarea resurselor neutilizate. În gradle.properties se poate forța dezactivarea R8 prin android.enableR8=false, dar acest lucru nu este recomandat — R8 este mai rapid și mai stabil.
Ofuscarea — redenumirea claselor, metodelor și câmpurilor în nume scurte fără sens (a, b, c). Clasa com.example.app.auth.LoginManager se transformă în a.a.a, metoda authenticateUser — în a. Aceasta complică ingineria inversă a aplicației, dar nu afectează logica de execuție. ProGuard și R8 redenumesc doar elementele care nu sunt protejate de regulile -keep. Fișierul mapping păstrează corespondența numelor originale cu cele ofuscate pentru decodarea logurilor de crash.
Pentru decodarea stack trace se utilizează utilitarul retrace (face parte din ProGuard/R8 SDK). Comanda: retrace mapping.txt crash-stacktrace.txt. Fișierul mapping se află în build/outputs/mapping/release/mapping.txt. Google Play Console suportă, de asemenea, încărcarea mapping.txt la publicarea AAB — logurile de crash sunt decodate automat în consolă. Fără fișierul mapping, stack trace va conține doar nume ofuscate a.b.c(), ceea ce este inutil pentru depanare.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și