Aufbau

Xamarin.Android-Anwendungen werden in der Mono-Ausführungsumgebung ausgeführt. Diese Ausführungsumgebung wird parallel mit dem virtuellen Computer für Android Runtime (ART) ausgeführt. Beide Laufzeitumgebungen werden auf dem Linux-Kernel ausgeführt und machen verschiedene APIs für den Benutzercode verfügbar, sodass Entwickler auf das zugrunde liegende System zugreifen können. Die Mono-Runtime ist in der Sprache C geschrieben.

Sie können system,System.IO, System.Net und die restlichen .NET-Klassenbibliotheken verwenden, um auf die zugrunde liegenden Linux-Betriebssystemfunktionen zuzugreifen.

Unter Android stehen die meisten Systemfunktionen wie Audio, Grafik, OpenGL und Telefonie nicht direkt für native Anwendungen zur Verfügung, sie werden nur über die Android-Runtime-Java-APIs verfügbar gemacht, die sich in einem der Java.*-Namespaces oder den Android.*-Namespaces befinden. Die Architektur sieht ungefähr wie folgt aus:

Diagramm von Mono und ART oberhalb des Kernels und unterhalb von .NET/Java + Bindungen

Xamarin.Android-Entwickler greifen auf die verschiedenen Features im Betriebssystem zu, indem sie entweder die ihnen bekannten .NET-APIs aufrufen (für Zugriff auf niedriger Ebene) oder die Klassen verwenden, die in den Android-Namespaces verfügbar gemacht werden, wodurch eine Brücke zu den Java-APIs bereitgestellt wird, die von der Android-Runtime verfügbar gemacht werden.

Weitere Informationen zur Kommunikation der Android-Klassen mit den Android-Runtime-Klassen finden Sie im Dokument API-Entwurf .

Anwendungspakete

Android-Anwendungspakete sind ZIP-Container mit der Dateierweiterung APK . Xamarin.Android-Anwendungspakete verfügen über die gleiche Struktur und das gleiche Layout wie normale Android-Pakete, mit den folgenden Ergänzungen:

  • Die Anwendungsassemblys (mit IL) werden unkomprimiert im Ordner assemblysgespeichert. Während des Prozessstarts in Releasebuilds wird die APK-Datei mmap() in den Prozess eingefügt, und die Assemblys werden aus dem Arbeitsspeicher geladen. Dies ermöglicht einen schnelleren App-Start, da Assemblys vor der Ausführung nicht extrahiert werden müssen.

  • Hinweis: Informationen zum Assemblyspeicherort wie Assembly.Location und Assembly.CodeBase können in Releasebuilds nicht verwendet werden. Sie sind nicht als unterschiedliche Dateisystemeinträge vorhanden, und sie haben keinen verwendbaren Speicherort.

  • Native Bibliotheken, die die Mono-Runtime enthalten, sind in der APK vorhanden. Eine Xamarin.Android-Anwendung muss native Bibliotheken für die gewünschten/zielorientierten Android-Architekturen enthalten, z. B. armeabi , armeabi-v7a , x86 . Xamarin.Android-Anwendungen können nicht auf einer Plattform ausgeführt werden, es sei denn, sie enthält die entsprechenden Laufzeitbibliotheken.

Xamarin.Android-Anwendungen enthalten auch Android Callable Wrapper , damit Android verwalteten Code aufrufen kann.

Android Callable Wrapper

  • Aufrufbare Android-Wrapper sind eine JNI-Brücke , die immer verwendet wird, wenn die Android-Runtime verwalteten Code aufrufen muss. Mit aufrufbaren Android-Wrappern können virtuelle Methoden überschrieben und Java-Schnittstellen implementiert werden. Weitere Informationen finden Sie im Dokument Übersicht über die Java-Integration .

Managed Callable Wrappers

Verwaltete aufrufbare Wrapper sind eine JNI-Brücke, die immer verwendet wird, wenn verwalteter Code Android-Code aufrufen muss und Unterstützung für das Überschreiben virtueller Methoden und die Implementierung von Java-Schnittstellen bietet. Die gesamten Android.*-Namespaces und die zugehörigen Namespaces sind verwaltete aufrufbare Wrapper, die über die JAR-Bindung generiert werden. Verwaltete aufrufbare Wrapper sind für die Konvertierung zwischen verwalteten und Android-Typen und das Aufrufen der zugrunde liegenden Android-Plattformmethoden über JNI verantwortlich.

