-
Notifications
You must be signed in to change notification settings - Fork 533
Blueprint
.NET for Android (née Xamarin.Android) is a set of tooling to create Android apps in C#.
What are the various "pieces" of .NET for Android, and what are their relationships with each other?
The .NET for Android app build process adds a number of custom steps to the standard .net build process.
- Resolve and Compile Android Resources (aapt2 compile)
- Build a temp package for Resource designer generation (aapt2 link)
- Generate Java Wrappers for .net code which derive from Java Classes (GenerateJavaStubs)
- Compile all Java code (CompileJava)
- Convert Java .class files to classes.dex (CompileToDalvik)
- Resolve and Compile any updated Android Resources (aapt2 compile)
- Build final app package (aapt2 link)
- Add .NET for Android specific files to the final app package (BuildApk)
- Sign and Align final app package.
The .NET for Android binding process, broadly speaking, involves four steps:
-
Parse
.class
files -- present in.jar
and.aar
files -- into an XML description. Theclass-parse
utility is responsible for generating this XML description. -
(Optional) Parse Java Source Code, typically provided via the
@(JavaSourceJar)
build action, to extract parameter names and Javadoc documentation. Java.class
files often do not contain parameter names -- it requires usingjavac -parameters
, which isn't a default -- thus parsing Java source code is often the most reliable mechanism to obtain parameter names. Thejava-source-utils
utility is responsible for parsing Java source code. -
Consume the XML description, and generate a set of C# files which bind the types in (1). The .NET for Android
generator
utility is responsible for this.generator
can also consume the output ofjava-source-utils
, and can attempt to convert extracted Javadoc comments into C# XML Documentation comments. -
Compile the C# from (3) into the final assembly.
class-parse
and generator
are implicitly used as part of the .NET for Android @(EmbeddedJar)
and @(InputJar)
build action within Binding projects, and in .NET for Android via the @(AndroidLibrary)
build action, when %(AndroidLibrary.Bind)
=True.
See also:
- https://learn.microsoft.com/xamarin/android/platform/binding-java-library/
- https://learn.microsoft.com/xamarin/android/deploy-test/building-apps/build-items#androidlibrary
Java Type Registration requires that On-Android Process Startup has completed, so that mono.android.Runtime
is initialized and usable. This means that Java types which are instantiated before our Content Provider is executed cannot participate in "normal" Java Type Registration. This constraint is primarily limited to Application
subclasses, support for which will be detailed "later".
During the App Build Process, Java Callable Wrappers are created. Among other things, the Java Callable Wrapper class constructor contains a Runtime.register()
invocation, which lists all Java native
methods which need to be registered:
/* partial */ class MainActivity {
static {
__md_methods =
"n_onCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n" +
"";
mono.android.Runtime.register ("HelloWorld.MainActivity, HelloWorld", MainActivity.class, __md_methods);
}
private native void n_onCreate (android.os.Bundle p0);
}
Runtime.register()
is Java_mono_android_Runtime_register()
, which eventually invokes JNIEnv. RegisterJniNatives()
and AndroidTypeManager.RegisterNativeMembers()
. AndroidTypeManager.RegisterNativeMembers()
is in turn an unholy mess of string splitting and Reflection: the __md_methods
value from the Java Callable Wrapper is split on \n
, one line per method to register, and each line is in turn split on :
, to extract:
- The Java
native
method name to register. - The JNI signature of the method to register.
- A "Locator" for where to get a Delegate which contains the marshal method to register.
- (Optional) the type that the Locator method is declared on. (Only used if the method was declared on an interface.)
This process in turn relies on internal implementation details of generator
output: the Locator method must exist either in the type (or base type) of the Managed type being registered -- HelloWorld.MainActivity, HelloWorld
, for the above example -- or must exist in the explicit 4th parameter. The Locator method must also be:
static
- Return
System.Delegate
- Take no parameters.
For example:
namespace Android.App {
public partial class Activity {
static Delegate? cb_onCreate_Landroid_os_Bundle_;
#pragma warning disable 0169
static Delegate GetOnCreate_Landroid_os_Bundle_Handler ()
{
if (cb_onCreate_Landroid_os_Bundle_ == null)
cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate ((_JniMarshal_PPL_V) n_OnCreate_Landroid_os_Bundle_);
return cb_onCreate_Landroid_os_Bundle_;
}
static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState)
{
var __this = global::Java.Lang.Object.GetObject<Android.App.Activity> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
var savedInstanceState = global::Java.Lang.Object.GetObject<Android.OS.Bundle> (native_savedInstanceState, JniHandleOwnership.DoNotTransfer);
__this.OnCreate (savedInstanceState);
}
#pragma warning restore 0169
}
}
Once all the Java native
methods to register have been found, along with their corresponding Locator methods, the Locator methods are invoked (via Reflection), and the resulting value stored into a JniNativeMethodRegistration
array, which is used to invoke JNIEnv::RegisterNatives()
.
Once this has been completed, Java code can invoke the Java native
methods, which will cause the marshal method to be invoked (e.g. Activity.n_OnCreate_Landroid_os_Bundle_()
), which will cause the Managed override
method to be invoked (e.g. MainActivity.OnCreate()
).
See also:
- https://learn.microsoft.com/xamarin/android/platform/java-integration/android-callable-wrappers
- https://github.com/xamarin/java.interop/tree/main/tools/jnimarshalmethod-gen
Primary source code locations:
- https://github.com/xamarin/java.interop/tree/main/src/Java.Interop.Tools.JavaCallableWrappers
- https://github.com/dotnet/android/blob/main/src/java-runtime/java/mono/android/Runtime.java
- https://github.com/dotnet/android/blob/main/src/monodroid/jni/monodroid-glue.cc
- https://github.com/xamarin/java.interop/blob/main/src/Java.Interop/Java.Interop/JniNativeMethodRegistration.cs
- https://github.com/dotnet/android/blob/main/src/Mono.Android/Android.Runtime/JNIEnv.cs
- https://github.com/dotnet/android/blob/main/src/Mono.Android/Android.Runtime/AndroidRuntime.cs
TODO: Determine of the App Startup AndroidX Library should change how we do things.
TODO: Find some sources to elaborate on the "pre-.NET startup" bits.
When the user taps on an App icon on an Android device, the Activity
associated with that App is looked up. If a process for that Activity
doesn't already exist, a process will be created (somewhere) in Zygote.java
, an Application
singleton instance is created based on the contents of //application/@android:name
attribute within AndroidManifest.xml
, and eventually all relevant <provider/>
s will be constructed.
This is where .NET for Android enters the picture: during the App Build Process, a <provider/>
is always added to AndroidManifest.xml
, one per detected process name:
<provider android:name="mono.MonoRuntimeProvider" android:exported="false" android:initOrder="1999999999" android:authorities="com.companyname.helloworld.mono.MonoRuntimeProvider.__mono_init__" />
This XML fragment causes MonoRuntimeProvider
to be instantiated, and the Content Provider lifecycle to be started. We don't use <provider/>
to actually add a Content Provider; we use it because this was the earliest point we could find during Android process startup through which we could execute code.
When MonoRuntimeProvider.attachInfo()
is invoked by Android, .NET for Android has an opportunity to load Mono. This is done via MonoPackageManager.LoadApplication()
, which is the primary Java-side startup code path.
MonoPackageManager.LoadApplication()
loads various native libraries, including libmonodroid.so
. Once libmonodroid.so
has been loaded, mono.android.Runtime
methods can be invoked, in particular Runtime.initInternal()
. Through the glory of JNI native method resolution, Runtime.initInternal()
is resolved as the C symbol Java_mono_android_Runtime_initInternal()
.
Runtime.initInternal()
is responsible for preparing the process for managed code execution:
- Initialize MonoVM
- Find assemblies included with the App
- etc., etc.
Once MonoVM has been initialized, Mono.Android.dll
is loaded, then JNIEnv.Initialize()
is invoked. This gives Mono.Android.dll
a chance to perform initialization.
Once Runtime.initInternal()
completes, MonoPackageManager.attachInfo()
returns, and normal Android app startup resumes.
Primary source code locations:
- https://github.com/dotnet/android/blob/main/src/Xamarin.Android.Build.Tasks/Resources/MonoRuntimeProvider.Bundled.java
- https://github.com/dotnet/android/blob/main/src/java-runtime/java/mono/android/MonoPackageManager.java
- https://github.com/dotnet/android/tree/main/src/monodroid/jni
- https://github.com/dotnet/android/blob/main/src/monodroid/jni/monodroid-glue.cc
- https://github.com/dotnet/android/blob/main/src/java-runtime/java/mono/android/Runtime.java
- https://github.com/dotnet/android/blob/main/src/Mono.Android/Android.Runtime/JNIEnv.cs
The pieces which make of .NET for Android include:
"Glue" code for running apps on Android device, process startup, etc.
Primary source code locations are:
- https://github.com/dotnet/android/tree/main/src/java-runtime
- https://github.com/dotnet/android/tree/main/src/monodroid
- https://github.com/dotnet/android/blob/main/src/Mono.Android/Android.Runtime/JNIEnv.cs
- https://github.com/dotnet/android/blob/main/src/Mono.Android/Android.Runtime/AndroidRuntime.cs
class-parse
parses .class
and .jar
files, and produces an XML description of the Java APIs found.
Primary source code locations are:
- https://github.com/xamarin/java.interop/tree/main/src/Xamarin.AndroidTools.Bytecode
- https://github.com/xamarin/Java.Interop/tree/main/tools/class-parse
Create C# bindings from XML description.
Also can optionally attempt to import Javadoc documentation, as imported from java-source-utils
, and convert into C# XML Documentation Comments.
Primary source code locations are:
- https://github.com/xamarin/java.interop/tree/main/src/Java.Interop.Tools.Generator
- https://github.com/xamarin/java.interop/tree/main/src/Xamarin.SourceWriter
- https://github.com/xamarin/java.interop/tree/main/tools/generator
Quickly update existing app installs on an Android device, for quick dev inner loops. Part of .NET for Android build tasks.
https://github.com/xamarin/monodroid#commercial-xamarinandroid-dependencies
Create Java “stub” code to permit Java-to-managed method invocations. Required for on-device app use. Part of Xamarin.AndroidBuild.Tasks.
Docs:
Primary source code locations are:
Java source code parser, using com.github.javaparser
.
Exists to improve Java Library bindings in .NET for Android. Bindings in .NET for Android frequently hit two common pitfalls:
- Lack of parameter names.
- Lack of documentation.
Java method parameter names are useful in-and-of-themselves. However, parameter names are also used when turning *Listener
interfaces into events, wherein each method parameter becomes an *EventArgs
property. See also: Events and Listeners in API Design.
The problem with Java method parameter names is that by default, Java .class
files don't contain parameter names. Java .class
files can contain parameter names, but only if javac -parameters
was used to compile the Java source code, which frequently is not the case. If parameter names aren't present, then names such as p0
are used, which results in very ugly *EventArgs
types.
Additionally, Java libraries frequently provide documentation, in the form of Javadoc documentation within Java source code.
java-source-utils
parses Java source code, extracting parameter names and Javadoc documentation. The extracted information can then be used by the .NET for Android generator
utility to provide parameter name information. generator
can also attempt to convert the extracted Javadoc documentation into C# XML Documentation Comments.
See also:
- https://github.com/xamarin/java.interop/commit/69e1b80afd81ac502494e4bc2a2de75f5171c73f
- https://github.com/xamarin/java.interop/commit/7574f166008bb45c0df97315aae7907ac25f8602
java-source-utils
is used by .NET for Android, via the @(JavaSourceJar)
build item.
Primary source code locations are:
Core binding for the Android API.
Primary source code locations are:
Runtime support for the [Export]
custom attribute.
Primary source code locations are:
"Glue" for creating Android .apk
and .aab
files. Provides MSBuild tasks and targets and wrappers for various Android SDK toolchain apps. ("You are in a maze of twisted MSBuild, all alike.")
Primary source code locations are:
- https://github.com/dotnet/android/tree/main/src/aapt2
- https://github.com/dotnet/android/tree/main/src/apksigner
- https://github.com/dotnet/android/tree/main/src/bundletool
- https://github.com/dotnet/android/tree/main/src/manifestmerger
- https://github.com/dotnet/android/tree/main/src/proguard
- https://github.com/dotnet/android/tree/main/src/r8
- https://github.com/dotnet/android/tree/main/src/Xamarin.AndroidBuild.Tasks
- https://github.com/dotnet/android/tree/main/src/Xamarin.AndroidTools.Aidl
- https://github.com/dotnet/android/tree/main/src/Xamarin.AndroidTools.JavadocImporter
- …
- APK Tests on the Hyper V Emulator
- Design Time Build System
- Profile MSBuild Tasks
- Diagnose Fast Deployment Issues
- Preview layout XML files with Android Studio
- Documentation