Métadonnées de liaisons Java
Important
Nous étudions actuellement l’utilisation des liaisons personnalisées sur la plateforme Xamarin. S’il vous plaît prendre cette enquête pour informer les efforts de développement futurs.
Le code C# dans Xamarin.Android appelle les bibliothèques Java via des liaisons, qui sont un mécanisme qui extrait les détails de bas niveau spécifiés dans Java Native Interface (JNI). Xamarin.Android fournit un outil qui génère ces liaisons. Ces outils permettent au développeur de contrôler la façon dont une liaison est créée à l’aide de métadonnées, ce qui autorise des procédures telles que la modification d’espaces de noms et le renommage des membres. Ce document décrit le fonctionnement des métadonnées, résume les attributs pris en charge par les métadonnées et explique comment résoudre les problèmes de liaison en modifiant ces métadonnées.
Vue d’ensemble
Une bibliothèque de liaisons Java Xamarin.Android tente d’automatiser une grande partie du travail nécessaire à la liaison d’une bibliothèque Android existante à l’aide d’un outil parfois appelé générateur de liaisons. Lors de la liaison d’une bibliothèque Java, Xamarin.Android inspecte les classes Java et génère une liste de tous les packages, types et membres à lier. Cette liste d’API est stockée dans un fichier XML qui se trouve dans {répertoire de projet}\obj\Release\api.xml pour une build RELEASE et dans {répertoire de projet}\obj\Debug\api.xml pour une build DEBUG .
Le générateur de liaisons utilise le fichier api.xml comme guide pour générer les classes wrapper C# nécessaires. Le contenu de ce fichier XML est une variante du format Android Open Source Project de Google. L’extrait de code suivant est un exemple du contenu de api.xml:
<api>
<package name="android">
<class abstract="false" deprecated="not deprecated" extends="java.lang.Object"
extends-generic-aware="java.lang.Object"
final="true"
name="Manifest"
static="false"
visibility="public">
<constructor deprecated="not deprecated" final="false"
name="Manifest" static="false" type="android.Manifest"
visibility="public">
</constructor>
</class>
...
</api>
Dans cet exemple, api.xml déclare une classe dans le android
package nommée Manifest
qui étend le java.lang.Object
.
Dans de nombreux cas, l’assistance humaine est nécessaire pour que l’API Java ressemble davantage à « « . NET » ou pour corriger les problèmes qui empêchent la compilation de l’assembly de liaison. Par exemple, il peut être nécessaire de remplacer les noms de packages Java par des espaces de noms .NET, de renommer une classe ou de modifier le type de retour d’une méthode.
Ces modifications ne sont pas obtenues en modifiant directementapi.xml . Au lieu de cela, les modifications sont enregistrées dans des fichiers XML spéciaux fournis par le modèle bibliothèque de liaison Java. Lors de la compilation de l’assembly de liaison Xamarin.Android, le générateur de liaisons est influencé par ces fichiers de mappage lors de la création de l’assembly de liaison
Ces fichiers de mappage XML se trouvent dans le dossier Transformations du projet :
MetaData.xml : permet d’apporter des modifications à l’API finale, telles que la modification de l’espace de noms de la liaison générée.
EnumFields.xml : contient le mappage entre les constantes Java
int
et C#enums
.EnumMethods.xml : permet de modifier les paramètres de méthode et de retourner des types de constantes Java
int
en C#enums
.
Le fichierMetaData.xml est le fichier le plus important de ces fichiers, car il permet des modifications à usage général de la liaison, telles que :
Renommez les espaces de noms, classes, méthodes ou champs afin qu’ils suivent les conventions .NET.
Suppression des espaces de noms, classes, méthodes ou champs qui ne sont pas nécessaires.
Déplacement de classes vers différents espaces de noms.
Ajout de classes de support supplémentaires pour que la conception de la liaison suive les modèles .NET Framework.
Passons à la discussion Metadata.xml plus en détail.
fichier de transformation Metadata.xml
Comme nous l’avons déjà appris, le fichier Metadata.xml est utilisé par le générateur de liaisons pour influencer la création de l’assembly de liaison. Le format des métadonnées utilise la syntaxe XPath et est presque identique aux métadonnées GAPI décrites dans le guide des métadonnées GAPI . Cette implémentation est presque une implémentation complète de XPath 1.0 et prend donc en charge les éléments de la norme 1.0. Ce fichier est un puissant mécanisme XPath permettant de modifier, d’ajouter, de masquer ou de déplacer un élément ou un attribut dans le fichier d’API. Tous les éléments de règle dans la spécification de métadonnées incluent un attribut path permettant d’identifier le nœud auquel la règle doit être appliquée. Les règles sont appliquées dans l’ordre suivant :
- add-node : ajoute un nœud enfant au nœud spécifié par l’attribut path.
- attr : définit la valeur d’un attribut de l’élément spécifié par l’attribut path.
- remove-node : supprime les nœuds correspondant à un XPath spécifié.
Voici un exemple de fichier Metadata.xml :
<metadata>
<!-- Normalize the namespace for .NET -->
<attr path="/api/package[@name='com.evernote.android.job']"
name="managedName">Evernote.AndroidJob</attr>
<!-- Don't need these packages for the Xamarin binding/public API -->
<remove-node path="/api/package[@name='com.evernote.android.job.v14']" />
<remove-node path="/api/package[@name='com.evernote.android.job.v21']" />
<!-- Change a parameter name from the generic p0 to a more meaningful one. -->
<attr path="/api/package[@name='com.evernote.android.job']/class[@name='JobManager']/method[@name='forceApi']/parameter[@name='p0']"
name="name">api</attr>
</metadata>
Voici quelques-uns des éléments XPath les plus couramment utilisés pour les API Java :
interface
: permet de localiser une interface Java. Exemple :/interface[@name='AuthListener']
.class
: utilisé pour localiser une classe . Exemple :/class[@name='MapView']
.method
: permet de localiser une méthode sur une classe ou une interface Java. Exemple :/class[@name='MapView']/method[@name='setTitleSource']
.parameter
: identifiez un paramètre pour une méthode. Exemple :/parameter[@name='p0']
Ajout de types
L’élément add-node
indique au projet de liaison Xamarin.Android d’ajouter une nouvelle classe wrapper à api.xml. Par exemple, l’extrait de code suivant indique au générateur de liaison de créer une classe avec un constructeur et un champ unique :
<add-node path="/api/package[@name='org.alljoyn.bus']">
<class abstract="false" deprecated="not deprecated" final="false" name="AuthListener.AuthRequest" static="true" visibility="public" extends="java.lang.Object">
<constructor deprecated="not deprecated" final="false" name="AuthListener.AuthRequest" static="false" type="org.alljoyn.bus.AuthListener.AuthRequest" visibility="public" />
<field name="p0" type="org.alljoyn.bus.AuthListener.Credentials" />
</class>
</add-node>
Suppression de types
Il est possible d’indiquer au générateur de liaisons Xamarin.Android d’ignorer un type Java et de ne pas le lier. Pour ce faire, ajoutez un remove-node
élément XML au fichier metadata.xml :
<remove-node path="/api/package[@name='{package_name}']/class[@name='{name}']" />
Renommage des membres
Le renommage des membres ne peut pas être effectué en modifiant directement le fichier api.xml , car Xamarin.Android nécessite les noms JNI (Java Native Interface) d’origine. Par conséquent, l’attribut //class/@name
ne peut pas être modifié ; s’il l’est, la liaison ne fonctionnera pas.
Prenons le cas où nous voulons renommer un type, android.Manifest
.
Pour ce faire, nous pouvons essayer de modifier directement api.xml et de renommer la classe comme suit :
<attr path="/api/package[@name='android']/class[@name='Manifest']"
name="name">NewName</attr>
Le générateur de liaisons crée alors le code C# suivant pour la classe wrapper :
[Register ("android/NewName")]
public class NewName : Java.Lang.Object { ... }
Notez que la classe wrapper a été renommée en NewName
, tandis que le type Java d’origine est toujours Manifest
. Il n’est plus possible pour la classe de liaison Xamarin.Android d’accéder à des méthodes sur android.Manifest
; la classe wrapper est liée à un type Java inexistant.
Pour modifier correctement le nom managé d’un type encapsulé (ou d’une méthode), il est nécessaire de définir l’attribut managedName
comme indiqué dans cet exemple :
<attr path="/api/package[@name='android']/class[@name='Manifest']"
name="managedName">NewName</attr>
Renommage des EventArg
classes Wrapper
Lorsque le générateur de liaison Xamarin.Android identifie une onXXX
méthode setter pour un type d’écouteur, un événement et EventArgs
une sous-classe C# sont générés pour prendre en charge une API aromatisée .NET pour le modèle d’écouteur java. Prenons l’exemple de la classe et de la méthode Java suivantes :
com.someapp.android.mpa.guidance.NavigationManager.on2DSignNextManuever(NextManueverListener listener);
Xamarin.Android supprime le préfixe on
de la méthode setter et utilise 2DSignNextManuever
à la place comme base pour le nom de la EventArgs
sous-classe. La sous-classe sera nommée comme suit :
NavigationManager.2DSignNextManueverEventArgs
Il ne s’agit pas d’un nom de classe C# légal. Pour corriger ce problème, l’auteur de la liaison doit utiliser l’attribut argsType
et fournir un nom C# valide pour la EventArgs
sous-classe :
<attr path="/api/package[@name='com.someapp.android.mpa.guidance']/
interface[@name='NavigationManager.Listener']/
method[@name='on2DSignNextManeuver']"
name="argsType">NavigationManager.TwoDSignNextManueverEventArgs</attr>
Attributs pris en charge
Les sections suivantes décrivent certains des attributs permettant de transformer les API Java.
argsType
Cet attribut est placé sur les méthodes setter pour nommer la EventArg
sous-classe qui sera générée pour prendre en charge les écouteurs Java. Cela est décrit plus en détail ci-dessous dans la section Renommage des classes wrapper EventArg plus loin dans ce guide.
eventName
Spécifie un nom pour un événement. S’il est vide, il empêche la génération d’événements. Cela est décrit plus en détail dans le titre de la section Renommage des classes de wrapper EventArg.
managedName
Il permet de modifier le nom d’un package, d’une classe, d’une méthode ou d’un paramètre. Par exemple, pour remplacer le nom de la classe MyClass
Java par NewClassName
:
<attr path="/api/package[@name='com.my.application']/class[@name='MyClass']"
name="managedName">NewClassName</attr>
L’exemple suivant illustre une expression XPath pour renommer la méthode java.lang.object.toString
en Java.Lang.Object.NewManagedName
:
<attr path="/api/package[@name='java.lang']/class[@name='Object']/method[@name='toString']"
name="managedName">NewMethodName</attr>
managedType
managedType
est utilisé pour modifier le type de retour d’une méthode. Dans certaines situations, le générateur de liaisons déduit incorrectement le type de retour d’une méthode Java, ce qui entraîne une erreur de compilation. Une solution possible dans cette situation consiste à modifier le type de retour de la méthode.
Par exemple, le générateur de liaisons estime que la méthode de.neom.neoreadersdk.resolution.compareTo()
Java doit retourner un int
et prendre Object
en tant que paramètres, ce qui entraîne le message d’erreur Erreur CS0535 : 'DE. Neom.Neoreadersdk.Resolution' n’implémente pas le membre d’interface 'Java.Lang.IComparable.CompareTo(Java.Lang.Object)'.
L’extrait de code suivant montre comment modifier le type du premier paramètre de la méthode C# générée d’un DE.Neom.Neoreadersdk.Resolution
en :Java.Lang.Object
<attr path="/api/package[@name='de.neom.neoreadersdk']/
class[@name='Resolution']/
method[@name='compareTo' and count(parameter)=1 and
parameter[1][@type='de.neom.neoreadersdk.Resolution']]/
parameter[1]" name="managedType">Java.Lang.Object</attr>
managedReturn
Modifie le type de retour d’une méthode. Cela ne modifie pas l’attribut de retour (car les modifications apportées aux attributs de retour peuvent entraîner des modifications incompatibles de la signature JNI). Dans l’exemple suivant, le type de retour de la append
méthode est modifié de SpannableStringBuilder
à IAppendable
(rappelez-vous que C# ne prend pas en charge les types de retour covariants) :
<attr path="/api/package[@name='android.text']/
class[@name='SpannableStringBuilder']/
method[@name='append']"
name="managedReturn">Java.Lang.IAppendable</attr>
Obfuscated
Les outils qui obfusquent des bibliothèques Java peuvent interférer avec le générateur de liaisons Xamarin.Android et sa capacité à générer des classes wrapper C#. Les caractéristiques des classes masquées sont les suivantes :
- Le nom de la classe inclut un $, c’est-à-dire un $.class
- Le nom de la classe est entièrement compromis en minuscules, c’est-à-dire a.class
Cet extrait de code est un exemple de génération d’un type C# « non obfusqué » :
<attr path="/api/package[@name='{package_name}']/class[@name='{name}']"
name="obfuscated">false</attr>
propertyName
Cet attribut peut être utilisé pour modifier le nom d’une propriété gérée.
Un cas spécialisé d’utilisation propertyName
implique la situation où une classe Java n’a qu’une méthode getter pour un champ. Dans ce cas, le générateur de liaison souhaite créer une propriété en écriture seule, ce qui est déconseillé dans .NET. L’extrait de code suivant montre comment « supprimer » les propriétés .NET en définissant sur propertyName
une chaîne vide :
<attr path="/api/package[@name='org.java_websocket.handshake']/class[@name='HandshakeImpl1Client']/method[@name='setResourceDescriptor'
and count(parameter)=1
and parameter[1][@type='java.lang.String']]"
name="propertyName"></attr>
<attr path="/api/package[@name='org.java_websocket.handshake']/class[@name='HandshakeImpl1Client']/method[@name='getResourceDescriptor'
and count(parameter)=0]"
name="propertyName"></attr>
Notez que les méthodes setter et getter seront toujours créées par le générateur de liaisons.
expéditeur
Spécifie le paramètre d’une méthode qui doit être le sender
paramètre lorsque la méthode est mappée à un événement. La valeur peut être true
ou false
. Par exemple :
<attr path="/api/package[@name='android.app']/
interface[@name='TimePickerDialog.OnTimeSetListener']/
method[@name='onTimeSet']/
parameter[@name='view']"
name="sender">true</ attr>
visibility
Cet attribut est utilisé pour modifier la visibilité d’une classe, d’une méthode ou d’une propriété. Par exemple, il peut être nécessaire de promouvoir une protected
méthode Java afin qu’elle soit le wrapper C# correspondant :public
<!-- Change the visibility of a class -->
<attr path="/api/package[@name='namespace']/class[@name='ClassName']" name="visibility">public</attr>
<!-- Change the visibility of a method -->
<attr path="/api/package[@name='namespace']/class[@name='ClassName']/method[@name='MethodName']" name="visibility">public</attr>
EnumFields.xml et EnumMethods.xml
Il existe des cas où les bibliothèques Android utilisent des constantes entières pour représenter des états passés aux propriétés ou aux méthodes des bibliothèques. Dans de nombreux cas, il est utile de lier ces constantes entières à des énumérations en C#. Pour faciliter ce mappage, utilisez les fichiers EnumFields.xml et EnumMethods.xml dans votre projet de liaison.
Définition d’un enum à l’aide de EnumFields.xml
Le fichier EnumFields.xml contient le mappage entre les constantes Java int
et C# enums
. Prenons l’exemple suivant d’une énumération C# en cours de création pour un ensemble de int
constantes :
<mapping jni-class="com/skobbler/ngx/map/realreach/SKRealReachSettings" clr-enum-type="Skobbler.Ngx.Map.RealReach.SKMeasurementUnit">
<field jni-name="UNIT_SECOND" clr-name="Second" value="0" />
<field jni-name="UNIT_METER" clr-name="Meter" value="1" />
<field jni-name="UNIT_MILIWATT_HOURS" clr-name="MilliwattHour" value="2" />
</mapping>
Ici, nous avons pris la classe SKRealReachSettings
Java et défini une énumération C# appelée SKMeasurementUnit
dans l’espace de noms Skobbler.Ngx.Map.RealReach
. Les field
entrées définissent le nom de la constante Java (exemple UNIT_SECOND
), le nom de l’entrée enum (exemple Second
) et la valeur entière représentée par les deux entités (exemple 0
).
Définition de méthodes Getter/Setter à l’aide de EnumMethods.xml
Le fichier EnumMethods.xml permet de modifier les paramètres de méthode et de retourner des types de constantes Java int
en C# enums
. En d’autres termes, il mappe la lecture et l’écriture des énumérations C# (définies dans le fichier EnumFields.xml) à la constante get
et set
aux méthodes Javaint
.
Compte tenu de l’énumération SKRealReachSettings
définie ci-dessus, le fichier EnumMethods.xml suivant définit l’élément getter/setter pour cette énumération :
<mapping jni-class="com/skobbler/ngx/map/realreach/SKRealReachSettings">
<method jni-name="getMeasurementUnit" parameter="return" clr-enum-type="Skobbler.Ngx.Map.RealReach.SKMeasurementUnit" />
<method jni-name="setMeasurementUnit" parameter="measurementUnit" clr-enum-type="Skobbler.Ngx.Map.RealReach.SKMeasurementUnit" />
</mapping>
La première method
ligne mappe la valeur de retour de la méthode Java getMeasurementUnit
à l’enum SKMeasurementUnit
. La deuxième method
ligne mappe le premier paramètre de à setMeasurementUnit
la même énumération.
Une fois toutes ces modifications en place, vous pouvez utiliser le code suivant dans Xamarin.Android pour définir :MeasurementUnit
realReachSettings.MeasurementUnit = SKMeasurementUnit.Second;
Résumé
Cet article explique comment Xamarin.Android utilise des métadonnées pour transformer une définition d’API à partir du format Google AOSP. Après avoir couvert les modifications possibles à l’aide deMetadata.xml, il a examiné les limitations rencontrées lors du changement de nom des membres et a présenté la liste des attributs XML pris en charge, décrivant quand chaque attribut doit être utilisé.