Skip to content

[Mono.Android] Add ManagedValueManager, use with CoreCLR #9973

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 27, 2025

Conversation

jonpryor
Copy link
Contributor

Context: #9962
Context: dotnet/java-interop@5852e6e

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<T> 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.

Context: #9962
Context: dotnet/java-interop@5852e6e

`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<T>` 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.
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.
Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might conflict slightly with:

But this one can go in first, it's a smaller change.

{
const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

readonly JniRuntime.JniTypeManager TypeManager;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird, I guess this is unused now.

@jonpryor jonpryor merged commit e347cd3 into main Mar 27, 2025
57 of 59 checks passed
@jonpryor jonpryor deleted the dev/jonp/jonp-add-ManagedValueManager branch March 27, 2025 01:02
@ivanpovazan
Copy link
Member

Will this get "backported" to release/10.0.1xx-preview3 ?

jonathanpeppers pushed a commit that referenced this pull request Mar 27, 2025
Context: #9962
Context: dotnet/java-interop@5852e6e

`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<T>` 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.

* 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.
@jonathanpeppers
Copy link
Member

Will this get "backported" to release/10.0.1xx-preview3 ?

It should be there now:

jonathanpeppers added a commit to dotnet/maui that referenced this pull request Apr 2, 2025
Context: dotnet/android#9973

We introduced a new `internal` type in `Mono.Android.dll`:

    namespace Microsoft.Android.Runtime;

    class ManagedValueManager : JniRuntime.JniValueManager

Causes various C# compiler errors in dotnet/maui:

    D:\src\maui\src\Essentials\src\AppActions\AppActions.shared.cs(62,28):
    error CS0234: The type or namespace name 'Content' does not exist in the namespace 'Microsoft.Android' (are you missing an assembly reference?)

At some point in .NET 10 (or future), we will likely introduce a
*public* type in `Microsoft.Android` namespace.

This means MAUI's codebase will need to be fixed in a .NET 10
timeframe, so instead of:

    Android.Content.Context content;

This works:

    using Android.Content;
    //...
    Context content;

Because the `using Android.Content;` directive is scoped *outside* of
a `Microsoft.*` namespace.

We don't anticipate this to be a problem with customers, because they
aren't writing code in `Microsoft.*` namespaces.

I generally took the approach:

* Use `using Android.Content;` if possible.

* Use `global::` if that caused other conflicts.

* Use `using AView = Android.Views.View;` in Microsoft.Maui.Controls
  or other places this is commonly used.

* Update `#if __ANDROID__` to `#if ANDROID` in a few places.
rmarinho pushed a commit to dotnet/maui that referenced this pull request Apr 3, 2025
Context: dotnet/android#9973

We introduced a new `internal` type in `Mono.Android.dll`:

    namespace Microsoft.Android.Runtime;

    class ManagedValueManager : JniRuntime.JniValueManager

Causes various C# compiler errors in dotnet/maui:

    D:\src\maui\src\Essentials\src\AppActions\AppActions.shared.cs(62,28):
    error CS0234: The type or namespace name 'Content' does not exist in the namespace 'Microsoft.Android' (are you missing an assembly reference?)

At some point in .NET 10 (or future), we will likely introduce a
*public* type in `Microsoft.Android` namespace.

This means MAUI's codebase will need to be fixed in a .NET 10
timeframe, so instead of:

    Android.Content.Context content;

This works:

    using Android.Content;
    //...
    Context content;

Because the `using Android.Content;` directive is scoped *outside* of
a `Microsoft.*` namespace.

We don't anticipate this to be a problem with customers, because they
aren't writing code in `Microsoft.*` namespaces.

I generally took the approach:

* Use `using Android.Content;` if possible.

* Use `global::` if that caused other conflicts.

* Use `using AView = Android.Views.View;` in Microsoft.Maui.Controls
  or other places this is commonly used.

* Update `#if __ANDROID__` to `#if ANDROID` in a few places.
@github-actions github-actions bot locked and limited conversation to collaborators Apr 27, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants