10 KiB
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 appliedbuild/outputs/mapping/release/mapping.txt- Obfuscation mappingsbuild/outputs/mapping/release/seeds.txt- Classes kept by-keeprulesbuild/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 rulesamethyst/build/outputs/mapping/release/- Proguard output filesamethyst/build.gradle- Proguard configuration