You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Current main / .NET 11 development builds. This is an implementation umbrella rather than a released-product regression.
Description
Enable R8 to safely remove, optimize, and obfuscate Java bytecode in trimmed .NET for Android applications, including MAUI applications.
Today, D8-only builds can package most of the transitive AndroidX, Material, Kotlin, and generated Java closure. Enabling R8 provides a large initial reduction, but the current conservative JNI naming policy prevents R8 from applying its full shrinking and obfuscation capabilities. Managed trimming and Java shrinking must also agree: when ILLink or NativeAOT removes managed C# code, the corresponding Java Callable Wrappers (JCWs), R8 roots, DEX classes, and runtime mapping entries must be removed as well.
The intended pipeline is:
C# compilation
-> ILLink or NativeAOT reachability
-> regenerate/prune typemap Java wrappers from surviving managed code
-> javac
-> one final R8 shrink/optimize/obfuscate pass
-> convert final mapping.txt into compact JNI remapping tables
-> native link and package
The runtime-remapping design keeps managed assemblies using their original JNI identifiers and translates lookups to R8's final identifiers at runtime. It requires:
Forward type remapping for managed-to-Java lookup.
Reverse type remapping for Java-object-to-managed-type activation through the trimmable typemap.
Method, constructor, field, and descriptor remapping.
Inherited member fallback.
Composition with existing remap inputs such as MAM/Intune mappings.
CoreCLR selection of required remaps from post-ILLink assemblies.
NativeAOT selection of required remaps from post-ILC JNI literals, followed by per-RID late native linking.
Correct clean and incremental behavior so stale .java, .class, DEX, ProGuard, or remapping outputs cannot survive.
A prototype based on #12795 and #12796 produced the following decoded DEX results using the repository's Xamarin.Forms-like CoreCLR Release size fixture:
Mode
Decoded DEX
Classes
No R8
9,534,068 bytes
7,352
R8 private-members
2,794,592 bytes
3,636
R8 runtime-remapping with trimmable typemap
2,047,188 bytes
2,365
This is a 78.5% DEX reduction and 67.8% class-count reduction relative to no R8, and a further 26.7% DEX reduction relative to the current private-members policy.
Add a supported AndroidR8ObfuscationMode=runtime-remapping mode.
Require AndroidLinkTool=r8, PublishTrimmed=true, AndroidTypeMapImplementation=trimmable, and CoreCLR or NativeAOT.
Root surviving JCWs while allowing ordinary JNI-visible types and members to be obfuscated where runtime remapping is available.
Preserve only bootstrap/runtime identifiers that are used outside the remapping machinery.
Preserve and compose AAPT rules, AAR consumer rules, user ProguardConfiguration items, user-authored Java sources, and explicit remap inputs.
Continue protecting generated native callback declarations and descriptor classes until RegisterNatives literals can be safely rewritten after R8.
The current native callback boundary intentionally relies on the standard rule:
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
Generated RegisterNatives UTF-8 names and signatures remain original. Native callback names are therefore pinned rather than runtime-remapped. A future optimization may rewrite these literals and reverse-remap the callback class, but that is not required for the initial feature.
Trimming and correctness requirements
A managed Java peer removed by ILLink must be absent from post-trim Java sources, acw-map.txt, generated R8 roots, final DEX, and runtime remapping tables.
A retained peer must survive R8 shrinking and work after genuine type/member obfuscation.
Java-to-managed activation must reverse-map the actual R8 class name before consulting the trimmable typemap.
Manifest components, XML custom views, runtime-owned JNI classes, interfaces, native callbacks, and user Java entry points must remain functional.
Dynamically constructed JNI identifiers must have a documented explicit-remap or keep-rule mechanism.
Existing MAM/Intune remappings must compose correctly with final R8 names.
CoreCLR and NativeAOT device tests must cover constructors, overloads, fields, inherited members, reverse activation, and changed R8 inputs.
Multi-RID NativeAOT builds must use one final R8 mapping while producing correct ABI-specific native remapping data.
Incremental-build requirements
Deleting a post-trim Java wrapper invalidates javac and removes the corresponding .class and DEX class.
Changed or missing R8/AAPT/ProGuard inputs rerun the appropriate targets.
Changed mapping.txt regenerates remapping tables.
NativeAOT remapping changes relink without unnecessarily rerunning ILC.
A failed R8 invocation must not allow NativeAOT to link against a stale mapping file.
Clean removes all generated remapping, configuration, mapping, and native object outputs.
Product validation and rollout
Measure current MAUI templates and at least one representative large MAUI dependency closure, not only the repository's Xamarin.Forms-like fixture.
Add DEX byte-size and class-count regression coverage for no R8, private-members, and runtime-remapping configurations.
Validate AndroidX, Material, Firebase, Google Play Services, MAM/Intune, custom XML views, reflection/service-loader Java, and native-library JNI entry points.
Document supported configurations, limitations, diagnostics, and escape hatches.
Initially ship as an explicit opt-in.
Consider making it the optimized trimmed default only after ecosystem compatibility is established.
The focused issues above can remain open for acceptance criteria that are broader than the initial runtime-remapping implementation, particularly NativeAOT pre-javac JCW pruning, Java-only retention policy, typemap DLL invalidation, and native callback registration redesign.
Steps to Reproduce
Build a Release MAUI or .NET for Android application without AndroidLinkTool=r8.
Inspect classes.dex and any secondary DEX files with APK Analyzer or dexdump.
Rebuild with R8 enabled and compare DEX bytes and class counts.
Apply a less conservative obfuscation policy without runtime JNI remapping and observe failures when managed bindings request original type, method, field, constructor, or descriptor names.
Trim an unused managed binding implementor and verify whether its Java wrapper, compiled class, R8 root, DEX class, and mapping entries are consistently removed.
Applications can currently opt into AndroidLinkTool=r8 and use the conservative private-members mode. This provides substantial Java shrinking but preserves many JNI-visible names and therefore leaves additional size reduction unavailable.
Applications may also provide custom ProGuard keep rules, but overly broad rules retain unnecessary Java code, while overly narrow rules can break JNI lookup, Java-to-managed activation, reflection, manifest/XML entry points, or RegisterNatives.
Android framework version
Affected platform version
Current
main/ .NET 11 development builds. This is an implementation umbrella rather than a released-product regression.Description
Enable R8 to safely remove, optimize, and obfuscate Java bytecode in trimmed .NET for Android applications, including MAUI applications.
Today, D8-only builds can package most of the transitive AndroidX, Material, Kotlin, and generated Java closure. Enabling R8 provides a large initial reduction, but the current conservative JNI naming policy prevents R8 from applying its full shrinking and obfuscation capabilities. Managed trimming and Java shrinking must also agree: when ILLink or NativeAOT removes managed C# code, the corresponding Java Callable Wrappers (JCWs), R8 roots, DEX classes, and runtime mapping entries must be removed as well.
The intended pipeline is:
The runtime-remapping design keeps managed assemblies using their original JNI identifiers and translates lookups to R8's final identifiers at runtime. It requires:
.java,.class, DEX, ProGuard, or remapping outputs cannot survive.A prototype based on #12795 and #12796 produced the following decoded DEX results using the repository's Xamarin.Forms-like CoreCLR Release size fixture:
private-membersruntime-remappingwith trimmable typemapThis is a 78.5% DEX reduction and 67.8% class-count reduction relative to no R8, and a further 26.7% DEX reduction relative to the current
private-memberspolicy.Existing implementation stack
R8 configuration requirements
AndroidR8ObfuscationMode=runtime-remappingmode.AndroidLinkTool=r8,PublishTrimmed=true,AndroidTypeMapImplementation=trimmable, and CoreCLR or NativeAOT.ProguardConfigurationitems, user-authored Java sources, and explicit remap inputs.RegisterNativesliterals can be safely rewritten after R8.The current native callback boundary intentionally relies on the standard rule:
Generated
RegisterNativesUTF-8 names and signatures remain original. Native callback names are therefore pinned rather than runtime-remapped. A future optimization may rewrite these literals and reverse-remap the callback class, but that is not required for the initial feature.Trimming and correctness requirements
acw-map.txt, generated R8 roots, final DEX, and runtime remapping tables.Incremental-build requirements
.classand DEX class.mapping.txtregenerates remapping tables.Product validation and rollout
private-members, andruntime-remappingconfigurations.Related issues
.javanot being trimmed #9943 — historical report of unused MAUI Java classes remaining in DEX.The focused issues above can remain open for acceptance criteria that are broader than the initial runtime-remapping implementation, particularly NativeAOT pre-javac JCW pruning, Java-only retention policy, typemap DLL invalidation, and native callback registration redesign.
Steps to Reproduce
AndroidLinkTool=r8.classes.dexand any secondary DEX files with APK Analyzer ordexdump.A prototype configuration is:
Did you find any workaround?
Applications can currently opt into
AndroidLinkTool=r8and use the conservativeprivate-membersmode. This provides substantial Java shrinking but preserves many JNI-visible names and therefore leaves additional size reduction unavailable.Applications may also provide custom ProGuard keep rules, but overly broad rules retain unnecessary Java code, while overly narrow rules can break JNI lookup, Java-to-managed activation, reflection, manifest/XML entry points, or
RegisterNatives.Relevant log output