469 lines
10 KiB
Markdown
469 lines
10 KiB
Markdown
# 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
|
|
|
|
```proguard
|
|
# 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
|
|
|
|
```proguard
|
|
# 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
|
|
|
|
```proguard
|
|
# 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
|
|
|
|
```proguard
|
|
# 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
|
|
|
|
```proguard
|
|
# 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)
|
|
|
|
```proguard
|
|
# 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)
|
|
|
|
```proguard
|
|
# Secp256k1 native library
|
|
-keep class fr.acinq.secp256k1.** { *; }
|
|
|
|
# Keep native methods
|
|
-keepclasseswithmembernames class * {
|
|
native <methods>;
|
|
}
|
|
```
|
|
|
|
### Keep Tor
|
|
|
|
```proguard
|
|
# Tor library
|
|
-keep class com.msopentech.thali.toronionproxy.** { *; }
|
|
-dontwarn com.msopentech.thali.toronionproxy.**
|
|
```
|
|
|
|
### Keep ExoPlayer (Media)
|
|
|
|
```proguard
|
|
# ExoPlayer (Media3)
|
|
-keep class androidx.media3.** { *; }
|
|
-dontwarn androidx.media3.**
|
|
|
|
-keep class com.google.android.exoplayer2.** { *; }
|
|
-dontwarn com.google.android.exoplayer2.**
|
|
```
|
|
|
|
### Keep Coil (Image Loading)
|
|
|
|
```proguard
|
|
# Coil image loading
|
|
-keep class coil.** { *; }
|
|
-keep class coil3.** { *; }
|
|
-dontwarn coil.**
|
|
-dontwarn coil3.**
|
|
```
|
|
|
|
### Keep ViewModels
|
|
|
|
```proguard
|
|
# 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
|
|
|
|
```proguard
|
|
# 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
|
|
|
|
```proguard
|
|
# Enums
|
|
-keepclassmembers enum * {
|
|
public static **[] values();
|
|
public static ** valueOf(java.lang.String);
|
|
}
|
|
```
|
|
|
|
### Remove Logging (Production)
|
|
|
|
```proguard
|
|
# 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
|
|
|
|
```proguard
|
|
# 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
|
|
|
|
```gradle
|
|
android {
|
|
buildTypes {
|
|
release {
|
|
minifyEnabled = true
|
|
shrinkResources = true
|
|
proguardFiles(
|
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
"proguard-rules.pro"
|
|
)
|
|
}
|
|
|
|
debug {
|
|
minifyEnabled = false
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Multiple Proguard Files
|
|
|
|
```gradle
|
|
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
|
|
|
|
```bash
|
|
# Using retrace (part of Android SDK)
|
|
retrace.sh mapping.txt stacktrace.txt
|
|
```
|
|
|
|
### Enable Proguard Output
|
|
|
|
```gradle
|
|
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
|
|
|
|
```bash
|
|
./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:**
|
|
```proguard
|
|
-keep class com.example.YourClass {
|
|
public <methods>;
|
|
}
|
|
```
|
|
|
|
### Issue: Serialization Fails
|
|
|
|
**Cause:** Data class fields were renamed.
|
|
|
|
**Solution:**
|
|
```proguard
|
|
-keep @kotlinx.serialization.Serializable class * { *; }
|
|
-keepclassmembers class * {
|
|
@kotlinx.serialization.SerialName <fields>;
|
|
}
|
|
```
|
|
|
|
### Issue: Compose Navigation Crashes
|
|
|
|
**Cause:** @Serializable route classes were obfuscated.
|
|
|
|
**Solution:**
|
|
```proguard
|
|
# 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:**
|
|
```proguard
|
|
-keepclasseswithmembernames class * {
|
|
native <methods>;
|
|
}
|
|
```
|
|
|
|
## Optimization Tips
|
|
|
|
### 1. Keep Only What's Necessary
|
|
|
|
Don't use broad wildcards:
|
|
```proguard
|
|
# 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
|
|
|
|
```gradle
|
|
android {
|
|
buildTypes {
|
|
release {
|
|
// Generate R8 configuration
|
|
android.debug.obsoleteApi = true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Analyze APK Size
|
|
|
|
```bash
|
|
# 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
|
|
# proguard-play.pro
|
|
-keep class com.google.firebase.** { *; }
|
|
-keep class com.google.android.gms.** { *; }
|
|
```
|
|
|
|
### F-Droid Flavor (No Google Services)
|
|
|
|
```proguard
|
|
# proguard-fdroid.pro
|
|
# UnifiedPush
|
|
-keep class org.unifiedpush.** { *; }
|
|
```
|
|
|
|
**Configure in build.gradle:**
|
|
```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
|
|
|
|
- [Android R8 Documentation](https://developer.android.com/build/shrink-code)
|
|
- [Proguard Manual](https://www.guardsquare.com/manual/configuration)
|
|
- [Kotlinx Serialization Proguard](https://github.com/Kotlin/kotlinx.serialization#android)
|