Partager via


Métadonnées de liaisons Java

Important

Nous étudions actuellement l’utilisation des liaisons personnalisées sur la plateforme Xamarin. Prenez cette enquête pour informer les futurs efforts de développement.

Le code C# dans Xamarin.Android appelle des 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. Cet outil permet au développeur de contrôler la façon dont une liaison est créée à l’aide de métadonnées, ce qui permet de modifier des espaces de noms et de renommer 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 pour lier 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 qui doivent être liés. Cette liste d’API est stockée dans un fichier XML qui se trouve dans le {répertoire du projet}\obj\Release\api.xml pour une build RELEASE et sur {project directory}\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 instructions 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é Manifest qui étend le java.lang.Object.

Dans de nombreux cas, l’assistance humaine est nécessaire pour que l’API Java se sente plus « .NET like » ou pour corriger les problèmes qui empêchent la compilation de l’assembly de liaison. Par exemple, il peut être nécessaire de modifier les noms de package Java en espaces de noms .NET, renommer une classe ou modifier le type de retour d’une méthode.

Ces modifications ne sont pas obtenues en modifiant api.xml directement. 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 les types de retour des constantes Java int en C# enums .

Le fichier MetaData.xml est l’importation la plus grande de ces fichiers, car il autorise les modifications à usage général apportées à la liaison, telles que :

  • Renommage des espaces de noms, des classes, des méthodes ou des champs afin qu’ils suivent les conventions .NET.

  • Suppression d’espaces de noms, de classes, de méthodes ou de champs qui ne sont pas nécessaires.

  • Déplacement de classes vers différents espaces de noms.

  • L’ajout de classes de prise en charge supplémentaires pour rendre la conception de la liaison respecte les modèles .NET Framework.

Permet de discuter plus en détail des Metadata.xml .

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 de 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 dans la norme 1.0. Ce fichier est un mécanisme puissant basé sur XPath pour modifier, ajouter, masquer ou déplacer n’importe quel élément ou attribut dans le fichier API. Tous les éléments de règle dans la spécification des métadonnées incluent un attribut de chemin d’accès pour 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 : utilisé pour 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 dirige le générateur de liaison pour créer une classe avec un constructeur et un seul champ :

<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

Impossible de renommer les membres 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é ; si c’est le cas, la liaison ne fonctionnera pas.

Considérez 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>

Cela entraîne la création du 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 NewNameen , 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 à toutes les 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 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 basé sur Java. Par exemple, considérez la classe et 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 plutôt 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 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 des 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. Cette procédure est décrite plus en détail ci-dessous dans la section Renommer les 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. Cette procédure est décrite plus en détail dans le titre de la section Renaming EventArg Wrapper Classes.

managedName

Il est utilisé pour 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 temps de compilation. Une solution possible dans cette situation consiste à modifier le type de retour de la méthode.

Par exemple, le générateur bindings croit que la méthode de.neom.neoreadersdk.resolution.compareTo() Java doit retourner un int paramètre et prendre Object en tant que paramètres, ce qui entraîne l’erreur du message d’erreur CS0535 : 'DE. Neom.Neoreadersdk.Resolution' n’implémente pas le membre de l’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 à un 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 avec la signature JNI). Dans l’exemple suivant, le type de retour de la append méthode est remplacé SpannableStringBuilder par 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 obfusent les 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 obfuscatées sont les suivantes :

  • Le nom de la classe comprend un $, c’est-à-dire un$.class
  • Le nom de la classe est entièrement compromis de caractères minuscules, c’est-à-dire a.class

Cet extrait de code est un exemple de génération d’un type C# « non obfuscated » :

<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 la propertyName valeur sur 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 sont 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 un 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 les états passés aux propriétés ou 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’une énumération à 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’énumération C# créée 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 d’énumération (exemple Second) et la valeur entière représentée par les deux entités (exemple 0).

Définition des méthodes Getter/Setter à l’aide de EnumMethods.xml

Le fichier EnumMethods.xml permet de modifier les paramètres de méthode et les types de retour des 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) à des méthodes et set constantes get Javaint.

Étant donné l’énumération SKRealReachSettings définie ci-dessus, le fichier EnumMethods.xml suivant définit le 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’énumération SKMeasurementUnit . La deuxième method ligne mappe le premier paramètre de l’énumération setMeasurementUnit au même enum.

Avec toutes ces modifications en place, vous pouvez utiliser le code suivant dans Xamarin.Android pour définir les MeasurementUnitéléments suivants :

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 de Metadata.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é.