ProGuard e R8 sono strumenti di offuscamento, minificazione e ottimizzazione per applicazioni Android. ProGuard, creato nel 2002, è stato a lungo lo standard de facto per la protezione del codice Java. R8 è il suo successore, sviluppato da Google e integrato in Android Gradle Plugin a partire da AGP 3.4. Entrambi gli strumenti riducono le dimensioni dell'APK, rimuovono il codice morto e complicano il reverse engineering. Secondo Android Developers, R8 esegue build 2–3 volte più velocemente di ProGuard con una qualità di offuscamento comparabile.
Punti Chiave
ProGuard è uno strumento open source (Apache 2.0) per offuscamento, minificazione, ottimizzazione e preverifica del bytecode Java. È stato sviluppato da Eric Lafarge nel 2002 come parte del progetto SourceForge. ProGuard prende in input classi Java compilate (.class) o archivi JAR e produce classi elaborate dello stesso formato, ma di dimensioni ridotte e con elementi rinominati.
Per molto tempo, ProGuard è stato l'unico standard per proteggere le applicazioni Android dal reverse engineering. Google ne ha raccomandato ufficialmente l'uso in Android SDK e ha fornito una configurazione predefinita nel file proguard-android-optimize.txt all'interno degli strumenti SDK. ProGuard funzionava come strumento separato, avviato dopo la compilazione del codice Java in bytecode e prima del confezionamento in DEX.
ProGuard è costituito da quattro fasi sequenziali: shrink (rimozione delle classi inutilizzate), optimize (ottimizzazione del bytecode — inlining, rimozione del codice morto), obfuscate (rinominazione di classi, metodi e campi in nomi brevi), preverify (verifica della compatibilità JVM). Ogni fase è controllata da regole separate dai file di configurazione.
Durante la fase di offuscamento, ProGuard genera un file di mapping (mapping.txt) che mappa i nomi originali a quelli offuscati. Questo file è fondamentale per decodificare i log di crash dalle build di rilascio tramite l'utilità retrace. Senza un file di mapping, una traccia dello stack diventa un insieme di lettere a(), b(), c() senza possibilità di ripristinare il contesto originale.
| Fase ProGuard | Scopo | Risultato |
|---|---|---|
| Shrink | Analisi del grafo delle chiamate e rimozione del codice morto | Meno classi nell'APK |
| Optimize | Inlining dei metodi, rimozione dei parametri inutilizzati | Esecuzione del codice più veloce |
| Obfuscate | Rinominazione di classi, campi e metodi | Protezione dal reverse engineering |
| Preverify | Aggiunta di attributi StackMap per JVM | Compatibilità Java 6+ |
R8 è uno strumento di offuscamento e minificazione di prossima generazione di Google, presentato per la prima volta in Android Studio 3.3 (novembre 2018) e diventato standard in AGP 3.4 (agosto 2019). A differenza di ProGuard, R8 fa parte del compilatore D8/R8 che converte il bytecode Java in formato DEX. R8 esegue tutte le fasi — offuscamento, minificazione e ottimizzazione — in un unico passaggio, senza passare file intermedi tra gli strumenti.
Google ha sviluppato R8 con due obiettivi: accelerare le build (ProGuard funzionava come strumento esterno) e garantire un'integrazione perfetta con lo stack Android moderno (Desugar, Core Library Desugaring, D8). R8 è scritto in Kotlin e Java e fa parte del repository R8/Desugar su AOSP (Android Open Source Project).
Un importante vantaggio di R8 è la piena compatibilità all'indietro con le regole ProGuard. I file .pro esistenti funzionano senza modifiche. R8 supporta persino direttive ProGuard specifiche, tra cui -whyareyoukeeping, -printconfiguration e -printmapping. Ciò significa che la transizione da ProGuard a R8 è trasparente: basta aggiornare AGP.
// build.gradle.kts — attivazione di R8 tramite minifyEnabled
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
// Configurazione di base da Android SDK
getDefaultProguardFile("proguard-android-optimize.txt"),
// Regole personalizzate del progetto
"proguard-rules.pro"
)
}
}
}Il codice mostra una configurazione standard di build di rilascio. Il flag isMinifyEnabled = true attiva R8 per offuscamento e ottimizzazione. isShrinkResources = true rimuove inoltre le risorse inutilizzate. getDefaultProguardFile carica le regole predefinite dall'SDK, mentre proguard-rules.pro contiene le impostazioni specifiche del progetto.
L'offuscamento è il processo di trasformazione del codice sorgente in una forma difficile da analizzare per gli esseri umani ma che mantiene la piena funzionalità. Nel contesto Android, l'offuscamento significa rinominare classi, metodi e campi in nomi brevi e privi di significato: com.example.app.auth.LoginManager diventa a.a.a, il metodo authenticateUser diventa a, il campo userToken diventa b.
I file APK Android sono archivi che possono essere aperti con qualsiasi archiviatore (ZIP, 7z, WinRAR). Senza offuscamento, un aggressore ottiene una mappa completa dell'applicazione: nomi dei pacchetti, classi, metodi e campi. Strumenti come jadx o Bytecode Viewer possono ripristinare codice Java quasi originale da file DEX in pochi secondi. L'offuscamento non rende il codice invulnerabile, ma aumenta significativamente la barriera d'ingresso: invece di nomi significativi, il lettore vede a(), b(), c().
Obiettivi tipici dell'offuscamento: proteggere la logica commerciale (algoritmi, formule di calcolo), ostacolare il furto di chiavi API e token, prevenire la sostituzione di classi tramite reflection e prevenire la modifica e il reimpacchettamento dell'APK (attacco di repackage). In pratica, il 70% dei compiti viene risolto solo con la rinomina — ecco perché si usano ProGuard/R8.
Di seguito è riportato un tipico file proguard-rules.pro per un progetto Android con Retrofit, Gson e Parcelable. Le regole -keep preservano le classi e i metodi necessari per il funzionamento delle librerie tramite reflection. Senza queste regole, R8 rimuoverà o rinominerà le classi a cui la libreria accede per nome stringa.
# =====================
# Retrofit — preservazione delle interfacce
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions
# =====================
# Gson — serializzazione 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 — rimozione dei log dalla 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(...);
}
# =====================
# Classi dati Kotlin — preservazione dei costruttori
# =====================
-keepclassmembers class * {
@kotlin.Metadata <fields>;
}
# =====================
# Activity — punto di ingresso
# =====================
-keep class * extends android.app.Activity {
@android.annotation.SuppressLint <methods>;
}Ogni direttiva in un file .pro risolve un compito specifico. -keep impedisce che l'intera classe venga rimossa o rinominata. -keepclassmembers protegge solo i membri della classe (campi e metodi) ma consente di rimuovere la classe stessa se non utilizzata. -assumenosideeffects dice a R8 che una chiamata a metodo non ha effetti collaterali e può essere rimossa in sicurezza. La direttiva -keepattributes preserva i metadati nel bytecode — annotazioni, firme, eccezioni.
La regola -keep,allowobfuscation,allowshrinking per Retrofit consente a R8 di rinominare le interfacce ma non di rimuoverle. Ciò è necessario perché Retrofit accede alle interfacce tramite proxy dinamici (java.lang.reflect.Proxy) e la rimozione causerebbe ClassNotFoundException in fase di esecuzione. Allo stesso modo, Gson utilizza la reflection per accedere ai campi annotati con @SerializedName — senza -keepclassmembers i campi verranno rimossi come inutilizzati.
La minificazione (shrinking) è il processo di rimozione del codice e delle risorse inutilizzati dalla build finale. ProGuard e R8 analizzano il grafo delle chiamate a partire dai punti di ingresso (Activity, Service, BroadcastReceiver) e rimuovono classi e metodi che non possono essere raggiunti attraverso la catena di chiamate. ShrinkResources è una fase aggiuntiva che rimuove le risorse inutilizzate da res/ (layout, drawable, string, color).
La minificazione offre il massimo beneficio nei progetti grandi con librerie. Uno scenario tipico: un progetto utilizza il 10% del codice di una libreria collegata (ad esempio Google Play Services). Senza minificazione, tutto il codice della libreria finisce nell'APK. Con la minificazione, R8 rimuove il 70–90% del codice della libreria, lasciando solo le classi e i metodi effettivamente utilizzati. Ciò influisce direttamente sulle dimensioni dell'APK, sui tempi di caricamento e sul consumo di memoria.
Il meccanismo ShrinkResources funziona in tandem con la minificazione del codice. Dopo che R8 ha determinato quali classi vengono utilizzate, il shrinking delle risorse analizza i riferimenti alle risorse dal codice: R.layout.main, R.drawable.icon, getString(R.string.title). Tutte le risorse senza un riferimento diretto o indiretto vengono rimosse dall'APK o AAB finale. Ciò viene fatto utilizzando il file delle risorse resources.arsc e le cartelle res/.
Una sfumatura importante: le risorse possono essere accedute tramite getIdentifier() o Resources.getResourceName() per nome stringa, bypassando la classe R. In tali casi, R8 non vede un collegamento diretto e può rimuovere una risorsa che è effettivamente utilizzata. Per proteggere tali risorse, esiste la direttiva -keep class **.R$* { *; } — preserva tutti gli identificatori della classe R.
<!-- Esempio: risorsa utilizzata solo tramite getIdentifier() -->
<string name="dynamic_title_welcome">Benvenuto</string>
<string name="dynamic_title_share">Condividi</string>
<!-- Codice Kotlin che accede per stringa -->
<!-- val title = getString(resources.getIdentifier( -->
<!-- \"dynamic_title_${type}\", \"string\", packageName)) -->In questo caso, R8 non vede un riferimento statico a dynamic_title_welcome nella classe R perché l'accesso avviene tramite getIdentifier con un nome dinamico. Per preservare tali risorse, aggiungi la direttiva -keepclassmembers class **.R$string { *; } a proguard-rules.pro — impedisce la rimozione di qualsiasi campo da tutte le classi R$string.
| Direttiva | Scopo | Esempio |
|---|---|---|
| -keep | Preserva la classe e tutti i suoi membri | -keep class com.example.api.** { *; } |
| -keepclassmembers | Preserva solo i membri della classe | -keepclassmembers class * { @SerializedName <fields>; } |
| -keepattributes | Preserva i metadati del bytecode | -keepattributes *Annotation*, Signature |
| -assumenosideeffects | Rimuove le chiamate senza effetti collaterali | -assumenosideeffects class Log { d(...); } |
| -dontwarn | Sopprime gli avvisi | -dontwarn com.example.legacy.** |
Nonostante R8 sia il successore di ProGuard, esistono differenze fondamentali tra gli strumenti in termini di architettura, prestazioni e comportamento. Google ha ufficialmente interrotto il supporto di ProGuard in Android Gradle Plugin a partire da AGP 7.0, ma ProGuard continua a essere utilizzato in progetti che richiedono un comportamento di ottimizzazione specifico non disponibile in R8.
| Caratteristica | ProGuard | R8 |
|---|---|---|
| Sviluppatore | GuardSquare (Eric Lafarge) | |
| Anno di rilascio | 2002 | 2018 (stabile nel 2019) |
| Architettura | 4 fasi separate (shrink → optimize → obfuscate → preverify) | Singolo passaggio: shrink + optimize + obfuscate simultaneamente |
| Integrazione AGP | Strumento esterno, eseguito dopo javac | Integrato nel compilatore D8 DEX |
| Velocità di build | 2–3 volte più lento | Più veloce grazie al passaggio unico e all'integrazione nativa |
| Supporto Kotlin | Limitato (problemi con inline, lambda, coroutine) | Completo: coroutine, funzioni inline, data class |
| File di mapping | mapping.txt (compatibile con retrace) | mapping.txt (stesso formato) |
| Personalizzazione ottimizzazione | 60+ opzioni -optimizationpasses, -optimizations | Limitata: la maggior parte delle ottimizzazioni abilitate per impostazione predefinita |
| Stato del supporto | Sostituito da R8 (AGP 7.0+ non lo usa) | Sviluppo attivo, parte di AOSP |
R8 è più aggressivo di ProGuard nel rimuovere il codice che considera morto. Ciò porta a situazioni in cui la build di debug funziona ma la build di rilascio si blocca con ClassNotFoundException o NoSuchMethodException. Casi tipici: librerie che utilizzano la reflection per nome di classe (Gson, Moshi, Retrofit, Room, Dagger); chiamate ServiceLoader o java.util.ServiceLoader; proxy dinamici (java.lang.reflect.Proxy); metodi nativi (JNI). La soluzione è aggiungere -keep per tutte le classi chiamate tramite reflection.
# Problemi tipici di reflection — R8 non vede il collegamento statico
# Room — preservazione di DAO e migrazioni
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }
# Dagger / Hilt — preservazione dei componenti
-keep class * extends dagger.hilt.android.components.** { *; }
# JNI — non rinominare i metodi nativi
-keepclasseswithmembernames class * {
native <methods>;
}
# Data Binding — preservazione delle classi Binding
-keep class *.databinding.** { *; }Se dopo aver aggiunto le regole la build si blocca ancora, utilizza il flag -printconfiguration full-config.txt in proguard-rules.pro. R8 genererà un file di configurazione completo che mostra quali regole vengono applicate e quali classi vengono preservate. Utile anche la direttiva -whyareyoukeeping class com.example.MyClass — mostra il motivo per cui R8 ha deciso di preservare la classe in questione.
La corretta configurazione delle regole ProGuard è la chiave per un offuscamento stabile senza bug in fase di esecuzione. Di seguito è riportato un processo di configurazione passo passo per un nuovo progetto o per un progetto in cui l'offuscamento causa errori.
Inizia includendo il file standard di Android SDK — proguard-android-optimize.txt. Contiene regole per i componenti Android di base: Activity, Service, BroadcastReceiver, ContentProvider, View, Fragment. Questo file si trova nella cartella SDK: $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt. Se usi AGP, getDefaultProguardFile lo caricherà automaticamente.
Ogni libreria popolare ha regole ProGuard raccomandate. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — tutte richiedono regole -keep specifiche. Di solito le regole sono incluse nella libreria AAR e vengono collegate automaticamente tramite le regole consumer. Verifica che la libreria fornisca un file proguard.txt all'interno dell'AAR — questo indica che le regole sono già state considerate.
Prima della pubblicazione, assicurati di testare la build di rilascio su un dispositivo reale o emulatore. I problemi di offuscamento si manifestano solo in fase di esecuzione. Verifica: autenticazione (login/registrazione), caricamento dati dalla rete, navigazione tra schermate, fotocamera e galleria, notifiche push, Deeplink, WebView. Ogni crash nella build di rilascio deve essere decodificato con retrace utilizzando il file di mapping e vanno aggiunte le regole -keep mancanti.
Il file di mapping viene generato in build/outputs/mapping/release/mapping.txt. Questo file deve essere conservato: senza di esso è impossibile decodificare i log di crash da Google Play Console. Includi mapping.txt nel tuo sistema di controllo versione o caricalo come artefatto CI. Google Play Console accetta il file di mapping automaticamente quando carichi un AAB con uploading mapping.txt abilitato.
Di seguito è riportato un flusso di lavoro completo di configurazione dell'offuscamento nel file proguard-rules.pro con commenti per ogni gruppo di regole.
# ===========================================
# proguard-rules.pro — esempio completo
# ===========================================
# --- Impostazioni generali ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify
# --- Componenti 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 {}
# --- Serializzazione ---
-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();
}
# --- Solo R8: preservazione forzata ---
# (ProGuard ignora questa direttiva)
-keep,allowobfuscation class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}Dopo la configurazione, esegui la build: ./gradlew assembleRelease. Verifica che i file siano apparsi in build/outputs/mapping/release/: mapping.txt (mappatura dei nomi originali a quelli offuscati), seeds.txt (classi preservate dalle regole -keep), usage.txt (classi rimosse durante la minificazione). La dimensione dell'APK dopo l'offuscamento dovrebbe diminuire del 20–50% a seconda del numero di librerie collegate.
Domande Frequenti
R8 è il successore di ProGuard, sviluppato da Google. R8 esegue offuscamento, minificazione e ottimizzazione in un unico passaggio, funziona 2–3 volte più velocemente di ProGuard ed è integrato direttamente in Android Gradle Plugin. ProGuard utilizza quattro fasi separate e richiede esecuzione esterna. A partire da AGP 7.0, ProGuard non viene utilizzato — R8 funziona per impostazione predefinita.
Sì, R8 utilizza le stesse regole ProGuard (file .pro). Le direttive -keep, -keepclassmembers, -keepattributes, -assumenosideeffects funzionano in modo identico. Le regole di base si trovano in proguard-android-optimize.txt dell'Android SDK, mentre le regole specifiche delle librerie (Retrofit, Room, Gson) vengono aggiunte in proguard-rules.pro del progetto. Senza queste regole, R8 potrebbe rimuovere le classi necessarie per le librerie che funzionano tramite reflection.
R8 è abilitato per impostazione predefinita in Android Gradle Plugin a partire da AGP 3.4. Per attivare la minificazione, imposta isMinifyEnabled = true nel blocco release buildType del file build.gradle.kts. Il flag aggiuntivo isShrinkResources = true abilita la rimozione delle risorse inutilizzate. In gradle.properties, puoi disabilitare forzatamente R8 tramite android.enableR8=false, ma non è raccomandato — R8 è più veloce e stabile.
Offuscamento — rinomina di classi, metodi e campi in nomi brevi privi di significato (a, b, c). La classe com.example.app.auth.LoginManager diventa a.a.a, il metodo authenticateUser diventa a. Ciò complica il reverse engineering dell'applicazione ma non influisce sulla logica di esecuzione. ProGuard e R8 rinomina solo gli elementi non protetti dalle regole -keep. Il file di mapping preserva la corrispondenza dei nomi originali e offuscati per decodificare i log di crash.
Per decodificare una traccia dello stack, utilizza l'utilità retrace (parte del SDK ProGuard/R8). Comando: retrace mapping.txt crash-stacktrace.txt. Il file di mapping si trova in build/outputs/mapping/release/mapping.txt. Google Play Console supporta anche il caricamento di mapping.txt durante la pubblicazione di un AAB — i log di crash vengono decodificati automaticamente nella console. Senza un file di mapping, la traccia dello stack conterrà solo nomi offuscati come a.b.c(), inutile per il debug.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche