Architecture

Les applications Xamarin.Android s’exécutent dans l’environnement d’exécution Mono. Cet environnement d’exécution s’exécute côte à côte avec la machine virtuelle Android Runtime (ART). Les deux environnements d’exécution s’exécutent sur le noyau Linux et exposent diverses API au code utilisateur qui permet aux développeurs d’accéder au système sous-jacent. Le runtime Mono est écrit en langage C.

Vous pouvez utiliser system,System.IO, System.Net et le reste des bibliothèques de classes .NET pour accéder aux installations du système d’exploitation Linux sous-jacentes.

Sur Android, la plupart des fonctionnalités système telles que Audio, Graphics, OpenGL et Telephony ne sont pas disponibles directement pour les applications natives. Elles sont uniquement exposées via les API Java d’Android Runtime résidant dans l’un des espaces de noms Java.* ou Android.*. L’architecture est à peu près la suivante :

Diagramme de Mono et ART au-dessus du noyau et sous .NET/Java + liaisons

Les développeurs Xamarin.Android accèdent aux différentes fonctionnalités du système d’exploitation en appelant les API .NET qu’ils connaissent (pour un accès de bas niveau) ou en utilisant les classes exposées dans les espaces de noms Android qui fournissent un pont vers les API Java exposées par le runtime Android.

Pour plus d’informations sur la façon dont les classes Android communiquent avec les classes Runtime Android, consultez le document Conception d’API .

Packages d’application

Les packages d’applications Android sont des conteneurs ZIP avec une extension de fichier .apk . Les packages d’applications Xamarin.Android ont la même structure et la même disposition que les packages Android normaux, avec les ajouts suivants :

  • Les assemblys d’application (contenant IL) sont stockés non compressés dans le dossier assemblys . Pendant le démarrage du processus dans les builds Release, le fichier .apk est mmap() intégré au processus et les assemblys sont chargés à partir de la mémoire. Cela permet un démarrage plus rapide de l’application, car les assemblys n’ont pas besoin d’être extraits avant l’exécution.

  • Note: Les informations d’emplacement de l’assembly telles que Assembly.Location et Assembly.CodeBasene peuvent pas être utilisées dans les builds Release. Elles n’existent pas en tant qu’entrées de système de fichiers distinctes et n’ont pas d’emplacement utilisable.

  • Les bibliothèques natives contenant le runtime Mono sont présentes dans le fichier .apk . Une application Xamarin.Android doit contenir des bibliothèques natives pour les architectures Android souhaitées/ciblées, par exemple armeabi , armeabi-v7a , x86 . Les applications Xamarin.Android ne peuvent pas s’exécuter sur une plateforme, sauf si elle contient les bibliothèques d’exécution appropriées.

Les applications Xamarin.Android contiennent également des wrappers Android Callable pour permettre à Android d’appeler du code managé.

Wrappers pouvant être appelés par Android

  • Les wrappers callables Android sont un pont JNI qui est utilisé chaque fois que le runtime Android a besoin d’appeler du code managé. Les wrappers appelants Android permettent de remplacer les méthodes virtuelles et d’implémenter des interfaces Java. Pour plus d’informations, consultez la documentation Vue d’ensemble de l’intégration Java .

Wrappers callables managés

Les wrappers pouvant être appelés managés sont un pont JNI qui est utilisé chaque fois que du code managé doit appeler du code Android et prendre en charge le remplacement des méthodes virtuelles et l’implémentation d’interfaces Java. L’ensemble des espaces de noms Android.* et associés sont des wrappers pouvant être callables managés générés via une liaison .jar. Les wrappers callables managés sont responsables de la conversion entre les types managés et Android et de l’appel des méthodes de plateforme Android sous-jacentes via JNI.

Chaque wrapper callable managé créé contient une référence globale Java, accessible via la propriété Android.Runtime.IJavaObject.Handle . Les références globales sont utilisées pour fournir le mappage entre les instances Java et les instances managées. Les références globales sont une ressource limitée : les émulateurs autorisent seulement 2 000 références globales à exister à la fois, tandis que la plupart du matériel permet à plus de 52 000 références globales d’exister à la fois.

Pour suivre quand les références globales sont créées et détruites, vous pouvez définir la propriété système debug.mono.log pour qu’elle contienne gref.

Les références globales peuvent être explicitement libérées en appelant Java.Lang.Object.Dispose() sur le wrapper pouvant être appelé géré. Cela supprime le mappage entre le instance Java et le instance managé et permet la collecte des instance Java. Si le instance Java est de nouveau accessible à partir du code managé, un nouveau wrapper pouvant être appelé managé est créé pour celui-ci.

