R8 / JNI Keep-Rule Authoring
SkillDev toolsR8 and ProGuard keep rules for serializable routes, JNI bindings, protobufs, and minification.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the R8 / JNI Keep-Rule Authoring skill
What this skill tells your AI
The instructions your AI receives, as published by po4yka/ripdpi in .agents/skills/r8-jni-keep-rules/SKILL.md and read by ahel’s review.
Release builds minify the app via R8. Three failure modes are common:
- A native method loses its exact symbol name, and
System.loadLibrarycan't resolve the JNI signature → runtimeUnsatisfiedLinkError. - A
kotlinx.serialization@Serializableclass loses its generated$$serializeror companion, and Navigation Compose type-safe route encode/decode fails → runtimeSerializationExceptionon nav transition. - A proto-lite class is renamed, and DataStore can't rehydrate persisted settings → runtime crash on first launch of the minified build.
The project keeps keep-rules narrow on purpose. Read the hard rule first:
Do not paste
missing_rules.txtsuggestions verbatim. Add the smallest rule that names the exact compatibility boundary. No blanket-keep class **or-dontwarn **— the reviewer will push back. —app/proguard-rules.pro:8-10
Where keep-rules live
| File | Scope | Contains |
|---|---|---|
core/engine/consumer-rules.pro | JNI boundary for the native engine | RipDpiProxyNativeBindings, Tun2SocksNativeBindings, NetworkDiagnosticsNativeBindings — -keepclasseswithmembernames + native <methods>; only |
core/data/consumer-rules.pro | Proto-lite compatibility surface | com.poyka.ripdpi.proto.** keep |
app/proguard-rules.pro | App-level shims (intentionally tiny) | Only Guava j2objc -dontwarn today |
Rule: Engine/Data compatibility boundaries belong in module-level consumer rules, not in app/proguard-rules.pro. Reviewers reject app-level rules that could have lived in a consumer rule file.
Decision tree
Adding a new JNI binding class
When you add a class with external fun members called from Rust:
- Add the class to
core/engine/consumer-rules.profollowing the exact pattern of the three existing entries. - Keep the rule to
-keepclasseswithmembernames+native <methods>;only. Do not add-keep class(full class preservation) — only the native method names need to survive shrinking, the higher-level Kotlin wrapper does not. - Verify the native method signatures resolve post-shrink with the affected flavor-qualified release task, then launch that artifact and trigger the new binding.
Anchor example:
-keepclasseswithmembernames class com.poyka.ripdpi.core.RipDpiProxyNativeBindings {
native <methods>;
}
Adding a new @Serializable navigation route
Navigation Compose type-safe routes use kotlinx.serialization at runtime. Read the active Navigation Compose version from gradle/libs.versions.toml, and locate ConfigGraph / SettingsGraph by symbol in app/src/main/kotlin/com/poyka/ripdpi/ui/navigation/RipDpiNavHost.kt. R8 can strip a generated $$serializer companion.
Current state: the project has no app-level -keep rule for serializable routes today. It works because the routes are data objects (stateless) and the Compose/Kotlin serialization Gradle plugin emits a consumer rule. Verify this remains true when adding a @Serializable data class route with fields.
If a future release build crashes with kotlinx.serialization.SerializationException: Serializer for class 'X' is not found:
- Add the narrowest rule that names the exact class + its serializer companion:
-keepclassmembers class com.poyka.ripdpi.ui.navigation.<RouteName> { kotlinx.serialization.KSerializer serializer(...); <fields>; } - Do not add
-keep class com.poyka.ripdpi.ui.navigation.** { *; }— that defeats minification for the entire navigation graph.
Adding a new proto-lite schema used at runtime
The existing rule -keep class com.poyka.ripdpi.proto.** { *; } covers everything under that package. If a new schema lands under a different package (e.g. a core/diagnostics-data proto), add a module-scoped consumer rule in that module's consumer-rules.pro, not a line in app/proguard-rules.pro.
Handling missing_rules.txt after a failed release build
For assembleGithubFullRelease, R8 emits app/build/outputs/mapping/githubFullRelease/missing_rules.txt with suggestions. These are mechanical — they don't know what's a genuine compatibility boundary vs. a false positive.
Review protocol:
- Read each suggested rule symbolically — what class? what member? why would this survive minification?
- If the rule is for a class that legitimately needs to survive (JNI symbol, serialization entrypoint, reflection), author the narrowest equivalent and add it to the correct module's
consumer-rules.pro. - If the rule is for a class you control and that should have been obfuscation-safe, fix the caller (usually: remove reflection, remove
Class.forNamestrings) rather than suppress. - Never commit
missing_rules.txtas-is, and nevercat missing_rules.txt >> app/proguard-rules.pro.
Verification
After any change to a .pro file:
- The affected flavor-qualified
assemble*Releasetask must complete without R8 warnings escalating to errors. - Install the release APK on an emulator and exercise the code path the new rule protects (a nav transition for a route rule, an engine call for a JNI rule, a settings read for a proto rule).
./gradlew staticAnalysis— lint must not regress.- If
missing_rules.txtappears with new suggestions after your change, treat the appearance itself as a review signal: something in your diff created a new boundary that R8 can't analyze. Surface this in PR review, don't paper over it.
Out of scope
- Hilt, Compose, Room, Retrofit, Coroutines: rely on the generated
-consumerProguardFilesfrom each library. Don't re-declare their rules. - Manifest entry points (activities, services, broadcast receivers): keep their names automatically via
AndroidManifest.xml. - Guava: already handled by the single
-dontwarn com.google.j2objc.annotations.**inapp/proguard-rules.pro.
External reference
Run android skills r8 for Google's generic R8 skill; use it alongside this RIPDPI-specific skill for background on new R8 features, obfuscation dictionaries, or full-mode vs compat-mode semantics. This skill is the project-specific authority for what to keep and where; Google's skill explains why R8 behaves the way it does.
Signals
- GitHub stars
- 69
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
r8-jni-keep-rules- Source
- github.com/po4yka/ripdpi