Jeder erstellte verwaltete aufrufbare Wrapper enthält einen globalen Java-Verweis, auf den über die Android.Runtime.IJavaObject.Handle-Eigenschaft zugegriffen werden kann. Globale Verweise werden verwendet, um die Zuordnung zwischen Java-Instanzen und verwalteten Instanzen bereitzustellen. Globale Verweise sind eine begrenzte Ressource: Emulatoren lassen nur 2.000 globale Verweise gleichzeitig zu, während die meisten Hardware es zulässt, dass mehr als 52.000 globale Verweise gleichzeitig vorhanden sind.

Um nachzuverfolgen, wann globale Verweise erstellt und zerstört werden, können Sie festlegen, dass die Debug.mono.log-Systemeigenschaftgref enthält.

Globale Verweise können explizit freigegeben werden, indem Java.Lang.Object.Dispose() für den verwalteten aufrufbaren Wrapper aufgerufen wird. Dadurch wird die Zuordnung zwischen dem Java-instance und dem verwalteten instance entfernt, und die Java-instance können gesammelt werden. Wenn von verwaltetem Code aus erneut auf die Java-instance zugegriffen wird, wird dafür ein neuer verwalteter aufrufbarer Wrapper erstellt.

Beim Entfernen von verwalteten aufrufbaren Wrappern muss Vorsicht walten lassen, wenn die instance versehentlich zwischen Threads freigegeben werden können, da sich das Entfernen der instance auf Verweise aus anderen Threads auswirkt. Für maximale Sicherheit weisen nur Dispose() Instanzen, die über newoder von Methoden zugeordnet wurden, von denen Sie wissen, immer neue Instanzen zu und nicht zwischengespeicherte Instanzen, was zu versehentlicher instance Freigabe zwischen Threads führen kann.

Verwaltete Aufrufbare Wrapper-Unterklassen

Verwaltete aufrufbare Wrapper-Unterklassen sind der Ort, an dem sich die gesamte "interessante" anwendungsspezifische Logik befinden kann. Dazu gehören benutzerdefinierte Android.App.Activity-Unterklassen (z. B. der Typ Activity1 in der Standardprojektvorlage). (Dies sind insbesondere java.Lang.Object-Unterklassen , die keinbenutzerdefiniertes RegisterAttribute-Attribut enthalten, oder RegisterAttribute.DoNotGenerateAcw ist false, was der Standardwert ist.)

Wie verwaltete aufrufbare Wrapper enthalten auch verwaltete aufrufbare Wrapperunterklassen einen globalen Verweis, auf den über die Java.Lang.Object.Handle-Eigenschaft zugegriffen werden kann. Genau wie bei verwalteten aufrufbaren Wrappern können globale Verweise explizit durch Aufrufen von Java.Lang.Object.Dispose() freigegeben werden. Im Gegensatz zu verwalteten aufrufbaren Wrappern sollte vor dem Löschen solcher Instanzen mit großer Sorgfalt bedacht werden, da Dispose()-ing der instance die Zuordnung zwischen dem Java-instance (einem instance eines Android Callable Wrappers) und dem verwalteten instance.

Java-Aktivierung

Wenn ein Android Callable Wrapper (ACW) aus Java erstellt wird, bewirkt der ACW-Konstruktor, dass der entsprechende C#-Konstruktor aufgerufen wird. Beispielsweise enthält der ACW für MainActivity einen Standardkonstruktor, der den Standardkonstruktor von MainActivity aufruft. (Dies erfolgt über den TypeManager.Activate() -Aufruf innerhalb der ACW-Konstruktoren.)

Es gibt eine weitere Konstruktorsignatur der Folge: den (IntPtr, JniHandleOwnership) -Konstruktor. Der Konstruktor (IntPtr, JniHandleOwnership) wird immer dann aufgerufen, wenn ein Java-Objekt für verwalteten Code verfügbar gemacht wird und ein managed Callable Wrapper erstellt werden muss, um das JNI-Handle zu verwalten. Dies erfolgt in der Regel automatisch.

