Partager via


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 .

Emplacement du fichier api.xml dans le dossier obj/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é.