Skip to content

Commit

Permalink
[NativeAOT] Add support for Application subclasses (#9716)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jonathanpeppers authored Feb 3, 2025
1 parent f3ef4fe commit dbb0b92
Show file tree
Hide file tree
Showing 15 changed files with 481 additions and 28 deletions.
5 changes: 3 additions & 2 deletions samples/NativeAOT/JavaInteropRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ static void JNI_OnUnload (IntPtr vm, IntPtr reserved)
static void init (IntPtr jnienv, IntPtr klass)
{
try {
var typeManager = new NativeAotTypeManager ();
var options = new JreRuntimeOptions {
EnvironmentPointer = jnienv,
TypeManager = new NativeAotTypeManager (),
ValueManager = new NativeAotValueManager (),
TypeManager = typeManager,
ValueManager = new NativeAotValueManager (typeManager),
UseMarshalMemberBuilder = false,
JniGlobalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:GREF"),
JniLocalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:LREF"),
Expand Down
7 changes: 5 additions & 2 deletions samples/NativeAOT/MainActivity.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
using Android.Runtime;
using Android.Util;
using System.Reflection;
using System.Runtime.InteropServices;

namespace NativeAOT;

[Register("my/MainActivity")] // Required for typemap in NativeAotTypeManager
[Activity(Label = "@string/app_name", MainLauncher = true)]
// Name required for typemap in NativeAotTypeManager
[Activity (Label = "@string/app_name", MainLauncher = true, Name = "my.MainActivity")]
public class MainActivity : Activity
{
protected override void OnCreate(Bundle? savedInstanceState)
{
Log.Debug ("NativeAOT", "MainActivity.OnCreate()");

base.OnCreate(savedInstanceState);

// Set our view from the "main" layout resource
Expand Down
23 changes: 23 additions & 0 deletions samples/NativeAOT/MainApplication.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Android.Runtime;
using Android.Util;

/// <summary>
/// NOTE: This class is not required, but used for testing Android.App.Application subclasses.
/// Name required for typemap in NativeAotTypeManager
/// </summary>
[Application (Name = "my.MainApplication")]
public class MainApplication : Application
{
public MainApplication (IntPtr handle, JniHandleOwnership transfer)
: base (handle, transfer)
{
Log.Debug ("NativeAOT", $"Application..ctor({handle.ToString ("x2")}, {transfer})");
}

public override void OnCreate ()
{
Log.Debug ("NativeAOT", "Application.OnCreate()");

base.OnCreate ();
}
}
2 changes: 2 additions & 0 deletions samples/NativeAOT/NativeAotRuntimeProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public boolean onCreate() {
public void attachInfo(android.content.Context context, android.content.pm.ProviderInfo info) {
Log.d(TAG, "NativeAotRuntimeProvider.attachInfo(): calling JavaInteropRuntime.init()…");
JavaInteropRuntime.init();
// NOTE: only required for custom applications
net.dot.android.ApplicationRegistration.registerApplications();
super.attachInfo (context, info);
}

Expand Down
2 changes: 2 additions & 0 deletions samples/NativeAOT/NativeAotTypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ partial class NativeAotTypeManager : JniRuntime.JniTypeManager {
// TODO: list of types specific to this application
Dictionary<string, Type> typeMappings = new () {
["android/app/Activity"] = typeof (Android.App.Activity),
["android/app/Application"] = typeof (Android.App.Application),
["android/content/Context"] = typeof (Android.Content.Context),
["android/content/ContextWrapper"] = typeof (Android.Content.ContextWrapper),
["android/os/BaseBundle"] = typeof (Android.OS.BaseBundle),
["android/os/Bundle"] = typeof (Android.OS.Bundle),
["android/view/ContextThemeWrapper"] = typeof (Android.Views.ContextThemeWrapper),
["my/MainActivity"] = typeof (MainActivity),
["my/MainApplication"] = typeof (MainApplication),
};

public NativeAotTypeManager ()
Expand Down
Loading

0 comments on commit dbb0b92

Please sign in to comment.