Es gibt zwei Szenarien, in denen der Konstruktor (IntPtr, JniHandleOwnership) manuell für eine Verwaltete aufrufbare Wrapper-Unterklasse bereitgestellt werden muss:

  1. Android.App.Application ist unterklassiert. Die Anwendung ist etwas Besonderes; Der Standardmäßige Applicaton-Konstruktor wird nie aufgerufen, und stattdessen muss der Konstruktor (IntPtr, JniHandleOwnership) bereitgestellt werden.

  2. Aufruf virtueller Methoden aus einem Basisklassenkonstruktor.

Beachten Sie, dass (2) eine undichte Abstraktion ist. In Java, wie in C#, rufen Aufrufe von virtuellen Methoden von einem Konstruktor immer die am meisten abgeleitete Methodenimplementierung auf. Der TextView(Context, AttributeSet, int)-Konstruktor ruft beispielsweise die virtuelle Methode TextView.getDefaultMovementMethod() auf, die als TextView.DefaultMovementMethod-Eigenschaft gebunden ist. Wenn also ein LogTextBox-Typ (1) die Unterklasse TextView, (2) TextView.DefaultMovementMethod überschreibt und (3) eine instance dieser Klasse über XML aktivieren würde, würde die überschriebene DefaultMovementMethod-Eigenschaft aufgerufen, bevor der ACW-Konstruktor eine Chance zur Ausführung hatte, und sie würde auftreten, bevor der C#-Konstruktor ausgeführt werden kann.

Dies wird unterstützt, indem ein instance LogTextBox über den LogTextView(IntPtr, JniHandleOwnership)-Konstruktor instanziiert wird, wenn das ACW LogTextBox-instance zuerst verwalteten Code eingibt, und dann den LogTextBox(Context, IAttributeSet, int)-Konstruktor auf demselben instance aufrufen, wenn der ACW-Konstruktor ausgeführt wird.

Reihenfolge der Ereignisse:

  1. Layout-XML wird in eine ContentView geladen.

  2. Android instanziiert das Layout-Objektdiagramm und instanziiert eine instance von monodroid.apidemo.LogTextBox , dem ACW für LogTextBox .

  3. Der monodroid.apidemo.LogTextBox-Konstruktor führt den android.widget.TextView-Konstruktor aus.

  4. Der TextView-Konstruktor ruft monodroid.apidemo.LogTextBox.getDefaultMovementMethod() auf .

  5. monodroid.apidemo.LogTextBox.getDefaultMovementMethod() ruft LogTextBox.n_getDefaultMovementMethod() auf, das TextView.n_GetDefaultMovementMethod() aufruft, das Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer) aufruft.

  6. Java.Lang.Object.GetObject<TextView>() überprüft, ob bereits eine entsprechende C#-instance für handle vorhanden ist. Wenn vorhanden, wird sie zurückgegeben. In diesem Szenario ist dies nicht der Fall, sodass Object.GetObject<T>() eine erstellen muss.

  7. Object.GetObject<T>() sucht nach dem LogTextBox(IntPtr, JniHandleOwneship)-Konstruktor, ruft ihn auf, erstellt eine Zuordnung zwischen dem Handle und dem erstellten instance und gibt die erstellte instance zurück.

  8. TextView.n_GetDefaultMovementMethod() ruft den Getter der LogTextBox.DefaultMovementMethod-Eigenschaft auf.

  9. Das Steuerelement kehrt zum android.widget.TextView-Konstruktor zurück, der die Ausführung beendet.

  10. Der monodroid.apidemo.LogTextBox-Konstruktor wird ausgeführt, wobei TypeManager.Activate() aufgerufen wird.

  11. Der LogTextBox(Context, IAttributeSet, int)-Konstruktor wird auf demselben instance ausgeführt, der in (7) erstellt wurde.

  12. Wenn der Konstruktor (IntPtr, JniHandleOwnership) nicht gefunden werden kann, wird eine System.MissingMethodException](xref:System.MissingMethodException) ausgelöst.

Vorzeitige Dispose()-Aufrufe

Es gibt eine Zuordnung zwischen einem JNI-Handle und dem entsprechenden C#-instance. Java.Lang.Object.Dispose() unterbricht diese Zuordnung. Wenn ein JNI-Handle verwalteten Code eingibt, nachdem die Zuordnung unterbrochen wurde, sieht es wie die Java-Aktivierung aus, und der Konstruktor (IntPtr, JniHandleOwnership) wird überprüft und aufgerufen. Wenn der Konstruktor nicht vorhanden ist, wird eine Ausnahme ausgelöst.

