Skip to Content
DeploymentR8, minification & obfuscation

R8, minification & obfuscation

The release build enables the works:

// android/app/build.gradle.kts buildTypes { release { isMinifyEnabled = true isShrinkResources = true signingConfig = signingConfigs.getByName("debug") proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } }

R8 in full mode: tree-shaking, optimisation, identifier obfuscation, and resource shrinking. It took the APK from ~48 MB to ~37 MB.

Why it almost didn’t work

1. UnsatisfiedLinkError on WebRTC native methods

libwebrtc’s Java classes are bound to native code by exact name. R8 renaming org.webrtc.SoftwareVideoDecoderFactory.nativeCreateDecoder breaks the JNI lookup at runtime - UnsatisfiedLinkError the first time a software codec is needed.

Fix - keep the whole org.webrtc package, names and all:

-keep class org.webrtc.** { *; } -keepclassmembernames class org.webrtc.** { *; } -keepnames class org.webrtc.** { *; } -keepclasseswithmembers class * { native <methods>; } -keep,allowshrinking class org.webrtc.** { *; } -dontwarn org.webrtc.**

2. Native SIGTRAP in JNI_OnLoad - the hard one

The io.github.webrtc-sdk AAR bundles Chromium’s JNI runtime, org.jni_zero. libjingle_peerconnection_so.so’s JNI_OnLoad does:

FindClass("org/jni_zero/JniInit"); // and resolves @CalledByNative members by name

R8 has no reason to keep org.jni_zero - nothing in Kotlin references it - so it strips the package. Then PeerConnectionFactory.initialize() triggers JNI_OnLoad, FindClass fails, and the process dies with a native SIGTRAP before any Java exception can be thrown. The AAR ships no consumer ProGuard rules, so nothing warns you.

Fix - keep org.jni_zero whole and preserve every @CalledByNative member anywhere:

-keep class org.jni_zero.** { *; } -keepclassmembers class org.jni_zero.** { *; } -dontwarn org.jni_zero.** -keepclasseswithmembers class * { @org.jni_zero.CalledByNative <methods>; } -keepclasseswithmembers class * { @org.jni_zero.CalledByNativeUnchecked <methods>; } -keepclasseswithmembers class * { @org.jni_zero.AccessedByNative <fields>; }

3. Gson models

Signaling wire types are (de)serialised by field name. R8 renaming the fields breaks the JSON mapping.

-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod,SourceFile,LineNumberTable -keepclassmembers class * { @com.google.gson.annotations.SerializedName <fields>; } -keep class com.example.videocall.** { *; }

-keepattributes …SourceFile,LineNumberTable keeps release stack traces de-obfuscatable with the mapping.txt.

De-obfuscating a release crash

Every CI release attaches …-mapping.txt. To read an obfuscated stack trace:

# from the Android SDK $ANDROID_HOME/tools/proguard/bin/retrace.sh mapping.txt stacktrace.txt # or the newer standalone java -jar retrace.jar mapping.txt stacktrace.txt

Keep the mapping file for every build you distribute - it’s the only way back from a.b.c(Unknown Source).

The full proguard-rules.pro

Lives at android/app/proguard-rules.pro. Beyond the above it also has the standard OkHttp/Okio -dontwarns and keeps okhttp3.internal.publicsuffix.PublicSuffixDatabase by name.