Skip to content

Commit dbb0b92

Browse files
[NativeAOT] Add support for Application subclasses (#9716)
Context: xamarin/monodroid@1a931e2 `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
1 parent f3ef4fe commit dbb0b92

File tree

15 files changed

+481
-28
lines changed

15 files changed

+481
-28
lines changed

samples/NativeAOT/JavaInteropRuntime.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ static void JNI_OnUnload (IntPtr vm, IntPtr reserved)
3434
static void init (IntPtr jnienv, IntPtr klass)
3535
{
3636
try {
37+
var typeManager = new NativeAotTypeManager ();
3738
var options = new JreRuntimeOptions {
3839
EnvironmentPointer = jnienv,
39-
TypeManager = new NativeAotTypeManager (),
40-
ValueManager = new NativeAotValueManager (),
40+
TypeManager = typeManager,
41+
ValueManager = new NativeAotValueManager (typeManager),
4142
UseMarshalMemberBuilder = false,
4243
JniGlobalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:GREF"),
4344
JniLocalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:LREF"),

samples/NativeAOT/MainActivity.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
using Android.Runtime;
2+
using Android.Util;
23
using System.Reflection;
34
using System.Runtime.InteropServices;
45

56
namespace NativeAOT;
67

7-
[Register("my/MainActivity")] // Required for typemap in NativeAotTypeManager
8-
[Activity(Label = "@string/app_name", MainLauncher = true)]
8+
// Name required for typemap in NativeAotTypeManager
9+
[Activity (Label = "@string/app_name", MainLauncher = true, Name = "my.MainActivity")]
910
public class MainActivity : Activity
1011
{
1112
protected override void OnCreate(Bundle? savedInstanceState)
1213
{
14+
Log.Debug ("NativeAOT", "MainActivity.OnCreate()");
15+
1316
base.OnCreate(savedInstanceState);
1417

1518
// Set our view from the "main" layout resource

samples/NativeAOT/MainApplication.cs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Android.Runtime;
2+
using Android.Util;
3+
4+
/// <summary>
5+
/// NOTE: This class is not required, but used for testing Android.App.Application subclasses.
6+
/// Name required for typemap in NativeAotTypeManager
7+
/// </summary>
8+
[Application (Name = "my.MainApplication")]
9+
public class MainApplication : Application
10+
{
11+
public MainApplication (IntPtr handle, JniHandleOwnership transfer)
12+
: base (handle, transfer)
13+
{
14+
Log.Debug ("NativeAOT", $"Application..ctor({handle.ToString ("x2")}, {transfer})");
15+
}
16+
17+
public override void OnCreate ()
18+
{
19+
Log.Debug ("NativeAOT", "Application.OnCreate()");
20+
21+
base.OnCreate ();
22+
}
23+
}

samples/NativeAOT/NativeAotRuntimeProvider.java

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public boolean onCreate() {
2121
public void attachInfo(android.content.Context context, android.content.pm.ProviderInfo info) {
2222
Log.d(TAG, "NativeAotRuntimeProvider.attachInfo(): calling JavaInteropRuntime.init()…");
2323
JavaInteropRuntime.init();
24+
// NOTE: only required for custom applications
25+
net.dot.android.ApplicationRegistration.registerApplications();
2426
super.attachInfo (context, info);
2527
}
2628

samples/NativeAOT/NativeAotTypeManager.cs

+2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ partial class NativeAotTypeManager : JniRuntime.JniTypeManager {
1313
// TODO: list of types specific to this application
1414
Dictionary<string, Type> typeMappings = new () {
1515
["android/app/Activity"] = typeof (Android.App.Activity),
16+
["android/app/Application"] = typeof (Android.App.Application),
1617
["android/content/Context"] = typeof (Android.Content.Context),
1718
["android/content/ContextWrapper"] = typeof (Android.Content.ContextWrapper),
1819
["android/os/BaseBundle"] = typeof (Android.OS.BaseBundle),
1920
["android/os/Bundle"] = typeof (Android.OS.Bundle),
2021
["android/view/ContextThemeWrapper"] = typeof (Android.Views.ContextThemeWrapper),
2122
["my/MainActivity"] = typeof (MainActivity),
23+
["my/MainApplication"] = typeof (MainApplication),
2224
};
2325

2426
public NativeAotTypeManager ()

0 commit comments

Comments
 (0)