¿Qué hace R8?

R8 es el compilador de Android que reemplazó a ProGuard desde AGP 3.4. Hace tres cosas sobre tu código en el build de release:

  • Shrinking — elimina clases, métodos y campos que no se usan. Reduce el tamaño del APK/AAB considerablemente.
  • Ofuscación — renombra clases, métodos y campos a nombres cortos sin significado (a, b, c...). Dificulta el reverse engineering.
  • Optimization — inlinea métodos cortos, elimina ramas muertas, simplifica código. Mejora el runtime performance.

ProGuard vs R8R8 hace todo lo que hacía ProGuard pero más rápido y con mejor integración con el toolchain de Android. Sigue usando el mismo formato de reglas (-keep, -dontwarn, etc.), por eso la documentación vieja de ProGuard sigue siendo válida.

Cómo habilitarlo

// build.gradle (app)
android {
    buildTypes {
        release {
            // Habilitar minificación (shrinking + ofuscación)
            minifyEnabled = true
            // Habilitar shrinking de recursos (imágenes, layouts, strings no usados)
            shrinkResources = true
            // Archivos de reglas
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        debug {
            // En debug NO se minifica — el build sería muy lento
            minifyEnabled = false
        }
    }
}

shrinkResources requiere minifyEnabledNo podés habilitar shrinkResources sin minifyEnabled. Android necesita el análisis del código para saber qué recursos realmente se referencian.

Shrinking — cuánto reduce

El shrinking puede reducir el APK dramáticamente. Una app "Hello World" que depende de Jetpack tiene cientos de miles de clases — la mayoría no se usa. R8 las elimina.

Para ver exactamente qué se eliminó, revisá los reportes que R8 genera en app/build/outputs/mapping/release/:

# Después de un release build:
app/build/outputs/mapping/release/
├── mapping.txt      # mapeo ofuscado → original (para desobfuscar crashes)
├── seeds.txt        # clases/miembros que NO se ofuscaron (por reglas keep)
├── usage.txt        # todo lo que fue ELIMINADO por shrinking
└── configuration.txt # todas las reglas aplicadas (merged)

Ofuscación — qué significa en la práctica

// Tu código antes de R8:
class ProductoRepositoryImpl @Inject constructor(
    private val dao: ProductoDao,
    private val api: ProductoApi
) : ProductoRepository {
    override suspend fun getProductos(): List {
        return dao.getAll().map { it.toDomain() }
    }
}

// Después de la ofuscación, para el atacante el .dex muestra algo como:
class a(private val b: c, private val d: e) : f {
    override suspend fun a(): List = b.a().map { it.a() }
}

Esto no es seguridad absoluta — un atacante con tiempo puede reconstruir la lógica. Pero eleva significativamente la barrera de entrada y protege la propiedad intelectual del código.

Reglas keep — qué NO tocar

R8 no puede saber qué se usa por reflection o por nombre en runtime. Para esos casos necesitás decirle explícitamente que no toque ciertas clases:

# proguard-rules.pro

# Mantener modelos de datos que se serializan/deserializan con Gson o Retrofit
# (R8 no sabe que los campos se acceden por nombre en el JSON)
-keep class ar.pensa.miapp.data.remote.dto.** { *; }
-keep class ar.pensa.miapp.data.local.entity.** { *; }

# Mantener el nombre de las clases para logging y debugging
-keepnames class ar.pensa.miapp.** { *; }

# Si usás Parcelable a mano (sin @Parcelize)
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

# Enums — R8 puede romperlos si los usás por nombre en SharedPreferences o Room
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# Annotations que se leen en runtime
-keepattributes *Annotation*
-keepattributes Signature
-keepattributes Exceptions

# Evitar warnings de librerías que no afectan el funcionamiento
-dontwarn okhttp3.**
-dontwarn javax.annotation.**

Con kotlinx.serialization no necesitás reglas extraA diferencia de Gson, kotlinx.serialization genera código en tiempo de compilación — no usa reflection en runtime. R8 puede ofuscar tus DTOs sin problema. Es una de las ventajas de preferirlo sobre Gson en proyectos nuevos.

Reglas de librerías — no las escribas vos

Las librerías bien mantenidas incluyen sus propias reglas de ProGuard en el AAR. Cuando agregás una dependencia, Gradle automáticamente las incorpora. Podés verlas todas en configuration.txt.

Si una librería no incluye sus reglas, buscalas en su documentación. Nunca uses -keep class ** { *; } — eso deshabilita toda la ofuscación.

# Ver todas las reglas aplicadas (merged) después de un build:
cat app/build/outputs/mapping/release/configuration.txt

Mapping files — desobfuscar crashes

Cuando un usuario reporta un crash de la versión release, el stack trace viene ofuscado. El mapping.txt permite traducirlo de vuelta al código original:

# Stack trace ofuscado que llega de producción:
# java.lang.NullPointerException
#   at a.b.c(Unknown Source:12)
#   at d.e.f(Unknown Source:3)

# Desobfuscar con retrace (incluido en Android SDK):
java -jar retrace.jar mapping.txt crash.txt

# Stack trace desobfuscado:
# java.lang.NullPointerException
#   at ar.pensa.app.ProductoViewModel.cargar(ProductoViewModel.kt:42)
#   at ar.pensa.app.ProductosFragment.onViewCreated(ProductosFragment.kt:28)

Guardá el mapping.txt de cada releaseSin el mapping.txt de la versión exacta que crasheó, es imposible desobfuscar el stack trace. Guardalo junto con el AAB firmado. Si subís a Play Store, Google guarda el mapping automáticamente y Crashlytics lo usa para desobfuscar en la consola.

Verificar el resultado sin subir a producción

# Build de release local:
./gradlew assembleRelease

# Inspeccionar el APK resultante con APK Analyzer (Android Studio)
# Build → Analyze APK → seleccionar el .apk

# O con apkanalyzer en línea de comandos:
apkanalyzer dex packages app-release.apk

# Ver el tamaño de cada clase/paquete después del shrinking
apkanalyzer dex packages --defined-only app-release.apk | head -50