Files
amethyst/.claude/skills/android-expert/references/proguard-rules.md
T
2025-12-30 15:18:48 +02:00

10 KiB

Proguard Rules for Amethyst

Proguard configuration for optimizing and obfuscating Android APK while preserving necessary code.

What is Proguard/R8?

R8 is Android's default code shrinker and obfuscator (replaced Proguard in AGP 3.4.0+). It:

  • Shrinks code by removing unused classes/methods
  • Obfuscates code by renaming classes/methods to short names
  • Optimizes code by inlining methods and removing dead code

Amethyst Proguard Configuration

File: amethyst/proguard-rules.pro

Keep Kotlin Metadata

# Kotlin metadata is required for reflection
-keep class kotlin.Metadata { *; }
-keep class kotlin.** { *; }
-dontwarn kotlin.**

# Kotlin serialization
-keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.AnnotationsKt
-dontnote kotlinx.serialization.SerializationKt

-keep,includedescriptorclasses class com.vitorpamplona.**$$serializer { *; }
-keepclassmembers class com.vitorpamplona.** {
    *** Companion;
}
-keepclasseswithmembers class com.vitorpamplona.** {
    kotlinx.serialization.KSerializer serializer(...);
}

Keep Nostr Event Classes

# Nostr events are serialized/deserialized
-keep class com.vitorpamplona.quartz.events.** { *; }
-keep class com.vitorpamplona.quartz.encoders.** { *; }

# Keep event builders
-keep class com.vitorpamplona.quartz.builders.** { *; }

# Keep tag classes
-keep class com.vitorpamplona.quartz.nip01Core.tags.** { *; }

Keep Data Classes

# Data classes used in ViewModels and serialization
-keep @kotlinx.serialization.Serializable class * { *; }

# Keep all data classes
-keep class com.vitorpamplona.amethyst.model.** { *; }
-keep class com.vitorpamplona.amethyst.service.model.** { *; }

Keep Compose Classes

# Jetpack Compose
-keep class androidx.compose.** { *; }
-dontwarn androidx.compose.**

# Compose runtime
-keep class androidx.compose.runtime.** { *; }

# Compose UI
-keep class androidx.compose.ui.** { *; }

# Material3
-keep class androidx.compose.material3.** { *; }

# Navigation Compose - Keep serializable routes
-keep class * implements java.io.Serializable { *; }
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

Keep OkHttp/Retrofit

# OkHttp
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class okhttp3.** { *; }
-keep class okio.** { *; }

# OkHttp WebSockets (for Nostr relays)
-keep class okhttp3.internal.ws.** { *; }

# Retrofit (if used)
-keepattributes Signature
-keepattributes Exceptions
-keep class retrofit2.** { *; }

Keep Jackson (JSON)

# Jackson JSON library
-keep class com.fasterxml.jackson.** { *; }
-keep class org.codehaus.** { *; }
-keepclassmembers class * {
     @com.fasterxml.jackson.annotation.* <methods>;
}

# Jackson polymorphic types
-keepattributes RuntimeVisibleAnnotations
-keep @com.fasterxml.jackson.annotation.JsonTypeInfo class *

Keep Secp256k1 (Crypto)

# Secp256k1 native library
-keep class fr.acinq.secp256k1.** { *; }

# Keep native methods
-keepclasseswithmembernames class * {
    native <methods>;
}

Keep Tor

# Tor library
-keep class com.msopentech.thali.toronionproxy.** { *; }
-dontwarn com.msopentech.thali.toronionproxy.**

Keep ExoPlayer (Media)

# ExoPlayer (Media3)
-keep class androidx.media3.** { *; }
-dontwarn androidx.media3.**

-keep class com.google.android.exoplayer2.** { *; }
-dontwarn com.google.android.exoplayer2.**

Keep Coil (Image Loading)

# Coil image loading
-keep class coil.** { *; }
-keep class coil3.** { *; }
-dontwarn coil.**
-dontwarn coil3.**

Keep ViewModels

# ViewModel classes
-keep class * extends androidx.lifecycle.ViewModel {
    <init>();
}

# ViewModel factories
-keep class * extends androidx.lifecycle.ViewModelProvider$Factory {
    <init>(...);
}

# Keep ViewModel constructors for reflection
-keepclassmembers class * extends androidx.lifecycle.ViewModel {
    <init>(...);
}

Keep Parcelable

# Parcelable
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

-keepclassmembers class * implements android.os.Parcelable {
  public <fields>;
  private <fields>;
}

Keep Enums

# Enums
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