Vous devez faire preuve de prudence lors de la suppression des wrappers callables managés si les instance peuvent être partagés par inadvertance entre des threads, car la suppression de l’instance aura un impact sur les références provenant d’autres threads. Pour une sécurité maximale, seules Dispose() les instances qui ont été allouées via newou à partir de méthodes que vous connaissez allouent toujours de nouvelles instances et non des instances mises en cache qui peuvent entraîner un partage accidentel instance entre les threads.

Sous-classes wrapper pouvant être appelées managées

Les sous-classes wrapper pouvant être appelées managées sont l’endroit où toutes les logiques « intéressantes » propres à l’application peuvent résider. Il s’agit notamment des sous-classes Android.App.Activity personnalisées (telles que le type Activity1 dans le modèle de projet par défaut). (Plus précisément, il s’agit de toutes les sous-classes Java.Lang.Object qui ne contiennent pas d’attribut personnalisé RegisterAttribute ou RegisterAttribute.DoNotGenerateAcw a la valeur false, ce qui est la valeur par défaut.)

À l’instar des wrappers pouvant être appelé gérés, les sous-classes de wrapper avec appel managé contiennent également une référence globale, accessible via la propriété Java.Lang.Object.Handle . Tout comme avec les wrappers pouvant être callables managés, les références globales peuvent être libérées explicitement en appelant Java.Lang.Object.Dispose(). Contrairement aux wrappers pouvant être callables managés, il faut faire très attention avant de supprimer ces instances, car la suppression de l’instance interrompt le mappage entre le instance Java (un instance d’un wrapper Android Callable) et le instance managé.

Java Activation

Lorsqu’un wrapper Callable ( ACW) Android est créé à partir de Java, le constructeur ACW provoque l’appel du constructeur C# correspondant. Par exemple, ACW pour MainActivity contiendra un constructeur par défaut qui appellera le constructeur par défaut de MainActivity. (Cette opération s’effectue via l’appel TypeManager.Activate() dans les constructeurs ACW.)

Il existe une autre signature de constructeur de conséquence : le constructeur (IntPtr, JniHandleOwnership). Le constructeur (IntPtr, JniHandleOwnership) est appelé chaque fois qu’un objet Java est exposé au code managé et qu’un wrapper callable managé doit être construit pour gérer le handle JNI. Cette opération est généralement effectuée automatiquement.

Il existe deux scénarios dans lesquels le constructeur (IntPtr, JniHandleOwnership) doit être fourni manuellement sur une sous-classe Wrapper pouvant être appelé managé :

  1. Android.App.Application est sous-classé. L’application est spéciale ; le constructeur Applicaton par défaut ne sera jamais appelé, et le constructeur (IntPtr, JniHandleOwnership) doit être fourni à la place.

  2. Appel de méthode virtuelle à partir d’un constructeur de classe de base.

Notez que (2) est une abstraction fuite. Dans Java, comme en C#, les appels aux méthodes virtuelles d’un constructeur appellent toujours l’implémentation de méthode la plus dérivée. Par exemple, le constructeur TextView(Context, AttributeSet, int) appelle la méthode virtuelle TextView.getDefaultMovementMethod(), qui est liée en tant que propriété TextView.DefaultMovementMethod. Ainsi, si un type LogTextBox devait (1) sous-classe TextView, (2) remplacer TextView.DefaultMovementMethod et (3) activer un instance de cette classe via XML, la propriété DefaultMovementMethod remplacée serait appelée avant que le constructeur ACW ait eu la possibilité d’exécuter, et cela se produirait avant que le constructeur C# ait la possibilité de s’exécuter.

Cela est pris en charge par l’instanciation d’un instance LogTextBox via le constructeur LogTextView(IntPtr, JniHandleOwnership) lorsque le instance LogTextBox ACW entre d’abord dans le code managé, puis en appelant le constructeur LogTextBox(Context, IAttributeSet, int)sur le même instance lorsque le constructeur ACW s’exécute.

