Part of #10789
Overview
Implement the Java peer type scanner using System.Reflection.Metadata (SRM) — no Mono.Cecil. This is the foundation that all code generation builds on.
New project: src/Microsoft.Android.Sdk.TrimmableTypeMap/ — a dedicated assembly separate from Xamarin.Android.Build.Tasks for fast inner dev loop.
What this includes
Scanner (JavaPeerScanner)
- Open assemblies via
PEReader + MetadataReader
- Find types with
[Register] attribute → extract JNI name and DoNotGenerateAcw flag
- Detect activation constructors: XI style (
IntPtr, JniHandleOwnership) first, then JI style (ref JniObjectReference, JniObjectReferenceOptions) — walk up hierarchy if not on type itself
- Collect marshal methods: methods with
[Register(name, sig, connector)] or [Export] attributes
- Detect component attributes (
[Activity], [Service], [BroadcastReceiver], [ContentProvider], [Application], [Instrumentation]) for unconditional marking
- Detect interface implementors and invoker types (including generic interface instantiations)
- Handle generic types: open generic definition gets the TypeMap entry
- Handle
[Application(BackupAgent=typeof(...))] and [Application(ManageSpaceActivity=typeof(...))] cross-references → force unconditional
- Support
IJniNameProviderAttribute for custom JNI name resolution
- Compute
CompatJniName (lowercased namespace) for acw-map.txt backward compatibility
Two-phase in-memory architecture (critical for performance)
Both PoCs suffered from O(n²) behavior scanning Mono.Android (~8000 types). The scanner uses:
Phase 1 — Index building (one pass per assembly):
TypesByFullName / TypesByJniName dictionaries for O(1) lookups
RegisterInfoByType / AttributesByType pre-indexed per type
IJniNameProviderAttribute detection and reclassification across assemblies
- All state stays in memory — MSBuild
Inputs/Outputs handles target-level incrementality
Phase 2 — Type analysis (uses cached indices only):
ActivationCtorCache — hierarchy walk results shared across types with same base
extendsJavaPeerCache — caches ExtendsJavaPeer results and doubles as cycle detection
- Cross-assembly type resolution via
TryResolveType / ResolveRegisterJniName helpers
Performance target: <1s for Mono.Android (~8000 types).
CRC64 polynomial
The scanner uses System.IO.Hashing.Crc64 (ECMA polynomial) instead of the legacy Crc64Helper (Jones polynomial). This is intentional and safe. The CRC64 hash only needs to be self-consistent within a single compilation — the scanner computes the JNI name, the JCW generator uses it, the typemap records it, and acw-map.txt maps it. All consumers read from the same scan pass. See implementation update comment for details.
Data model
JavaPeerInfo {
JavaName, CompatJniName,
ManagedTypeName, ManagedTypeNamespace, ManagedTypeShortName,
AssemblyName, BaseJavaName, ImplementedInterfaceJavaNames,
IsInterface, IsAbstract, DoNotGenerateAcw,
IsUnconditional, IsGenericDefinition,
MarshalMethods[], JavaConstructors[],
ActivationCtor, InvokerTypeName?
}
MarshalMethodInfo {
JniName, JniSignature, Connector,
ManagedMethodName, DeclaringTypeName, DeclaringAssemblyName,
NativeCallbackName, Parameters[], JniReturnType,
IsConstructor, ThrownNames, SuperArgumentsString
}
Test projects
Unit tests (tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/):
- Hand-crafted test assembly (~20 types) for TDD inner loop
- 75 tests covering scanner logic, JNI parsing, edge cases
Integration tests (tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/):
- Legacy comparison tests: Run both legacy Cecil-based scanner and new SRM scanner on same assemblies, compare results
- Real Mono.Android.dll (~8000 types) side-by-side comparison
UserTypesFixture.dll — user-type edge cases ([Activity], [Service], [Export], nested types, generic types, plain Java.Lang.Object subclasses)
- 10 tests (type maps + marshal methods for both assemblies)
Type classification rules (spec §15.9)
| Type |
Unconditional? |
Gets JCW? |
Gets RegisterNatives? |
User class with [Activity]/[Service]/etc |
✅ |
✅ |
✅ |
| Custom view from layout XML |
✅ |
✅ |
✅ |
| User class without component attr |
Trimmable |
✅ |
✅ |
MCW binding (DoNotGenerateAcw=true) |
Trimmable |
❌ |
❌ |
| Interface |
Trimmable |
❌ |
❌ |
| Invoker |
Excluded |
❌ |
❌ |
Part of #10789
Overview
Implement the Java peer type scanner using System.Reflection.Metadata (SRM) — no Mono.Cecil. This is the foundation that all code generation builds on.
New project:
src/Microsoft.Android.Sdk.TrimmableTypeMap/— a dedicated assembly separate fromXamarin.Android.Build.Tasksfor fast inner dev loop.What this includes
Scanner (
JavaPeerScanner)PEReader+MetadataReader[Register]attribute → extract JNI name andDoNotGenerateAcwflagIntPtr, JniHandleOwnership) first, then JI style (ref JniObjectReference, JniObjectReferenceOptions) — walk up hierarchy if not on type itself[Register(name, sig, connector)]or[Export]attributes[Activity],[Service],[BroadcastReceiver],[ContentProvider],[Application],[Instrumentation]) for unconditional marking[Application(BackupAgent=typeof(...))]and[Application(ManageSpaceActivity=typeof(...))]cross-references → force unconditionalIJniNameProviderAttributefor custom JNI name resolutionCompatJniName(lowercased namespace) for acw-map.txt backward compatibilityTwo-phase in-memory architecture (critical for performance)
Both PoCs suffered from O(n²) behavior scanning Mono.Android (~8000 types). The scanner uses:
Phase 1 — Index building (one pass per assembly):
TypesByFullName/TypesByJniNamedictionaries for O(1) lookupsRegisterInfoByType/AttributesByTypepre-indexed per typeIJniNameProviderAttributedetection and reclassification across assembliesInputs/Outputshandles target-level incrementalityPhase 2 — Type analysis (uses cached indices only):
ActivationCtorCache— hierarchy walk results shared across types with same baseextendsJavaPeerCache— cachesExtendsJavaPeerresults and doubles as cycle detectionTryResolveType/ResolveRegisterJniNamehelpersPerformance target: <1s for Mono.Android (~8000 types).
CRC64 polynomial
The scanner uses
System.IO.Hashing.Crc64(ECMA polynomial) instead of the legacyCrc64Helper(Jones polynomial). This is intentional and safe. The CRC64 hash only needs to be self-consistent within a single compilation — the scanner computes the JNI name, the JCW generator uses it, the typemap records it, andacw-map.txtmaps it. All consumers read from the same scan pass. See implementation update comment for details.Data model
Test projects
Unit tests (
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/):Integration tests (
tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/):UserTypesFixture.dll— user-type edge cases ([Activity], [Service], [Export], nested types, generic types, plain Java.Lang.Object subclasses)Type classification rules (spec §15.9)
[Activity]/[Service]/etcDoNotGenerateAcw=true)