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 dans le langage C.
Vous pouvez utiliser le système, 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 installations système telles que Audio, Graphics, OpenGL et Téléphonie ne sont pas disponibles directement pour les applications natives, elles ne sont exposées qu’à l’aide des API Java Android Runtime résidant dans l’un des espaces de noms Java.* ou les espaces de noms Android.* . L’architecture est à peu près semblable à ceci :
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 aux API Java exposées par le Runtime Android.
Pour plus d’informations sur la façon dont les classes Android communiquent avec les classes Android Runtime, consultez le document Création d’API.
Packages d’application
Les packages d’application Android sont des conteneurs ZIP avec une extension de fichier .apk . Les packages d’application Xamarin.Android ont la même structure et 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 Release, l '.apk est mmap() dans le 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.
Remarque : 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 la .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 s’il contient les bibliothèques d’exécution appropriées.
Les applications Xamarin.Android contiennent également des wrappers android appelants pour permettre à Android d’appeler du code managé.
Wrappers pouvant être appelés par Android
- Les wrappers pouvant être appelés Android sont un pont JNI qui est utilisé chaque fois que le runtime Android doit appeler du code managé. Les wrappers pouvant être appelé Android sont la façon dont les méthodes virtuelles peuvent être substituées et les interfaces Java peuvent être implémentées. Pour plus d’informations, consultez la documentation Vue d’ensemble de l’intégration Java.
Wrappers pouvant être appelé gérés
Les wrappers pouvant être appelés gérés sont un pont JNI qui est utilisé chaque fois que le code managé doit appeler du code Android et prendre en charge la substitution de méthodes virtuelles et l’implémentation d’interfaces Java. L’ensemble d’Android.* et les espaces de noms associés sont des wrappers pouvant être appelé gérés générés via .jar liaison. Les wrappers pouvant être appelé gérés sont responsables de la conversion entre les types managés et Android et l’appel des méthodes de plateforme Android sous-jacentes via JNI.
Chaque wrapper managé pouvant être appelé 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 uniquement 2000 références globales à exister à la fois, tandis que la plupart des matériels autorisent plus de 52 000 références globales à exister à la fois.
Pour effectuer le suivi de la création et de la destruction de références globales, vous pouvez définir la propriété système debug.mono.log pour contenir 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 l’instance Java et l’instance managée et autorise la collecte de l’instance Java. Si l’instance Java est recréé à partir du code managé, un wrapper pouvant être appelé managé est créé pour celui-ci.
Les soins doivent être exercés lors de la suppression de Wrappers pouvant être gérés si l’instance peut être partagée par inadvertance entre les threads, car la suppression de l’instance aura un impact sur les références des autres threads. Pour une sécurité maximale, seules Dispose()
les instances qui ont été allouées via new
ou à partir de méthodes que vous connaissez allouent toujours de nouvelles instances et non des instances mises en cache qui peuvent entraîner le partage d’instances accidentelles entre les threads.
Sous-classes wrapper pouvant être appelées managées
Les sous-classes wrapper pouvant être appelées managées sont où toutes les logiques spécifiques à l’application « intéressantes » peuvent vivre. Celles-ci incluent 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 n’importe quelle Sous-classes Java.Lang.Object qui ne contiennent pas d’attribut personnalisé RegisterAttribute ou RegisterAttribute.DoNotGenerateAcw est false, qui est la valeur par défaut.)
Comme les wrappers pouvant être appelé gérés, les sous-classes wrapper pouvant être appelées contiennent également une référence globale, accessible via la propriété Java.Lang.Object.Handle . Tout comme avec les wrappers pouvant être appelé gérés, les références globales peuvent être explicitement libérées en appelant Java.Lang.Object.Dispose(). Contrairement aux wrappers pouvant être appelants gérés, vous devez prendre soin avant de supprimer de telles instances, car dispose()-ing de l’instance interrompt le mappage entre l’instance Java (une instance d’un wrapper pouvant être appelé Android) et l’instance managée.
Java Activation
Lorsqu’un wrapper pouvant être appelé Android (ACW) est créé à partir de Java, le constructeur ACW entraîne l’appel du constructeur C# correspondant. Par exemple, l’ACW pour MainActivity contient un constructeur par défaut qui appelle le constructeur par défaut de MainActivity. (Cette opération est effectuée par le biais du 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 pouvant être appelé doit être construit pour gérer le handle JNI. Cela est généralement effectué 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é :
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.
Appel de méthode virtuelle à partir d’un constructeur de classe de base.
Notez que (2) est une abstraction fuite. En 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. Par conséquent, si un type LogTextBox devait (1) sous-classe TextView, (2) remplacer TextView.DefaultMovementMethod et (3) activer une instance de cette classe via XML, la propriété DefaultMovementMethod substituée serait appelée avant que le constructeur ACW ait eu la chance d’exécuter, et il se produirait avant que le constructeur C# ait eu la chance d’exécuter.
Cela est pris en charge par l’instanciation d’une instance LogTextBox via le constructeur LogTextView(IntPtr, JniHandleOwnership) lorsque l’instance ACW LogTextBox entre d’abord dans le code managé, puis en appelant le constructeur LogTextBox(Context, IAttributeSet, int) sur la même instance lorsque le constructeur ACW s’exécute.
Ordre des événements :
Le code XML de disposition est chargé dans un ContentView.
Android instancie le graphique d’objet Layout et instancie une instance de monodroid.apidemo.LogTextBox , ACW pour LogTextBox .
Le constructeur monodroid.apidemo.LogTextBox exécute le constructeur android.widget.TextView .
Le constructeur TextView appelle monodroid.apidemo.LogTextBox.getDefaultMovementMethod().
monodroid.apidemo.LogTextBox.getDefaultMovementMethod() appelle LogTextBox.n_getDefaultMovementMethod(), qui appelle TextView.n_GetDefaultMovementMethod(), qui appelle Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer) .
Java.Lang.Object.GetObject<TextView>() vérifie s’il existe déjà une instance C# correspondante pour le handle . S’il y en a, il est retourné. Dans ce scénario, il n’y en a pas, donc Object.GetObject<T>() doit en créer un.
Object.GetObject<T>() recherche le constructeur LogTextBox(IntPtr, JniHandleOwneship), l’appelle, crée un mappage entre handle et l’instance créée et retourne l’instance créée.
TextView.n_GetDefaultMovementMethod() appelle la propriété LogTextBox.DefaultMovementMethod getter.
Le contrôle retourne au constructeur android.widget.TextView , qui termine l’exécution.
Le constructeur monodroid.apidemo.LogTextBox s’exécute, appelant TypeManager.Activate().
Le constructeur LogTextBox(Context, IAttributeSet, int) s’exécute sur la même instance créée dans (7).
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 l’instance C# correspondante. Java.Lang.Object.Dispose() interrompt ce mappage. Si un handle JNI entre dans le code managé une fois le mappage rompu, il semble que l’activation Java et le constructeur (IntPtr, JniHandleOwnership) soit vérifié et appelé. Si le constructeur n’existe pas, une exception est levée.
Par exemple, étant donné la sous-classe Wraper pouvant être appelée managée 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 une instance, Dispose() de celle-ci et nous allons recréer le wrapper pouvant être appelé géré :
var list = new JavaList<IJavaObject>();
list.Add (new ManagedValue ("value"));
list [0].Dispose ();
Console.WriteLine (list [0].ToString ());
Le programme mourra :
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, l’instance semble « perdre » toutes les données d’instance, car il s’agit d’une nouvelle instance. (Notez que la valeur est null.)
I/mono-stdout( 2993): [Managed: Value=]
Seul 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 plus de données d’instance et qu’un constructeur (IntPtr, JniHandleOwnership) a été fourni.
Démarrage d’une application
Lorsqu’une activité, un service, etc. est lancé, Android vérifie d’abord 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 ont leur méthode ContentProvider.attachInfo%28) appelée. Xamarin.Android s’y connecte en ajoutant un mono. MonoRuntimeProvider ContentProvider à AndroidManifest.xml pendant le processus de génération. Mono . La méthode MonoRuntimeProvider.attachInfo() est chargée de charger le runtime Mono dans le processus. Toutes les tentatives d’utilisation de Mono avant ce point échouent. ( Remarque : C’est pourquoi les types qui sous-classe Android.App.Application doivent fournir un constructeur (IntPtr, JniHandleOwnership), car l’instance d’application est créée avant que Mono puisse être initialisé.)
Une fois l’initialisation du processus terminée, AndroidManifest.xml
est consultée pour rechercher 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 android.app.Activity.
Le type spécifié est chargé via Class.forName() (ce qui exige que le type soit un type Java, par conséquent les wrappers pouvant être appelables Android), puis instanciés. La création d’une instance wrapper pouvant être appelée Android déclenche la création d’une instance du type C# correspondant. Android appelle ensuite Activity.onCreate(Bundle), ce qui entraîne l’appel d’Activity.OnCreate(Bundle) correspondant et vous êtes désactivé aux courses.