-
Notifications
You must be signed in to change notification settings - Fork 536
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
[nativeaot] support for Application
subclasses
#9716
Conversation
src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java
Outdated
Show resolved
Hide resolved
src/Xamarin.Android.Build.Tasks/Resources/ApplicationRegistration.java
Outdated
Show resolved
Hide resolved
Context: https://github.com/dotnet/android/pull/9630/files#r1891090085 Context: dotnet/android#9716 As part of NativeAOT prototyping within dotnet/android, we need to update `Java.Lang.Object.GetObject<T>()` so that it uses `Java.Interop.JniRuntime.JniValueManager` APIs instead of its own `TypeManager.CreateInstance()` invocation, as `TypeManager.CreateInstance()` hits P/Invokes which don't currently work in the NativeAOT sample environment. However, updated it to *what*? The obvious thing to do would be to use `JniRuntime.JniValueManager.GetValue()`: partial class Object { internal static IJavaPeerable? GetObject (IntPtr handle, JniHandleOwnershipt ransfer, Type? type = null) { var r = PeekObject (handle, type); if (r != null) { JNIEnv.DeleteRef (handle, transfer); return r; } var reference = new JniObjectReference (handle); r = (IJavaPeerable) JNIEnvInit.ValueManager.GetValue ( ref reference, JniObjectReferenceOptions.Copy, type); JNIEnv.DeleteRef (handle, transfer); return r; } } The problem is that this blows up good: <System.InvalidCastException: Arg_InvalidCastException at Java.Lang.Object.GetObject(IntPtr , JniHandleOwnership , Type ) at Android.Runtime.JNIEnv.<CreateNativeArrayElementToManaged>g__GetObject|74_11(IntPtr , Type ) at Android.Runtime.JNIEnv.<>c.<CreateNativeArrayElementToManaged>b__74_9(Type type, IntPtr source, Int32 index) at Android.Runtime.JNIEnv.GetObjectArray(IntPtr , Type[] ) at Java.InteropTests.JnienvTest.<>c__DisplayClass26_0.<MoarThreadingTests>b__1()> because somewhere in that stack trace we have a `java.lang.Integer` instance, and `.GetValue(Integer_ref, …)` returns a `System.Int32` containing the underling value, *not* an `IJavaPeerable` value for the `java.lang.Integer` instance. Consider: var i_class = new JniType ("java/lang/Integer"); var i_ctor = i_class.GetConstructor ("(I)V"); JniArgumentValue* i_args = stackalloc JniArgumentValue [1]; i_args [0] = new JniArgumentValue (42); var i_value = i_class.NewObject (i_ctor, i_args); var v = JniEnvironment.Runtime.ValueManager.GetValue (ref i_value, JniObjectReferenceOptions.CopyAndDispose, null); Console.WriteLine ($"v? {v} {v?.GetType ()}"); which prints `v? 42 System.Int32`. This was expected and desirable, until we try to use `GetValue()` for `Object.GetObject<T>()`; the semantics don't match. Add a new `JniRuntime.JniValueManager.GetPeer()` method, which better matches the semantics that `Object.GetObject<T>()` requires, allowing: partial class Object { internal static IJavaPeerable? GetObject (IntPtr handle, JniHandleOwnershipt ransfer, Type? type = null) { var r = JNIEnvInit.ValueManager.GetPeer (new JniObjectReference (handle)); JNIEnv.DeleteRef (handle, transfer); return r; } } Finally, add a new `JniRuntimeJniValueManagerContract` unit test, so that we have "more formalized" semantic requirements on `JniRuntime.JniValueManager` implementations.
Context: https://github.com/dotnet/android/pull/9630/files#r1891090085 Context: dotnet/android#9716 As part of NativeAOT prototyping within dotnet/android, we need to update [`Java.Lang.Object.GetObject<T>()`][0] so that it uses `Java.Interop.JniRuntime.JniValueManager` APIs instead of its own `TypeManager.CreateInstance()` invocation, as `TypeManager.CreateInstance()` hits P/Invokes which don't currently work in the NativeAOT sample environment. However, update it to *what*? The obvious thing to do would be to use `JniRuntime.JniValueManager.GetValue()`: partial class Object { internal static IJavaPeerable? GetObject (IntPtr handle, JniHandleOwnership transfer, Type? type = null) { var r = PeekObject (handle, type); if (r != null) { JNIEnv.DeleteRef (handle, transfer); return r; } var reference = new JniObjectReference (handle); r = (IJavaPeerable) JNIEnvInit.ValueManager.GetValue ( ref reference, JniObjectReferenceOptions.Copy, type); JNIEnv.DeleteRef (handle, transfer); return r; } } The problem is that this blows up good: <System.InvalidCastException: Arg_InvalidCastException at Java.Lang.Object.GetObject(IntPtr , JniHandleOwnership , Type ) at Android.Runtime.JNIEnv.<CreateNativeArrayElementToManaged>g__GetObject|74_11(IntPtr , Type ) at Android.Runtime.JNIEnv.<>c.<CreateNativeArrayElementToManaged>b__74_9(Type type, IntPtr source, Int32 index) at Android.Runtime.JNIEnv.GetObjectArray(IntPtr , Type[] ) at Java.InteropTests.JnienvTest.<>c__DisplayClass26_0.<MoarThreadingTests>b__1()> because somewhere in that stack trace we have a `java.lang.Integer` instance, and `.GetValue(Integer_ref, …)` returns a `System.Int32` containing the underling value, *not* an `IJavaPeerable` value for the `java.lang.Integer` instance. Consider: var i_class = new JniType ("java/lang/Integer"); var i_ctor = i_class.GetConstructor ("(I)V"); JniArgumentValue* i_args = stackalloc JniArgumentValue [1]; i_args [0] = new JniArgumentValue (42); var i_value = i_class.NewObject (i_ctor, i_args); var v = JniEnvironment.Runtime.ValueManager.GetValue (ref i_value, JniObjectReferenceOptions.CopyAndDispose, null); Console.WriteLine ($"v? {v} {v?.GetType ()}"); which prints `v? 42 System.Int32`. This was expected and desirable, until we try to use `GetValue()` for `Object.GetObject<T>()`; the semantics don't match. Add a new `JniRuntime.JniValueManager.GetPeer()` method, which better matches the semantics that `Object.GetObject<T>()` requires, allowing: partial class Object { internal static IJavaPeerable? GetObject (IntPtr handle, JniHandleOwnership transfer, Type? type = null) { var r = JNIEnvInit.ValueManager.GetPeer (new JniObjectReference (handle)); JNIEnv.DeleteRef (handle, transfer); return r; } } Finally, add a new `JniRuntimeJniValueManagerContract` unit test, so that we have "more formalized" semantic requirements on `JniRuntime.JniValueManager` implementations. [0]: https://github.com/dotnet/android/blob/cc35a263e046444c2123e7a7dba106b18e2bbebb/src/Mono.Android/Java.Lang/Object.cs#L133-L166
Should fix the unexpected P/Invoke which is causing the NativeAOT sample to crash: net.dot.jni.internal.JavaProxyThrowable: System.DllNotFoundException: DllNotFound_Linux, xa-internal-api, dlopen failed: library "xa-internal-api.so" not found dlopen failed: library "libxa-internal-api.so" not found dlopen failed: library "xa-internal-api" not found dlopen failed: library "libxa-internal-api" not found 01-30 02:19:25.570 3348 3348 E AndroidRuntime: at System.Runtime.InteropServices.NativeLibrary.LoadLibErrorTracker.Throw(String) + 0x47 at Internal.Runtime.CompilerHelpers.InteropHelpers.FixupModuleCell(InteropHelpers.ModuleFixupCell*) + 0xe2 at Internal.Runtime.CompilerHelpers.InteropHelpers.ResolvePInvokeSlow(InteropHelpers.MethodFixupCell*) + 0x35 at Android.Runtime.RuntimeNativeMethods.monodroid_TypeManager_get_java_class_name(IntPtr) + 0x22 at Java.Interop.TypeManager.GetClassName(IntPtr) + 0xe at Java.Interop.TypeManager.CreateInstance(IntPtr, JniHandleOwnership, Type) + 0x69 at Java.Lang.Object._GetObject[T](IntPtr, JniHandleOwnership) + 0x4e at Android.App.Application.n_OnCreate(IntPtr jnienv, IntPtr native__this) + 0x89 at my.MainApplication.n_onCreate(Native Method) at my.MainApplication.onCreate(MainApplication.java:24) at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1182) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6460) at android.app.ActivityThread.access$1300(ActivityThread.java:219) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1859) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Context: #9630 Context: #9716 Context: dotnet/java-interop@e288589 @jonathanpeppers attempted to prototype MAUI startup in a NativeAOT environment, which promptly crashes similar to: E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.DllNotFoundException: DllNotFound_Linux, xa-internal-api, E AndroidRuntime: dlopen failed: library "xa-internal-api" not found E AndroidRuntime: dlopen failed: library "libxa-internal-api" not found E AndroidRuntime: E AndroidRuntime: at System.Runtime.InteropServices.NativeLibrary.LoadLibErrorTracker.Throw(String) + 0x47 E AndroidRuntime: at Internal.Runtime.CompilerHelpers.InteropHelpers.FixupModuleCell(InteropHelpers.ModuleFixupCell*) + 0xe2 E AndroidRuntime: at Internal.Runtime.CompilerHelpers.InteropHelpers.ResolvePInvokeSlow(InteropHelpers.MethodFixupCell*) + 0x35 E AndroidRuntime: at Android.Runtime.RuntimeNativeMethods.monodroid_TypeManager_get_java_class_name(IntPtr) + 0x22 E AndroidRuntime: at Java.Interop.TypeManager.GetClassName(IntPtr) + 0xe E AndroidRuntime: at Java.Interop.TypeManager.CreateInstance(IntPtr, JniHandleOwnership, Type) + 0x69 E AndroidRuntime: at Java.Lang.Object._GetObject[T](IntPtr, JniHandleOwnership) + 0x4e E AndroidRuntime: at Android.App.Application.n_OnCreate(IntPtr jnienv, IntPtr native__this) + 0x89 E AndroidRuntime: at my.MainApplication.n_onCreate(Native Method) E AndroidRuntime: at my.MainApplication.onCreate(MainApplication.java:24) E AndroidRuntime: at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1182) E AndroidRuntime: at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6460) E AndroidRuntime: at android.app.ActivityThread.access$1300(ActivityThread.java:219) E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1859) E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:107) E AndroidRuntime: at android.os.Looper.loop(Looper.java:214) E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7356) E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) because: 1. MAUI apps provide an `Android.App.Application` sublass, and 2. [`Application` is "special"][0], and 3. When `Java.Lang.Object.GetObject<T>()` is hit for an instance which doesn't already have an instance mapping -- i.e. `Object.PeekObject()` returns `null` -- then we'd hit `TypeManager.CreateInstance()`, which is a codepath full of P/Invokes, and P/Invokes don't currently work on NativeAOT [^1]. The solution is to partially resuscitate PR #9630, but this time have `Java.Lang.Object.GetObject<T>()` call `Java.Interop.JniRuntime.JniValueManager.GetPeer()` instead of `Java.Interop.JniRuntime.JniValueManager.GetValue()`; see also dotnet/java-interop@e288589d. This allows `Application` subclasses to be used (along with other build system changes present in #9716). This also cleans up a few things if `Java.Lang.Object` introduces an internal `DynamicallyAccessedMemberTypes Constructors` field. [0]: https://learn.microsoft.com/en-us/previous-versions/xamarin/android/internals/architecture#java-activation [^1]: It's *not* that NativeAOT doesn't support P/Invokes; it does. The problem is that for a P/Invoke to work, we need to know the name of the native library we're P/Invoking into, and our NativeAOT sample environment doesn't include any `.so` files other than the one for the app, i.e. whatever we'd P/Invoke into doesn't exist.
…on/nativeaot This should hopefully fix the NativeAOT sample…
|
||
public NativeAotValueManager(NativeAotTypeManager typeManager) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this doesn't need to know about NativeAotTypeManager
, then this can instead provide a default constructor and override OnSetRuntime()
to cache the TypeManager
instance:
public override void OnSetRuntime (JniRuntime runtime)
{
base.OnSetRuntime (runtime);
TypeManager = runtime.TypeManager;
}
The "benefit" is that you don't need to change the the init code in JavaInteropRuntime.init()
. (That might be a silly reason…)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like this works because of ordering:
runtime.TypeManager
is null at this point.
I changed the field to JniRuntime.JniTypeManager
, though.
Context: https://discord.com/channels/732297728826277939/732297837953679412/1334614545871929345 PR #9716 was crashing with a stack overflow: I NativeAotFromAndroid: at Java.Interop.JniEnvironment.InstanceMethods.CallVoidMethod(JniObjectReference, JniMethodInfo, JniArgumentValue*) + 0xa8 I NativeAotFromAndroid: at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeVirtualVoidMethod(String, IJavaPeerable, JniArgumentValue*) + 0x184 I NativeAotFromAndroid: at Android.App.Application.n_OnCreate(IntPtr jnienv, IntPtr native__this) + 0xa8 I NativeAotFromAndroid: at libNativeAOT!<BaseAddress>+0x4f3e44 The cause was the topmost frame: `CallVoidMethod()`, which performs a *virtual* method invocation. The stack overflow was that Java `MainApplication.onCreate()` called C# `Application.n_OnCreate()`, which called `InvokeVirtualVoidMethod()`, which did a *virtual* invocation back on `MainApplication.onCreate()`, … `InvokeVirtualVoidMethod()` should have been calling `CallNonvirtualVoidMethod()`; why wasn't it? Further investigation showed: Created PeerReference=0x2d06/G IdentityHashCode=0x8edcb07 Instance=0x957d2a Instance.Type=Android.App.Application, Java.Type=my/MainApplication which at a glance seems correct, but isn't: the `Instance.Type` for a `Java.Type` of `my/MainApplication` should be `MainApplication`, *not* `Android.App.Application`! Because the runtime type of this value was `Application`, it was warranted and expected that `InvokeVirtualVoidMethod()` would do a virtual invocation! So, why did the avove `Created PeerReference …` line show the wrong type? Because `NativeAotTypeManager.CreatePeer()` needs to check for bindings of the the runtime type of the Java handle *before* using the `targetType` parameter, because the targetType parameter will *never* be for that of a custom subclass. Copy *lots* of code from dotnet/java-interop -- showing that this needs some major cleanup & refactoring -- so that we properly check the runtime type of `reference` + base classes when trying to determine the type of the proxy to create. This fixes the stack overflow.
Context: 78d5937 Context: dotnet/android@7a772f0 Context: dotnet/android#9716 Context: dotnet/android@694e975 dotnet/android@7a772f03 added the beginnings of a NativeAOT sample to dotnet/android which built a ".NET for Android" app using NativeAOT, which "rhymed with" the `Hello-NativeAOTFromAndroid` sample in 78d5937. Further work on the sample showed that it was lacking support for `Android.App.Application` subclasses. dotnet/android#9716 began fixing that oversight, but in the process was triggering a stack overflow because when it needed to create a "proxy" peer around the `my.MainApplication` Java type, which subclassed `android.app.Application`, instead of creating an instance of the expected `MainApplication` C# type, it instead created an instance of `Android.App.Application`. This was visible from the logs: Created PeerReference=0x2d06/G IdentityHashCode=0x8edcb07 Instance=0x957d2a Instance.Type=Android.App.Application, Java.Type=my/MainApplication Note that `Instance.Type` is `Android.App.Application`, not the in-sample `MainApplication` C# type. Because the runtime type was `Android.App.Application`, when we later attempted to dispatch the `Application.OnCreate()` override, this resulted in a *virtual* invocation of the Java `Application.onCreate()` method instead of a *non-virtual* invocation of `Application.onCreate()`. This virtual invocation was the root of a recursive loop which eventually resulted in a stack overflow. The fix in dotnet/android@694e975e was to fix `NativeAotTypeManager.CreatePeer()` so that it properly checked for a binding of the *runtime type* of the Java instance *before* using the "fallback" type provided to `Object.GetObject<T>()` in the `Application.n_OnCreate()` method: partial class Application { static void n_OnCreate (IntPtr jnienv, IntPtr native__this) { // … var __this = global::Java.Lang.Object.GetObject< Android.App.Application // This is the "fallback" NativeAotTypeManager > (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; __this.OnCreate (); // … } } All well and good. The problem is that `NativeAotTypeManager` in dotnet/android needs to support *both* dotnet/java-interop "activation constructors" with a signature of `(ref JniObjectReference, JniObjectReferenceOptions)`, *and* the .NET for Android signature of `(IntPtr, JniHandleOwnership)`. Trying to support both constructors resulted in the need to copy *all* of `JniRuntime.JniValueManager.CreatePeer()` *and dependencies*, which felt a bit excessive. Add a new `JniRuntime.JniValueManager.TryCreatePeer()` method, which will invoke the activation constructor to create an `IJavaPeerable`: partial class JniRuntime { partial class JniValueManager { protected virtual IJavaPeerable? TryCreatePeer ( ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType); } } If the activation constructor is not found, then `TryCreatePeer()` shall return `null`, allowing `CreatePeerInstance()` to try for a base type or, ultimately, the fallback type. This will allow a future dotnet/android PR to *remove* `NativeAotTypeManager.CreatePeer()` and its dependencies entirely, and instead do: partial class NativeAotTypeManager { const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; protected override IJavaPeerable TryCreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions options, Type type) { var c = type.GetConstructor (ActivationConstructorBindingFlags, null, XAConstructorSignature, null); if (c != null) { var args = new object[] { reference.Handle, JniHandleOwnership.DoNotTransfer, }; var p = (IJavaPeerable) c.Invoke (args); JniObjectReference.Dispose (ref reference, options); return p; } return base.TryCreatePeer (ref reference, options, type); } } vastly reducing the code it needs to care about.
Context: 78d5937 Context: be6cc8f Context: dotnet/android@7a772f0 Context: dotnet/android#9716 Context: dotnet/android@694e975 dotnet/android@7a772f03 added the beginnings of a NativeAOT sample to dotnet/android which built a ".NET for Android" app using NativeAOT, which "rhymed with" the `Hello-NativeAOTFromAndroid` sample in 78d5937. Further work on the sample showed that it was lacking support for `Android.App.Application` subclasses: [Application (Name = "my.MainApplication")] public class MainApplication : Application { public MainApplication (IntPtr handle, JniHandleOwnership transfer) : base (handle, transfer) { } public override void OnCreate () { base.OnCreate (); } } dotnet/android#9716 began fixing that oversight, but in the process was triggering a stack overflow because when it needed to create a "proxy" peer around the `my.MainApplication` Java type, which subclassed `android.app.Application`. Instead of creating an instance of the expected `MainApplication` C# type, it instead created an instance of `Android.App.Application`. This was visible from the logs: Created PeerReference=0x2d06/G IdentityHashCode=0x8edcb07 Instance=0x957d2a Instance.Type=Android.App.Application, Java.Type=my/MainApplication Note that `Instance.Type` is `Android.App.Application`, not the in-sample `MainApplication` C# type. Because the runtime type was `Android.App.Application`, when we later attempted to dispatch the `Application.OnCreate()` override, this resulted in a *virtual* invocation of the Java `Application.onCreate()` method instead of a *non-virtual* invocation of `Application.onCreate()`. This virtual invocation was the root of a recursive loop which eventually resulted in a stack overflow. The fix in dotnet/android@694e975e was to fix `NativeAotTypeManager.CreatePeer()` so that it properly checked for a binding of the *runtime type* of the Java instance *before* using the "fallback" type provided to `Object.GetObject<T>()` in the `Application.n_OnCreate()` method: partial class Application { static void n_OnCreate (IntPtr jnienv, IntPtr native__this) { // … var __this = global::Java.Lang.Object.GetObject< Android.App.Application // This is the "fallback" NativeAotTypeManager > (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!; __this.OnCreate (); // … } } All well and good. The problem is that `NativeAotTypeManager` in dotnet/android needs to support *both* dotnet/java-interop "activation constructors" with a signature of `(ref JniObjectReference, JniObjectReferenceOptions)`, *and* the .NET for Android signature of `(IntPtr, JniHandleOwnership)`. Trying to support both constructors resulted in the need to copy *all* of `JniRuntime.JniValueManager.CreatePeer()` *and dependencies*, which felt a bit excessive. Add a new `JniRuntime.JniValueManager.TryCreatePeer()` method, which will invoke the activation constructor to create an `IJavaPeerable`: partial class JniRuntime { partial class JniValueManager { protected virtual IJavaPeerable? TryCreatePeer ( ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType); } } If the activation constructor is not found, then `TryCreatePeer()` shall return `null`, allowing `CreatePeerInstance()` to try for a base type or, ultimately, the fallback type. This will allow a future dotnet/android to *remove* `NativeAotTypeManager.CreatePeer()` and its dependencies entirely, and instead do: partial class NativeAotTypeManager { const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; protected override IJavaPeerable TryCreatePeer (ref JniObjectReference reference, JniObjectReferenceOptions options, Type type) { var c = type.GetConstructor (ActivationConstructorBindingFlags, null, XAConstructorSignature, null); if (c != null) { var args = new object[] { reference.Handle, JniHandleOwnership.DoNotTransfer, }; var p = (IJavaPeerable) c.Invoke (args); JniObjectReference.Dispose (ref reference, options); return p; } return base.TryCreatePeer (ref reference, options, type); } } vastly reducing the code it needs to care about. Additionally, add `JniRuntime.JniTypeManager.GetInvokerType()`: partial class JniRuntime { partial class JniTypeManager { public Type? GetInvokerType (Type type); protected virtual Type? GetInvokerTypeCore (Type type); } } `GetInvokerType()` calls `GetInvokerTypeCore()`, allowing flexibility for subclasses to lookup the "string-based" the .NET for Android -style Invoker types that JavaInterop1-style bindings used before commit be6cc8f.
Draft commit message: [NativeAOT] Add support for `Application` subclasses
Context: https://github.com/xamarin/monodroid/commit/1a931e276ad38c28b944b83157573a55044c1be3
`android.app.Application` and `android.app.Instrumentation` are
["special"][0], in terms of app startup and type registration,
because MonoVM is initialized via a `ContentProvider`, which is
constructed *after* `Application` instance is constructed.
In broad terms:
1. `Application` instance is created.
(Specific type is via [`//application/@android:name`][1].)
2. `ContentProvider`s are created, including
`MonoRuntimeProvider` (MonoVM) or
`NativeAotRuntimeProvider` (NativeAOT).
3. `*RuntimeProvider.attachInfo()` invoked, provided (1).
4. `*RuntimeProvider.attachInfo()` does whatever runtime init is
required, e.g. MonoVM's `MonoRuntimeProvider` calls
`MonoPackageManager.LoadApplication()`.
We cannot dispatch Java `native` methods into managed code until
*after* (4) finishes. Meanwhile, we allow C# to subclass
`Android.App.Application` and override methods like
`Application.OnCreate()`:
[Application (Name = "my.MainApplication")]
public partial class MainApplication : Application
{
public override void OnCreate ()
{
base.OnCreate ();
}
}
How does that work?
It works via a leaky abstraction: unlike other types, the
Java Callable Wrapper (JCW) for `Application` and `Instrumentation`
subclasses lacks:
1. A `Runtime.register()` invocation in the static constructor, and
2. A call to `TypeManager.Activate()` in the instance constructor.
Compare an `Activity` JCW:
public /* partial */ class MainActivity extends android.app.Activity {
static {
__md_methods = "…";
mono.android.Runtime.register ("….MainActivity, …", MainActivity.class, __md_methods);
}
public MainActivity ()
{
super ();
if (getClass () == MainActivity.class) {
mono.android.TypeManager.Activate ("….MainActivity, …", "", this, new java.lang.Object[] { });
}
}
}
to an `Application` JCW:
public /* partial */ class MainApplication extends android.app.Application {
{
static {
// mostly empty
}}
public MainApplication ()
{
// Used to provide `Android.App.Application.Context` property
mono.MonoPackageManager.setContext (this);
// No `TypeManager.Activate(…)` call
}
}
Instead of a `Runtime.register()` invocation in the e.g.
`MainApplication` static constructor, `MainApplication` is instead
registered via `ApplicationRegistration.registerApplications()`, which is:
1. Generated via the `<GenerateJavaStubs/>` task, and
2. Invoked from `MonoPackageManager.LoadApplication()`.
"Later", when the Java `Application.onCreate()` method is invoked,
the `Application.n_OnCreate()` marshal method will call
`Java.Lang.Object.GetObject<Application>(…)`. This will look for
the `(IntPtr, JniHandleOwnership)` "activation constructor", which
must be present on the C# type:
public partial class MainApplication : Application
{
public MainApplication (IntPtr handle, JniHandleOwnership transfer)
: base (handle, transfer)
{
}
}
To support `Application` subclasses under NativeAOT, we need to
ensure that the `<GenerateJavaStubs/>` task creates
`ApplicationRegistration.java`, and update
`NativeAotRuntimeProvider` to invoke
`ApplicationRegistration.registerApplications()`. We also need
various changes to `NativeAotValueManager` so that we can create the
`MainApplication` "proxy" instance via the activation constructor.
Other changes:
* Change the package name for `ApplicationRegistration` from
`mono.android` to `net.dot.android`.
* Stop using `AndroidValueManager` and instead copy
[`ManagedValueManager`][2] into `NativeAotValueManager`.
* Allow `CodeGenerationTarget` to be explicitly provided to
`<GenerateJavaStubs/>`, instead of inferring it based on
`$(_AndroidRuntime)`.
[0]: https://learn.microsoft.com/en-us/previous-versions/xamarin/android/internals/architecture#java-activation
[1]: https://developer.android.com/guide/topics/manifest/application-element#nm
[2]: https://github.com/dotnet/java-interop/blob/dd3c1d0514addfe379f050627b3e97493e985da6/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs |
* main: [NativeAOT] Add support for `Application` subclasses (#9716) Bump to dotnet/sdk@d6bc7918c0 10.0.100-preview.2.25102.3 (#9726) [xa-prep-tasks] fix build errors for long `darc-` branch names (#9740)
This a step toward testing a .NET MAUI app, as it uses a custom
Android.App.Application
subclass.