Beispiel: Die folgende Managed Callable Wraper-Unterklasse:

class ManagedValue : Java.Lang.Object {

    public string Value {get; private set;}

    public ManagedValue (string value)
    {
        Value = value;
    }

    public override string ToString ()
    {
        return string.Format ("[Managed: Value={0}]", Value);
    }
}

Wenn wir einen instance erstellen, entsorgen() sie, und lassen Sie den verwalteten aufrufbaren Wrapper neu erstellen:

var list = new JavaList<IJavaObject>();
list.Add (new ManagedValue ("value"));
list [0].Dispose ();
Console.WriteLine (list [0].ToString ());

Das Programm wird sterben:

E/mono    ( 2906): Unhandled Exception: System.NotSupportedException: Unable to activate instance of type Scratch.PrematureDispose.ManagedValue from native handle 4051c8c8 --->
System.MissingMethodException: No constructor found for Scratch.PrematureDispose.ManagedValue::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateProxy (System.Type type, IntPtr handle, JniHandleOwnership transfer) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   --- End of inner exception stack trace ---
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object.GetObject (IntPtr handle, JniHandleOwnership transfer, System.Type type) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object._GetObject[IJavaObject] (IntPtr handle, JniHandleOwnership transfer) [0x00000

Wenn die Unterklasse einen (IntPtr, JniHandleOwnership)-Konstruktor enthält, wird eine neue instance des Typs erstellt. Infolgedessen scheint die instance alle instance Daten zu "verlieren", da es sich um eine neue instance handelt. (Beachten Sie, dass der Wert NULL ist.)

I/mono-stdout( 2993): [Managed: Value=]

Nur Dispose() von verwalteten aufrufbaren Wrapperunterklassen, wenn Sie wissen, dass das Java-Objekt nicht mehr verwendet wird oder die Unterklasse keine instance Daten enthält und ein (IntPtr, JniHandleOwnership)-Konstruktor bereitgestellt wurde.

Anwendungsstart

Wenn eine Aktivität, ein Dienst usw. wird gestartet, Android überprüft zunächst, ob bereits ein Prozess ausgeführt wird, um die Aktivität/den Dienst/etc. zu hosten. Wenn kein solcher Prozess vorhanden ist, wird ein neuer Prozess erstellt, die AndroidManifest.xml gelesen, und der im /manifest/application/@android:name-Attribut angegebene Typ wird geladen und instanziiert. Als Nächstes werden alle Typen, die von den Attributwerten /manifest/application/provider/@android:name angegeben werden, instanziiert und ihre ContentProvider.attachInfo%28) -Methode aufgerufen. Xamarin.Android fügt ein Mono hinzu. MonoRuntimeProviderContentProvider , der während des Buildprozesses AndroidManifest.xml. Der Mono. Die MonoRuntimeProvider.attachInfo()- Methode ist für das Laden der Mono-Runtime in den Prozess verantwortlich. Alle Versuche, Mono vor diesem Punkt zu verwenden, schlagen fehl. ( Hinweis: Aus diesem Grund müssen Typen, die die Unterklasse Android.App.Application benötigen, einen (IntPtr, JniHandleOwnership)-Konstruktor bereitstellen, da die Application instance erstellt wird, bevor Mono initialisiert werden kann.)

Nach Abschluss der Prozessinitialisierung wird gefragt, AndroidManifest.xml um den Klassennamen der zu startenden Aktivität/Des-Diensts/etc. zu ermitteln. Beispielsweise wird das Attribut /manifest/application/activity/@android:name verwendet, um den Namen einer zu ladenden Aktivität zu bestimmen. Für Aktivitäten muss dieser Typ android.app.Activity erben. Der angegebene Typ wird über Class.forName() geladen (was erfordert, dass der Typ ein Java-Typ sein muss, daher die Android Callable Wrappers), und dann instanziiert. Das Erstellen eines aufrufbaren Android-Wrappers instance löst die Erstellung einer instance des entsprechenden C#-Typs aus. Android ruft dann Activity.onCreate(Bundle) auf, was dazu führt, dass die entsprechende Activity.OnCreate(Bundle) aufgerufen wird, und Sie sind auf dem Weg zu den Rassen.