Remove Logging (Production)

# Remove debug logging in release builds
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
    public static *** i(...);
}

# Keep error/warning logs
-assumenosideeffects class android.util.Log {
    public static *** e(...) return false;
    public static *** w(...) return false;
}

Keep Crashlytics/Firebase

# Firebase Crashlytics
-keepattributes SourceFile,LineNumberTable
-keep public class * extends java.lang.Exception

# Firebase
-keep class com.google.firebase.** { *; }
-dontwarn com.google.firebase.**

Build Configuration

Enable R8 in build.gradle

android {
    buildTypes {
        release {
            minifyEnabled = true
            shrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }

        debug {
            minifyEnabled = false
        }
    }
}

Multiple Proguard Files

android {
    buildTypes {
        release {
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro",
                "proguard-quartz.pro",  // Library-specific rules
                "proguard-compose.pro"  // Compose-specific rules
            )
        }
    }
}

Debugging Proguard Issues

Generate Mapping File

R8 generates mapping.txt in app/build/outputs/mapping/release/:

# Original class name -> Obfuscated name
com.vitorpamplona.amethyst.ui.MainActivity -> a.b.c:
    void onCreate(Bundle) -> a

Deobfuscate Stack Traces

# Using retrace (part of Android SDK)
retrace.sh mapping.txt stacktrace.txt

Enable Proguard Output

android {
    buildTypes {
        release {
            proguardFiles(...)

            // Generate reports
            postprocessing {
                proguardFiles = [...]
                obfuscate = true
                optimizeCode = true
                removeUnusedCode = true
            }
        }
    }
}

Output files:

  • build/outputs/mapping/release/configuration.txt - All Proguard rules applied
  • build/outputs/mapping/release/mapping.txt - Obfuscation mappings
  • build/outputs/mapping/release/seeds.txt - Classes kept by -keep rules
  • build/outputs/mapping/release/usage.txt - Code removed by R8

Test Release Build

./gradlew assembleRelease

# Install and test
adb install app/build/outputs/apk/release/app-release.apk

Common Issues

Issue: NoSuchMethodException at Runtime

Cause: Proguard removed or renamed a method used via reflection.

Solution:

-keep class com.example.YourClass {
    public <methods>;
}

Issue: Serialization Fails

Cause: Data class fields were renamed.

Solution:

-keep @kotlinx.serialization.Serializable class * { *; }
-keepclassmembers class * {
    @kotlinx.serialization.SerialName <fields>;
}

Issue: Compose Navigation Crashes

Cause: @Serializable route classes were obfuscated.

Solution:

# Keep all route classes
-keep @kotlinx.serialization.Serializable class com.vitorpamplona.amethyst.ui.navigation.routes.** { *; }

Issue: Native Library Crashes

Cause: Native method signatures were changed.

Solution:

-keepclasseswithmembernames class * {
    native <methods>;
}

Optimization Tips

1. Keep Only What's Necessary

Don't use broad wildcards:

# Bad - keeps everything
-keep class com.vitorpamplona.** { *; }

# Good - keeps only specific packages
-keep class com.vitorpamplona.quartz.events.** { *; }

2. Test Thoroughly

  • Test all app features after enabling Proguard
  • Test deep links and navigation
  • Test serialization/deserialization
  • Test external library integrations

3. Use AGP's Proguard Analysis

android {
    buildTypes {
        release {
            // Generate R8 configuration
            android.debug.obsoleteApi = true
        }
    }
}

4. Analyze APK Size

# Build release APK
./gradlew assembleRelease

# Analyze APK with Android Studio
# Build > Analyze APK > Select app-release.apk

See scripts/analyze-apk-size.sh for automated analysis.

Product Flavor Specific Rules

Play Flavor (Firebase)

# proguard-play.pro
-keep class com.google.firebase.** { *; }
-keep class com.google.android.gms.** { *; }

F-Droid Flavor (No Google Services)

# proguard-fdroid.pro
# UnifiedPush
-keep class org.unifiedpush.** { *; }

Configure in build.gradle:

android {
    flavorDimensions = ["channel"]
    productFlavors {
        create("play") {
            dimension = "channel"
            proguardFiles("proguard-play.pro")
        }
        create("fdroid") {
            dimension = "channel"
            proguardFiles("proguard-fdroid.pro")
        }
    }
}

File Locations

  • amethyst/proguard-rules.pro - Main Proguard rules
  • amethyst/build/outputs/mapping/release/ - Proguard output files
  • amethyst/build.gradle - Proguard configuration

Resources