From 2735c111f934b260d3560afaf266c1b1a246bdff Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 26 Mar 2025 14:59:26 -0400 Subject: [PATCH 1/2] [Mono.Android] Add ManagedValueManager, use with CoreCLR Context: https://github.com/dotnet/android/issues/9962 Context: https://github.com/dotnet/java-interop/commit/5852e6e398a6c96d42fff3ed4b8a5d4b5a0abf97 `dotnet new maui -sc` fails under CoreCLR: D AndroidRuntime: Shutting down VM E AndroidRuntime: FATAL EXCEPTION: main E AndroidRuntime: Process: com.companyname.dotnetnewmauisamplecontent, PID: 6548 E AndroidRuntime: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: InvalidOperation_HandleIsNotInitialized E AndroidRuntime: at System.WeakReference`1.SetTarget + 0x18(Unknown Source) E AndroidRuntime: at Microsoft.Maui.ApplicationModel.ActivityLifecycleContextListener.set_Activity + 0x0(Unknown Source) E AndroidRuntime: at Microsoft.Maui.ApplicationModel.ActivityLifecycleContextListener.Android.App.Application.IActivityLifecycleCallbacks.OnActivityResumed + 0x0(Unknown Source) E AndroidRuntime: at Android.App.Application+IActivityLifecycleCallbacksInvoker.n_OnActivityResumed_Landroid_app_Activity_ + 0xe(Unknown Source) E AndroidRuntime: at crc64ba438d8f48cf7e75.ActivityLifecycleContextListener.n_onActivityResumed(Native Method) E AndroidRuntime: at crc64ba438d8f48cf7e75.ActivityLifecycleContextListener.onActivityResumed(ActivityLifecycleContextListener.java:42) E AndroidRuntime: at android.app.Application.dispatchActivityResumed(Application.java:431) E AndroidRuntime: at android.app.Activity.dispatchActivityResumed(Activity.java:1434) E AndroidRuntime: at android.app.Activity.onResume(Activity.java:1995) E AndroidRuntime: at androidx.fragment.app.FragmentActivity.onResume(FragmentActivity.java:309) E AndroidRuntime: at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1531) E AndroidRuntime: at android.app.Activity.performResume(Activity.java:8422) E AndroidRuntime: at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4793) E AndroidRuntime: at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4836) E AndroidRuntime: at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:54) E AndroidRuntime: at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45) E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176) E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97) E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2308) E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106) E AndroidRuntime: at android.os.Looper.loopOnce(Looper.java:201) E AndroidRuntime: at android.os.Looper.loop(Looper.java:288) E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7898) E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) *A* likely explanation is that we're using the wrong `JniRuntime.JniValueManager` when running under CoreCLR: we're using `AndroidValueManager`, which uses `WeakReference` to hold `IJavaPeerable` instances. As CoreCLR does not have a GC bridge, this means that any `IJavaPeerable` instance which is kept alive only by Java code -- such as `Activity` instances! -- will be collected, which at minimum will be "surprising". Fortunately, we already have a `JniRuntime.JniValueManager` which retains strong references to every created `IJavaPeerable` instance: `NativeAotValueManager`! The problem is that `NativeAotValueManager` is in `Microsoft.Android.Runtime.NativeAOT.dll`, which isn't usable from a CoreCLR context. Move `NativeAotValueManager` into `Mono.Android.dll`, renaming it `ManagedValueManager`. Update `JNIEnvInit.Initialize()` to use `ManagedValueManager` when running under CoreCLR. Relatedly: dotnet/java-interop@5852e6e3 obsoleted `JniRuntime.CreationOptions.ClassLoader_LoadClass_id`, so update `AndroidRuntime` and `AndroidRuntimeOptions` to no longer use that. --- .../JavaInteropRuntime.cs | 2 +- .../Java.Interop/JreRuntime.cs | 2 +- .../Android.Runtime/AndroidRuntime.cs | 14 ++++++++------ src/Mono.Android/Android.Runtime/JNIEnvInit.cs | 11 ++++++++++- .../ManagedValueManager.cs} | 18 +++++++++--------- src/Mono.Android/Mono.Android.csproj | 1 + 6 files changed, 30 insertions(+), 18 deletions(-) rename src/{Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotValueManager.cs => Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs} (94%) diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs index 1d0aae0ff41..a4fadb5cc7a 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs @@ -41,7 +41,7 @@ static void init (IntPtr jnienv, IntPtr klass) var options = new NativeAotRuntimeOptions { EnvironmentPointer = jnienv, TypeManager = typeManager, - ValueManager = new NativeAotValueManager (typeManager), + ValueManager = new ManagedValueManager (), UseMarshalMemberBuilder = false, JniGlobalReferenceLogWriter = settings.GrefLog, JniLocalReferenceLogWriter = settings.LrefLog, diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs index 630707a17cc..07ecd1037db 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs @@ -61,7 +61,7 @@ static NativeAotRuntimeOptions CreateJreVM (NativeAotRuntimeOptions builder) builder.TypeManager ??= new NativeAotTypeManager (); #endif // NET - builder.ValueManager ??= new NativeAotValueManager (builder.TypeManager); + builder.ValueManager ??= new ManagedValueManager (); builder.ObjectReferenceManager ??= new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter); if (builder.InvocationPointer != IntPtr.Zero || builder.EnvironmentPointer != IntPtr.Zero) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index abf30a6ef4b..fa62ebce7bf 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -23,12 +23,14 @@ class AndroidRuntime : JniRuntime { internal AndroidRuntime (IntPtr jnienv, IntPtr vm, IntPtr classLoader, - IntPtr classLoader_loadClass, + JniRuntime.JniTypeManager? typeManager, + JniRuntime.JniValueManager? valueManager, bool jniAddNativeMethodRegistrationAttributePresent) : base (new AndroidRuntimeOptions (jnienv, vm, classLoader, - classLoader_loadClass, + typeManager, + valueManager, jniAddNativeMethodRegistrationAttributePresent)) { // This is not ideal, but we need to set this while the runtime is initializing but we can't do it directly from the `JNIEnvInit.Initialize` method, since @@ -93,16 +95,16 @@ class AndroidRuntimeOptions : JniRuntime.CreationOptions { public AndroidRuntimeOptions (IntPtr jnienv, IntPtr vm, IntPtr classLoader, - IntPtr classLoader_loadClass, + JniRuntime.JniTypeManager? typeManager, + JniRuntime.JniValueManager? valueManager, bool jniAddNativeMethodRegistrationAttributePresent) { EnvironmentPointer = jnienv; ClassLoader = new JniObjectReference (classLoader, JniObjectReferenceType.Global); - ClassLoader_LoadClass_id= classLoader_loadClass; InvocationPointer = vm; ObjectReferenceManager = new AndroidObjectReferenceManager (); - TypeManager = new AndroidTypeManager (jniAddNativeMethodRegistrationAttributePresent); - ValueManager = new AndroidValueManager (); + TypeManager = typeManager ?? new AndroidTypeManager (jniAddNativeMethodRegistrationAttributePresent); + ValueManager = valueManager ?? new AndroidValueManager (); UseMarshalMemberBuilder = false; JniAddNativeMethodRegistrationAttributePresent = jniAddNativeMethodRegistrationAttributePresent; } diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index cb6f9935c4c..873965d4c3f 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -8,6 +8,8 @@ using Java.Interop; using Java.Interop.Tools.TypeNameMappings; +using Microsoft.Android.Runtime; + namespace Android.Runtime { static internal class JNIEnvInit @@ -108,7 +110,14 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) java_class_loader = args->grefLoader; BoundExceptionType = (BoundExceptionType)args->ioExceptionType; - androidRuntime = new AndroidRuntime (args->env, args->javaVm, args->grefLoader, args->Loader_loadClass, args->jniAddNativeMethodRegistrationAttributePresent != 0); + androidRuntime = new AndroidRuntime ( + args->env, + args->javaVm, + args->grefLoader, + null, + RuntimeType != DotNetRuntimeType.MonoVM ? new ManagedValueManager () : null, + args->jniAddNativeMethodRegistrationAttributePresent != 0 + ); ValueManager = androidRuntime.ValueManager; IsRunningOnDesktop = args->isRunningOnDesktop == 1; diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs similarity index 94% rename from src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotValueManager.cs rename to src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs index e54d49ca7e4..ce738aaac8a 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/NativeAotValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs @@ -16,15 +16,15 @@ namespace Microsoft.Android.Runtime; -class NativeAotValueManager : JniRuntime.JniValueManager +class ManagedValueManager : JniRuntime.JniValueManager { const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; - readonly JniRuntime.JniTypeManager TypeManager; Dictionary>? RegisteredInstances = new Dictionary>(); - public NativeAotValueManager(JniRuntime.JniTypeManager typeManager) => - TypeManager = typeManager; + internal ManagedValueManager () + { + } public override void WaitForGCBridgeProcessing () { @@ -33,7 +33,7 @@ public override void WaitForGCBridgeProcessing () public override void CollectPeers () { if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (NativeAotValueManager)); + throw new ObjectDisposedException (nameof (ManagedValueManager)); var peers = new List (); @@ -62,7 +62,7 @@ public override void CollectPeers () public override void AddPeer (IJavaPeerable value) { if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (NativeAotValueManager)); + throw new ObjectDisposedException (nameof (ManagedValueManager)); var r = value.PeerReference; if (!r.IsValid) @@ -127,7 +127,7 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal public override IJavaPeerable? PeekPeer (JniObjectReference reference) { if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (NativeAotValueManager)); + throw new ObjectDisposedException (nameof (ManagedValueManager)); if (!reference.IsValid) return null; @@ -153,7 +153,7 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal public override void RemovePeer (IJavaPeerable value) { if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (NativeAotValueManager)); + throw new ObjectDisposedException (nameof (ManagedValueManager)); if (value == null) throw new ArgumentNullException (nameof (value)); @@ -243,7 +243,7 @@ void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, public override List GetSurfacedPeers () { if (RegisteredInstances == null) - throw new ObjectDisposedException (nameof (NativeAotValueManager)); + throw new ObjectDisposedException (nameof (ManagedValueManager)); lock (RegisteredInstances) { var peers = new List (RegisteredInstances.Count); diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 51f06fbd502..229a3b8d116 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -349,6 +349,7 @@ + From 921f64a5fe0fbaf2c95be865621baee931b374df Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 26 Mar 2025 16:47:58 -0400 Subject: [PATCH 2/2] Suppress IL2072 CI was emitting a new warning: /Users/builder/azdo/_work/10/s/xamarin-android/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs(236,3): Trim analysis warning IL2072: Microsoft.Android.Runtime.ManagedValueManager.ActivateViaReflection(JniObjectReference, ConstructorInfo, Object[]): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors', 'DynamicallyAccessedMemberTypes.NonPublicConstructors' in call to 'System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(Type)'. The return value of method 'System.Reflection.MemberInfo.DeclaringType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. Suppress the warnings. --- .../Microsoft.Android.Runtime/ManagedValueManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs index ce738aaac8a..616217ca4bc 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs @@ -230,7 +230,7 @@ public override void ActivatePeer (IJavaPeerable? self, JniObjectReference refer void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) { - var declType = cinfo.DeclaringType ?? throw new NotSupportedException ("Do not know the type to create!"); + var declType = GetDeclaringType (cinfo); #pragma warning disable IL2072 var self = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (declType); @@ -238,6 +238,11 @@ void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, self.SetPeerReference (reference); cinfo.Invoke (self, argumentValues); + + [UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "🤷‍♂️")] + [return: DynamicallyAccessedMembers (Constructors)] + Type GetDeclaringType (ConstructorInfo cinfo) => + cinfo.DeclaringType ?? throw new NotSupportedException ("Do not know the type to create!"); } public override List GetSurfacedPeers ()