Ordre des événements :

  1. Le xml de disposition est chargé dans un ContentView.

  2. Android instancie le graphe d’objets Layout et instancie un instance de monodroid.apidemo.LogTextBox , acw pour LogTextBox .

  3. Le constructeur monodroid.apidemo.LogTextBox exécute le constructeur android.widget.TextView .

  4. Le constructeur TextView appelle monodroid.apidemo.LogTextBox.getDefaultMovementMethod() .

  5. monodroid.apidemo.LogTextBox.getDefaultMovementMethod() appelle LogTextBox.n_getDefaultMovementMethod() , qui appelle TextView.n_GetDefaultMovementMethod() , qui appelle Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer) .

  6. Java.Lang.Object.GetObject<TextView>() vérifie s’il existe déjà un instance C# correspondant pour le handle . Si c’est le cas, il est retourné. Dans ce scénario, il n’y en a pas, donc Object.GetObject<T>() doit en créer un.

  7. Object.GetObject<T>() recherche le constructeur LogTextBox(IntPtr, JniHandleOwneship), l’appelle, crée un mappage entre handle et le instance créé, puis retourne le instance créé.

  8. TextView.n_GetDefaultMovementMethod() appelle la propriété LogTextBox.DefaultMovementMethod getter.

  9. Le contrôle retourne au constructeur android.widget.TextView , qui termine l’exécution.

  10. Le constructeur monodroid.apidemo.LogTextBox s’exécute en appelant TypeManager.Activate() .

  11. Le constructeur LogTextBox(Context, IAttributeSet, int) s’exécute sur le même instance créé dans (7) .

  12. Si le constructeur (IntPtr, JniHandleOwnership) est introuvable, une exception System.MissingMethodException](xref:System.MissingMethodException) est levée.

Appels Dispose() prématurés

Il existe un mappage entre un handle JNI et le instance C# correspondant. Java.Lang.Object.Dispose() interrompt ce mappage. Si un handle JNI entre dans le code managé après que le mappage a été rompu, il ressemble à activation Java, et le constructeur (IntPtr, JniHandleOwnership) est vérifié et appelé. Si le constructeur n’existe pas, une exception est levée.

Par exemple, compte tenu de la sous-classe Managed Callable Wraper suivante :

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);
    }
}

Si nous créons un instance, supprimer() de celui-ci et recréer le wrapper callable managé :

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

Le programme va mourir :

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

Si la sous-classe contient un constructeur (IntPtr, JniHandleOwnership), une nouvelle instance du type est créée. Par conséquent, le instance semble « perdre » toutes les données instance, car il s’agit d’une nouvelle instance. (Notez que la valeur est null.)

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

Seule dispose() des sous-classes wrapper pouvant être appelées managées lorsque vous savez que l’objet Java ne sera plus utilisé, ou que la sous-classe ne contient pas de données instance et qu’un constructeur (IntPtr, JniHandleOwnership) a été fourni.

Démarrage de l'application

Quand une activité, un service, etc. est lancé, Android va d’abord case activée pour voir s’il existe déjà un processus en cours d’exécution pour héberger l’activité/le service/etc. S’il n’existe aucun processus de ce type, un nouveau processus est créé, le AndroidManifest.xml est lu et le type spécifié dans l’attribut /manifest/application/@android:name est chargé et instancié. Ensuite, tous les types spécifiés par les valeurs d’attribut /manifest/application/provider/@android:name sont instanciés et leur méthode ContentProvider.attachInfo%28) est appelée. Xamarin.Android s’y connecte en ajoutant un mono. MonoRuntimeProviderContentProvider pour AndroidManifest.xml pendant le processus de génération. Le mono. La méthode MonoRuntimeProvider.attachInfo() est responsable du chargement du runtime Mono dans le processus. Toutes les tentatives d’utilisation de Mono antérieures à ce point échouent. ( Remarque : C’est pourquoi les types dont la sous-classe Android.App.Application doit fournir un constructeur (IntPtr, JniHandleOwnership), car l’application instance est créée avant l’initialisation de Mono.)

Une fois l’initialisation du processus terminée, AndroidManifest.xml est consulté pour trouver le nom de classe de l’activité/service/etc. à lancer. Par exemple, l’attribut /manifest/application/activity/@android:name est utilisé pour déterminer le nom d’une activité à charger. Pour les activités, ce type doit hériter de android.app.Activity. Le type spécifié est chargé via Class.forName() (ce qui nécessite que le type soit un type Java, d’où les wrappers Android Callable), puis instancié. La création d’un wrapper callable Android instance déclenche la création d’un instance du type C# correspondant. Android appelle ensuite Activity.onCreate(Bundle) , ce qui entraîne l’appel de Activity.OnCreate(Bundle) correspondant, et vous êtes en route vers les courses.