Uh oh!
There was an error while loading. Please reload this page.
Split reflection-based JniTypeManager and JniValueManager behavior - #1441
Conversation
5ff4c17 to
6d72140Compare6d72140 to
58f0dbdComparesimonrozsival
commented
Jun 4, 2026
/azp run |
|
No pipelines are associated with this pull request. |
There was a problem hiding this comment.
Pull request overview
This draft PR refactors JniRuntime.JniTypeManager into an abstract base and moves the default reflection-based behavior into a new JniRuntime.DynamicJniTypeManager, with corresponding updates across tests, JRE runtime helpers, samples, and public API baselines.
Changes:
- Introduces
JniRuntime.DynamicJniTypeManagerand makesJniRuntime.JniTypeManagerabstract, shifting default/reflection-backed logic into the new derived type. - Updates test and JRE type managers to inherit from
DynamicJniTypeManager, and adjusts tests for new behaviors (e.g., generic invoker handling,GetTypearray behavior). - Updates trimmer/AOT annotations and PublicAPI baselines to reflect the split and new abstract members.
Show a summary per file
| File | Description |
|---|---|
| tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs | Updates expectations around GetType behavior (notably array signatures and generic holder mapping). |
| tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs | Switches proxy manager to DynamicJniTypeManager and adds trim suppression for test-only reflection use. |
| tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniTypeManagerTests.cs | Adds a generic invoker negative test and updates test manager base type. |
| tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs | Updates fixture type manager to DynamicJniTypeManager and refines type lookup with trimming annotations. |
| src/Java.Runtime.Environment/Java.Interop/JreTypeManager.cs | Moves to DynamicJniTypeManager and adds a DAM-annotated GetTypeForSimpleReference override. |
| src/Java.Interop/PublicAPI.Unshipped.txt | Records newly introduced/reshaped APIs (new type, newly-abstract members, overrides). |
| src/Java.Interop/PublicAPI.Shipped.txt | Updates shipped API surface to reflect newly-abstract members and removed methods from JniTypeManager. |
| src/Java.Interop/Java.Interop/JniValueMarshaler.cs | Simplifies expression-based marshaler instantiation and adjusts trimming annotations. |
| src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs | Switches peer-type selection to GetReflectionConstructibleTypes() and refactors uninitialized peer creation. |
| src/Java.Interop/Java.Interop/JniRuntime.JniTypeManager.cs | Makes JniTypeManager abstract and factors previously-inlined logic into abstract core methods. |
| src/Java.Interop/Java.Interop/JniRuntime.DynamicJniTypeManager.cs | Adds the new reflection-backed type manager implementation and related registration behavior. |
| src/Java.Interop/Java.Interop/JniRuntime.cs | Removes the default new JniTypeManager() fallback; requires an explicit type manager in creation options. |
| src/Java.Interop/Java.Interop/JavaPrimitiveArrays.tt | Retargets primitive array helper generation to DynamicJniTypeManager. |
| src/Java.Interop/Java.Interop/JavaPrimitiveArrays.cs | Updates the generated primitive array helper container type to DynamicJniTypeManager. |
| src/Java.Interop/GlobalSuppressions.cs | Adds CA1034 suppression for the new nested DynamicJniTypeManager type. |
| samples/Hello-NativeAOTFromJNI/NativeAotTypeManager.cs | Updates sample type manager implementation to new hierarchy and adds explicit native member registration. |
| samples/Hello-NativeAOTFromJNI/ManagedType.cs | Changes the native registration hook to internal for sample-driven registration. |
| samples/Hello-NativeAOTFromAndroid/NativeAotTypeManager.cs | Updates sample type manager implementation to new hierarchy and implements abstract members. |
Copilot's findings
- Files reviewed: 18/18 changed files
- Comments generated: 3
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Map Java.Interop's internal proxy peer types in the base type manager so custom AOT-safe managers do not need reflection-based DynamicJniTypeManager behavior for JavaProxyThrowable/JavaProxyObject. Register built-in proxy native members by JNI type name before asking custom managers for a managed Type, avoiding DAM-annotated lookups of internal Exception/Delegate-derived types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Let the base JniTypeManager resolve primitive JNI keyword signatures without requiring custom AOT-safe managers to duplicate the dynamic type manager's primitive mappings. Use that base lookup as a fallback when matching Java-to-managed constructor signatures so signatures such as (I)V resolve to System.Int32. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename DynamicJniTypeManager to ReflectionJniTypeManager so the type name describes the reflection-based behavior that is incompatible with Native AOT. Update the matching source file name, API baseline, JRE type manager inheritance, generated primitive-array partial, and tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolved the merge conflicts in commit The two new commits in
Also removed the remaining |
simonrozsival
commented
Jun 9, 2026
/review |
✅ Java.Interop PR Reviewer completed successfully! |
There was a problem hiding this comment.
✅ LGTM — Clean architectural split
Summary: This PR cleanly separates the reflection-based JniTypeManager / JniValueManager behavior into dedicated ReflectionJniTypeManager and ReflectionJniValueManager subclasses, making the base classes abstract and properly annotating the reflection-based paths with [RequiresDynamicCode] / [RequiresUnreferencedCode]. The NativeAOT samples are updated to implement the abstract base directly, and tests/downstream types are updated accordingly.
What looks good:
- The split is well-structured — base classes contain only the contract and built-in type handling; all reflection goes into the
Reflection*subclasses. [RequiresDynamicCode]and[RequiresUnreferencedCode]annotations are applied consistently throughout the chain (samples, tests, JRE runtime types).- Public API baselines are updated correctly.
TryRegisterBuiltInNativeMembersis a nice extraction that NativeAOT consumers can call without needing the full reflection machinery.- The
JreRuntimeOptions.CreateJreVM(JniTypeManager)overload is a good ergonomic addition.
Issues by severity:
| Severity | Count |
|---|---|
| 1 | |
| 💡 Suggestion | 3 |
Notes:
- CI only shows
license/cla— no build/test results visible yet. The PR is not mergeable until CI validates the full build. - The large abstract surface area for NativeAOT consumers (~12 methods) may be worth revisiting — see inline suggestion about making unused extension points virtual with default null returns.
Generated by Java.Interop PR Reviewer for issue #1441 · ● 14.3M
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Map JNI primitive and boxed primitive signatures in the NativeAOT sample type managers so constructor signature matching can resolve primitive parameters such as I to System.Int32. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
To minimize migration pain for callers moving from .NET 10 to .NET 11, revert JniTypeManager from `abstract` back to a regular class. Methods that were turned into `abstract` overrides now have `virtual` defaults so existing subclasses keep compiling: * Type lookup methods return null/empty defaults. * RegisterNativeMembers is a no-op by default; reflection-based behavior remains in ReflectionJniTypeManager (override to opt-in). Restore PublicAPI.Shipped.txt to its main-branch state (per the `Microsoft.CodeAnalysis.PublicApiAnalyzers` convention, never edit shipped entries in place) and track every breaking change in PublicAPI.Unshipped.txt: * 5 new JniTypeManager.* APIs are now `virtual` (not `abstract`). * `*REMOVED*` entries for the two `JniTypeManager.TryRegisterNativeMembers` overloads that moved to `ReflectionJniTypeManager`. * `*REMOVED*virtual` + `abstract` pairs for the two virtual->abstract transitions on `JniValueManager` (CreatePeer, GetValueMarshalerCore(Type)). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.
Follow-up to #1441 We need this method virtual so that `JavaMarshalValueManager` can override it and try to invoke the `XAConstructorSignature`. The method was previously virtual but as an oversight I removed it in #1441: - https://github.com/dotnet/java-interop/pull/1441/changes#diff-8ead12d157bc7712affd3d920bb93cddbff3885147fe6297957a44cc522d5430R7 - https://github.com/dotnet/java-interop/pull/1441/changes#diff-b92884de5db7f82df63b483b8319360cd9bfdac3cd97222eb9aff7292a01a07bL449-L467 I noticed we're not disposing the `reference` correctly which might cause a ref leak. In the Mono `AndroidValueManager` we never called into the base method so this would not be observed on mono previously (https://github.com/dotnet/android/blob/main/src/Mono.Android/Java.Interop/TypeManager.cs#L416-L423). Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
#1441 added `JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers ()` -- a reflection-free direct call to `JavaProxyObject.RegisterNativeMembers` -- and bumped that method to `internal` so the call would compile. That method is dead code: nothing in Java.Interop (or dotnet/android) ever calls it, and the base `JniTypeManager.RegisterNativeMembers` is a no-op. `JavaProxyObject`'s native members are -- and were before #1441 -- registered purely via reflection in `ReflectionJniTypeManager` (`FindAndCallRegisterMethod` discovering the `[JniAddNativeMethodRegistrationAttribute]`-annotated method), which finds private methods just fine via `GetRuntimeMethods ()`. Remove the unused `TryRegisterBuiltInNativeMembers` and restore `RegisterNativeMembers` to `private` (its state before #1441). Making it private again also strips the attribute-bearing method from the reference assembly, which fixes dotnet/android's trimmable typemap scanner falsely rejecting the built-in `JavaProxyObject` with XA4251 (dotnet/android#11622). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the production TryRegisterBuiltInNativeMembers helper with a samples-only solution, restoring the pre-#1441 behavior for the NativeAOT type managers. Background: making JavaProxyObject.RegisterNativeMembers private (the XA4251 fix) is sufficient for the default Android runtime, which discovers and invokes it via reflection (FindAndCallRegisterMethod -> GetRuntimeMethods(), which returns private methods). The only thing that needed JavaProxyObject's registration via a non-reflection path was the two NativeAOT samples, which #1441 had rewritten to derive from the reflection-free JniRuntime.JniTypeManager base. That is why #1441 introduced TryRegisterBuiltInNativeMembers. Instead of carrying that helper in production, have the sample type managers derive from JniRuntime.ReflectionJniTypeManager again - exactly what the base JniRuntime.JniTypeManager provided before #1441. Built-in types such as JavaProxyObject/JavaProxyThrowable are then registered automatically via reflection, and the samples no longer need any custom registration code. Changes: - JavaProxyObject.RegisterNativeMembers: internal -> private. The marshalers stay (the default reflection runtime registers them by reflecting over this type); only the visibility changes, which strips the attribute from the reference assembly and fixes the dotnet/android XA4251 scanner failure. - Delete JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers (+ its PublicAPI.Unshipped.txt entry); no longer needed. - Hello-NativeAOTFromJNI / Hello-NativeAOTFromAndroid: derive from JniRuntime.ReflectionJniTypeManager and drop the reflection-free overrides and hand-written JavaProxyObject registration. ReflectionJniTypeManager is [RequiresDynamicCode]/[RequiresUnreferencedCode], so the constructor suppresses IL2026/IL3050 with [UnconditionalSuppressMessage]. A #pragma is insufficient here: it silences the Roslyn analyzer but not the ILLink/ILC publish passes, which only honor the attribute (verified with a trim-publish of the real type manager against Java.Interop.dll). Net: production loses code (1-line visibility change + deletions) and the samples shrink substantially. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Build 1465402's "run Hello-NativeAOTFromJNI" step failed at runtime: System.NotSupportedException: Could not find System.Type corresponding to Java type JniTypeSignature(TypeName=example/ManagedType ...) at Java.Interop.ManagedPeer.RegisterNativeMembers(...) at example.ManagedType.<clinit>(ManagedType.java:15) Root cause: after #1441, JniRuntime.JniTypeManager.GetType() dispatches through GetTypeForSimpleReference (singular), not GetTypesForSimpleReference (plural). The previous sample rewrite only overrode the plural method, so app types like example/ManagedType were never resolved (the base ReflectionJniTypeManager's GetTypeForSimpleReference only knows built-in types), and ManagedPeer registration threw before it could register the type. Fix: override GetTypeForSimpleReference (singular) in both NativeAOT sample type managers to resolve the sample's own managed types, falling back to the base for built-ins. Registration and the reverse Type->JNI mapping continue to be handled by the reflection base (the pre-#1441 behavior). The override carries the same [return: DynamicallyAccessedMembers(...)] as the base to satisfy IL2093. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a class-level comment to both NativeAOT sample type managers explaining the rationale for the [UnconditionalSuppressMessage] IL2026/IL3050 suppressions: - These are *samples*, not product code. .NET for Android (what we ship) does not pair ReflectionJniTypeManager with NativeAOT, so it isn't worth the effort to make these samples fully trim/AOT-clean right now. - The reflection paths were always trim/AOT-unsafe. Before #1441 the equivalent suppressions lived inside JniTypeManager itself (justified "NotUsedInAndroid"); #1441 simply moved that responsibility to callers via [RequiresDynamicCode]/[RequiresUnreferencedCode]. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Revert the external/Java.Interop submodule bump. Pinning an unmerged java-interop PR-branch commit is fragile, and advancing to java-interop main HEAD would drag in unrelated changes (e.g. dotnet/java-interop#1441) that need their own coordination. The generator-side removal lives in dotnet/java-interop#1470 and will reach Mono.Android through the normal submodule bump once it merges; this PR keeps only the independent runtime cleanup of the now-dead `packageLookup` plumbing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…in (#1468) #1441 changed `JavaProxyObject.RegisterNativeMembers` from `private` to `internal` so the new reflection-free `JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers ()` could call it directly. A side effect is that the `[JniAddNativeMethodRegistrationAttribute]`- annotated method now appears in Java.Interop's *reference* assembly. dotnet/android's trimmable typemap scanner reads reference assemblies and rejects any type carrying that attribute (XA4251), so every trimmable typemap build now fails on the built-in `JavaProxyObject` (see dotnet/android#11622). Restore the method to `private` (so it is stripped from the reference assembly) and expose a small attribute-free `internal` entry point, `RegisterBuiltInNativeMembers (JniType)`, for the built-in registration path to call. Runtime behavior is unchanged. ### [Java.Interop] Restore pure-reflection registration for JavaProxyObject #1441 added `JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers ()` -- a reflection-free direct call to `JavaProxyObject.RegisterNativeMembers` -- and bumped that method to `internal` so the call would compile. That method is dead code: nothing in Java.Interop (or dotnet/android) ever calls it, and the base `JniTypeManager.RegisterNativeMembers` is a no-op. `JavaProxyObject`'s native members are -- and were before #1441 -- registered purely via reflection in `ReflectionJniTypeManager` (`FindAndCallRegisterMethod` discovering the `[JniAddNativeMethodRegistrationAttribute]`-annotated method), which finds private methods just fine via `GetRuntimeMethods ()`. Remove the unused `TryRegisterBuiltInNativeMembers` and restore `RegisterNativeMembers` to `private` (its state before #1441). Making it private again also strips the attribute-bearing method from the reference assembly, which fixes dotnet/android's trimmable typemap scanner falsely rejecting the built-in `JavaProxyObject` with XA4251 (dotnet/android#11622). ### Address review: restore TryRegisterBuiltInNativeMembers (not dead code) The previous commit deleted JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers claiming it was unused. That was wrong: it has two live callers in the NativeAOT samples (samples/Hello-NativeAOTFromJNI/NativeAotTypeManager.cs and samples/Hello-NativeAOTFromAndroid/NativeAotTypeManager.cs), both JniTypeManager subclasses. Deleting it broke them (CS0103) and removed the only path that registers JavaProxyObject's equals/hashCode/toString natives for NativeAOT type managers (which don't use the reflection-based FindAndCallRegisterMethod path). The breakage was hidden in CI only because the NativeAOT sample steps use continueOnError: true. Keep the XA4251 fix (RegisterNativeMembers stays private, so the [JniAddNativeMethodRegistrationAttribute] is stripped from the reference assembly) and restore TryRegisterBuiltInNativeMembers by extracting the registration logic into a new attribute-free internal helper, JavaProxyObject.AddBuiltInRegistrations. Both the private attributed RegisterNativeMembers (reflection path) and TryRegisterBuiltInNativeMembers (NativeAOT path) call it. Restore the PublicAPI.Unshipped.txt entry as well. Verified: Java.Interop.dll builds clean; the reference assembly contains no method carrying [JniAddNativeMethodRegistration]; the sample's call to TryRegisterBuiltInNativeMembers resolves (no CS0103). ### Keep built-in registration in the samples via ReflectionJniTypeManager Replaces the production TryRegisterBuiltInNativeMembers helper with a samples-only solution, restoring the pre-#1441 behavior for the NativeAOT type managers. Background: making JavaProxyObject.RegisterNativeMembers private (the XA4251 fix) is sufficient for the default Android runtime, which discovers and invokes it via reflection (FindAndCallRegisterMethod -> GetRuntimeMethods(), which returns private methods). The only thing that needed JavaProxyObject's registration via a non-reflection path was the two NativeAOT samples, which #1441 had rewritten to derive from the reflection-free JniRuntime.JniTypeManager base. That is why #1441 introduced TryRegisterBuiltInNativeMembers. Instead of carrying that helper in production, have the sample type managers derive from JniRuntime.ReflectionJniTypeManager again - exactly what the base JniRuntime.JniTypeManager provided before #1441. Built-in types such as JavaProxyObject/JavaProxyThrowable are then registered automatically via reflection, and the samples no longer need any custom registration code. Changes: - JavaProxyObject.RegisterNativeMembers: internal -> private. The marshalers stay (the default reflection runtime registers them by reflecting over this type); only the visibility changes, which strips the attribute from the reference assembly and fixes the dotnet/android XA4251 scanner failure. - Delete JniRuntime.JniTypeManager.TryRegisterBuiltInNativeMembers (+ its PublicAPI.Unshipped.txt entry); no longer needed. - Hello-NativeAOTFromJNI / Hello-NativeAOTFromAndroid: derive from JniRuntime.ReflectionJniTypeManager and drop the reflection-free overrides and hand-written JavaProxyObject registration. ReflectionJniTypeManager is [RequiresDynamicCode]/[RequiresUnreferencedCode], so the constructor suppresses IL2026/IL3050 with [UnconditionalSuppressMessage]. A #pragma is insufficient here: it silences the Roslyn analyzer but not the ILLink/ILC publish passes, which only honor the attribute (verified with a trim-publish of the real type manager against Java.Interop.dll). Net: production loses code (1-line visibility change + deletions) and the samples shrink substantially. ### Drop verbose explanatory comments from the NativeAOT sample type managers The [UnconditionalSuppressMessage] Justification strings already convey the necessary context. ### Fix NativeAOT sample type resolution: override GetTypeForSimpleReference Build 1465402's "run Hello-NativeAOTFromJNI" step failed at runtime: System.NotSupportedException: Could not find System.Type corresponding to Java type JniTypeSignature(TypeName=example/ManagedType ...) at Java.Interop.ManagedPeer.RegisterNativeMembers(...) at example.ManagedType.<clinit>(ManagedType.java:15) Root cause: after #1441, JniRuntime.JniTypeManager.GetType() dispatches through GetTypeForSimpleReference (singular), not GetTypesForSimpleReference (plural). The previous sample rewrite only overrode the plural method, so app types like example/ManagedType were never resolved (the base ReflectionJniTypeManager's GetTypeForSimpleReference only knows built-in types), and ManagedPeer registration threw before it could register the type. Fix: override GetTypeForSimpleReference (singular) in both NativeAOT sample type managers to resolve the sample's own managed types, falling back to the base for built-ins. Registration and the reverse Type->JNI mapping continue to be handled by the reflection base (the pre-#1441 behavior). The override carries the same [return: DynamicallyAccessedMembers(...)] as the base to satisfy IL2093. ### Address review: drop unreachable null guard in FromAndroid sample `typeMappings` is assigned in its field initializer and never set to null, so the `if (typeMappings == null) yield break;` guard in CreateSimpleReferencesEnumerator was unreachable dead code. Remove it. ### Document why the NativeAOT sample trim/AOT suppressions are acceptable Add a class-level comment to both NativeAOT sample type managers explaining the rationale for the [UnconditionalSuppressMessage] IL2026/IL3050 suppressions: - These are *samples*, not product code. .NET for Android (what we ship) does not pair ReflectionJniTypeManager with NativeAOT, so it isn't worth the effort to make these samples fully trim/AOT-clean right now. - The reflection paths were always trim/AOT-unsafe. Before #1441 the equivalent suppressions lived inside JniTypeManager itself (justified "NotUsedInAndroid"); #1441 simply moved that responsibility to callers via [RequiresDynamicCode]/[RequiresUnreferencedCode]. ### Reword NativeAOT sample suppression justifications to describe why it's safe The previous IL2026/IL3050 justifications ("does not require unreferenced code / runtime code generation") read as the opposite of reality, since ReflectionJniTypeManager is exactly [RequiresUnreferencedCode]/[RequiresDynamicCode]. Reword to describe why the suppression is correct for this sample: - IL2026: the assembly is rooted via TrimmerRootAssembly and the reflected registration members are preserved by the [DynamicallyAccessedMembers] annotations on the RegisterNativeMembers(Type) -> FindAndCallRegisterMethod path. - IL3050: registration uses CreateDelegate on compile-time-known static methods (no MakeGenericType / expression compilation), so no runtime codegen is required. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mbing (#11667) The binding generator used to emit a `Java.Interop.__TypeRegistrations` class whose `RegisterPackages ()` populated `TypeManager.packageLookup`. The only reader of that dictionary -- the `TypeRegistrationFallback` path in `GetJavaToManagedTypeCore` -- was removed in #9471, so the dictionary has been write-only (and, since nothing generated calls it, effectively unused) ever since. Type resolution now goes through the native / trimmable type map. This bumps external/Java.Interop to the generator change that stops emitting the dead class (dotnet/java-interop#1470) and removes the now-dead runtime plumbing: * Delete the `packageLookup` field and `LazyInitPackageLookup ()`. * `TypeManager.RegisterPackage`/`RegisterPackages` are shipped public API, so they are kept as validated no-op stubs rather than removed. Fixes#11663 ### [Mono.Android] Throw NotSupportedException with an explanatory message `TypeManager.RegisterPackage`/`RegisterPackages` are retired shipped public APIs whose registration mechanism no longer exists. Rather than silently doing nothing, they now throw `NotSupportedException` carrying an explanation of why the call is unsupported and what replaced it ("Java package type registration is no longer supported. Java-to-managed type resolution now goes through the native and trimmable type maps."), so a caller that still invokes them gets a clear, actionable reason instead of a bare exception. ### [Mono.Android] Don't bump external/Java.Interop in this PR Revert the external/Java.Interop submodule bump. Pinning an unmerged java-interop PR-branch commit is fragile, and advancing to java-interop main HEAD would drag in unrelated changes (e.g. dotnet/java-interop#1441) that need their own coordination. The generator-side removal lives in dotnet/java-interop#1470 and will reach Mono.Android through the normal submodule bump once it merges; this PR keeps only the independent runtime cleanup of the now-dead `packageLookup` plumbing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bumps [external/Java.Interop](https://github.com/dotnet/java-interop) from `b881d21` to [`6820a9cb`](dotnet/java-interop@6820a9c). ## Java.Interop changes pulled in - [`6820a9c`](dotnet/java-interop@6820a9c) [Java.Interop] Make JavaProxyObject.RegisterNativeMembers private again ([#1468](dotnet/java-interop#1468)) - [`a2159d7`](dotnet/java-interop@a2159d70) Remove NuGet dependency grouping from Dependabot config ([#1459](dotnet/java-interop#1459)) - [`a0ca676`](dotnet/java-interop@a0ca6760) [ci] Update agentic workflows to claude-opus-4.8 ([#1461](dotnet/java-interop#1461)) - [`6d8aef7`](dotnet/java-interop@6d8aef7b) Bump to dotnet/android-tools@1fb68b6 ([#1457](dotnet/java-interop#1457)) - [`cfca8ad`](dotnet/java-interop@cfca8ad) Upgrade gh-aw to v0.79.6 and recompile workflows ([#1460](dotnet/java-interop#1460)) - [`0104236`](dotnet/java-interop@0104236e) [JniValueManager] Make TryConstructPeer virtual ([#1456](dotnet/java-interop#1456)) - [`d7dbad5`](dotnet/java-interop@d7dbad5) Remove Java.Interop.Dynamic ([#1449](dotnet/java-interop#1449)) - [`0244c10`](dotnet/java-interop@0244c10) Split reflection-based JniTypeManager and JniValueManager behavior ([#1441](dotnet/java-interop#1441)) - [`9ca7064`](dotnet/java-interop@9ca7064) [Java.Interop] Remove legacy `NET` preprocessor paths ([#1451](dotnet/java-interop#1451)) - [`cd2fc12`](dotnet/java-interop@cd2fc12) Remove Java.Interop.GenericMarshaler ([#1450](dotnet/java-interop#1450)) See full diff in [compare view](dotnet/java-interop@b881d21...6820a9c). ## Changes in this PR (beyond the submodule bump) The Java.Interop bump — particularly [#1441](dotnet/java-interop#1441) (split reflection-based `JniTypeManager` and `JniValueManager`) and [#1449](dotnet/java-interop#1449) (removal of `Java.Interop.Dynamic`) — required a number of follow-on changes in `dotnet/android`: ### Runtime / `Mono.Android` - **Adapt Android runtime to Java.Interop reflection managers** — switch the `JavaInteropTypeManager` / `JavaInteropValueManager` hierarchy to the new reflection-based base types and update type/value manager construction to match. - **Change base type of value managers and type managers** so the runtime composes correctly with the new Java.Interop split. - **Suppress reflection manager trim warnings only** — narrow the existing trim suppressions so they apply to the reflection-only managers and don't hide other warnings. - **Suppress IL2068 on `ManagedTypeManager.GetTypeForSimpleReference`** — annotate the entry point that legitimately requires reflection so trim analysis is satisfied. ### Tests / apkdescs - **Regenerate `BuildReleaseArm64SimpleDotNet.MonoVM.apkdesc`** to track size changes from the Java.Interop bump. - **Regenerate Simple `BuildReleaseArm64` apkdescs** (`MonoVM` + `NativeAOT`) from a `Release` SDK so they match what CI produces. - **Update NativeAOT warning expectations** — `Mono.Android` now produces fewer trim/AOT warnings, so loosen the strict `4 Warning(s)` checks in `SupportedOSPlatformVersion` and `CheckSignApk` to `AssertHasAtMostWarnings (2)` (new helper in `AssertionExtensions`) and drop the `BuildHasTrimmerWarnings` data counts (`3 → 2`, `4 → 3`) accordingly. ### Merges - Two merges from `origin/main` to keep the branch current and resolve a conflict in `Microsoft.Android.Sdk.TypeMap.Trimmable.targets`. Co-authored-by: Simon Rozsival <simon@rozsival.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
… "trimmable type map" (#1454) Follow-up to #1441. Prerequisite for dotnet/android#11617. This keeps the Java.Interop changes focused on the small base hook dotnet/android needs for the trimmable type-map integration. The reflection value manager continues to use value marshalers internally, while Android's generated/trimmable value manager can provide the only production `JavaObjectArray<T>` element-assignment object-reference path without implementing `GetValueMarshaler*()`. ## Changes - Add minimal `JniValueManager` object-reference API for `JavaObjectArray<T>.SetElementAt()`: - `CreateLocalObjectReferenceArgument(Type type, object? value)` returns an owned local `JniObjectReference` for element assignment. Callers must dispose the returned reference. - Make the matching core method abstract so non-reflection value managers can implement this path directly. - Keep value marshalers as a `ReflectionJniValueManager` implementation detail: reflection creates marshaler state, copies out an independent local reference, then destroys the state immediately. - Update `JavaObjectArray<T>` to call the value manager directly instead of calling `GetValueMarshaler<T>()` in production paths. - Simplify `JavaObjectArray<T>.Clear()` to set array slots to Java null directly; it no longer needs value-manager or value-marshaler state. - Remove the earlier exposed proxy/peerable marshaler accessors, broad/generic state overloads, default-value state API, destroy-state API, and `ParameterAttributes synchronize` from this value-manager object-reference path. - Keep ManagedPeer-dependent tests categorized as unsupported for the Android trimmable configuration rather than carrying Android-specific Java fixture workarounds in this PR. - Include the small type-manager/test cleanups needed by the dotnet/android integration branch. ## Non-goals - This PR does not make value marshalers public trimmable API. - This PR does not require trimmable Android value managers to implement or use `GetValueMarshaler*()`. - This PR does not remove or replace ManagedPeer-dependent Java.Interop test fixtures. ## Validation - `dotnet build external/Java.Interop/src/Java.Interop/Java.Interop.csproj -p:Configuration=Debug -m:1 -nodeReuse:false --no-restore -v:minimal` - From the dotnet/android integration branch: - `dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj -v minimal --no-restore` (`562` passed) - `dotnet build src/Mono.Android/Mono.Android.csproj -p:Configuration=Debug -p:AndroidSdkDirectory=/Users/simonrozsival/android-toolchain/sdk -m:1 -nodeReuse:false --no-restore -v:minimal` compiled `Mono.Android.Runtime.dll`; the remaining local failure is Android SDK provisioning (`extras/android/m2repository.staging` and `docs.staging` missing), not C# or trim-analyzer errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The ReflectionJniTypeManager [RequiresDynamicCode] attribute predates this submodule bump (added in dotnet/java-interop#1441), so the ManagedTypeManager..ctor() IL3050 is already part of the BuildHasNoWarnings test's baseline of 4 warnings. Only the RegisterNativeMembers warning is new from this PR; suppressing the ctor warning too would drop the count to 2 and fail the test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Splits the reflection-backed paths out of
JniRuntime.JniTypeManagerandJniRuntime.JniValueManagerso callers that don't need reflection (e.g. NativeAOT) can opt out, while keeping the public API surface as compatible as possible for callers migrating from .NET 10.What changed
JniRuntime.ReflectionJniTypeManager(new): all reflection-based type lookup, signature discovery, and native-method registration moves here. Annotated with[RequiresDynamicCode]/[RequiresUnreferencedCode].JniRuntime.ReflectionJniValueManager(new): all reflection-based peer construction, value marshaling, and activation moves here.JniRuntime.JniTypeManagerstays a non-abstract base class. Methods that previously had reflection defaults are nowvirtualno-ops (or returnnull/empty). Subclasses that want the old default behavior should derive fromReflectionJniTypeManager.JniRuntime.JniValueManagerstays abstract (it already was). A few previously-virtualmethods (CreatePeer,GetValueMarshalerCore(Type)) becomeabstractsince their reflection-based defaults moved out — public consumers (dotnet/android's value managers) already override these.JreTypeManager,ManagedValueManager,MonoRuntimeValueManagernow derive from the newReflection*bases. The NativeAOT samples keep deriving from the abstract bases with explicit type mappings.Public API impact
PublicAPI.Shipped.txtis unchanged. All breaking changes are tracked inPublicAPI.Unshipped.txtwith*REMOVED*markers:JniTypeManager.TryRegisterNativeMembers(both overloads) — moved toReflectionJniTypeManagerasprotectedhelpers.JniValueManager.CreatePeerandGetValueMarshalerCore(Type)—virtual→abstract.A non-abstract
JniTypeManagerkeepsnew JniRuntime.JniTypeManager()and direct subclasses compiling without changes.Validation
dotnet build Java.Interop.slnpasses (PublicApiAnalyzer enforces tracking is consistent).