Metadati per le associazioni Java

Importante

Attualmente si sta esaminando l'utilizzo dell'associazione personalizzata nella piattaforma Xamarin. Si prega di prendere questo sondaggio per informare i futuri sforzi di sviluppo.

Il codice C# in Xamarin.Android chiama librerie Java tramite associazioni, un meccanismo che astrae i dettagli di basso livello specificati in Java Native Interface (JNI). Xamarin.Android fornisce uno strumento che genera queste associazioni. Questo strumento consente allo sviluppatore di controllare il modo in cui viene creata un'associazione usando i metadati, che consente procedure come la modifica di spazi dei nomi e la ridenominazione dei membri. Questo documento illustra il funzionamento dei metadati, riepiloga gli attributi supportati dai metadati e spiega come risolvere i problemi di associazione modificando questi metadati.

Panoramica

Una libreria di binding Java Xamarin.Android tenta di automatizzare gran parte del lavoro necessario per associare una libreria Android esistente con l'aiuto di uno strumento talvolta noto come generatore di associazioni. Quando si associa una libreria Java, Xamarin.Android esamina le classi Java e genera un elenco di tutti i pacchetti, i tipi e i membri da associare. Questo elenco di API è archiviato in un file XML disponibile in {directory progetto}\obj\Release\api.xml per una compilazione RELEA edizione Standard e in {directory progetto}\obj\Debug\api.xml per una compilazione DEBUG.

Location of the api.xml file in the obj/Debug folder

Il generatore di associazioni userà il file api.xml come linea guida per generare le classi wrapper C# necessarie. Il contenuto di questo file XML è una variante del formato Android Open Source Project di Google. Il frammento di codice seguente è un esempio del contenuto di 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>

In questo esempio, api.xml dichiara una classe nel android pacchetto denominato Manifest che estende .java.lang.Object

In molti casi, l'assistenza umana è necessaria per rendere l'API Java più simile a ".NET" o per correggere i problemi che impediscono la compilazione dell'assembly di associazione. Ad esempio, potrebbe essere necessario modificare i nomi dei pacchetti Java in spazi dei nomi .NET, rinominare una classe o modificare il tipo restituito di un metodo.

Queste modifiche non vengono apportate modificando direttamente api.xml . Le modifiche vengono invece registrate in file XML speciali forniti dal modello Libreria di binding Java. Quando si compila l'assembly di associazione Xamarin.Android, il generatore di associazioni verrà influenzato da questi file di mapping durante la creazione dell'assembly di associazione

Questi file di mapping XML sono disponibili nella cartella Transforms del progetto:

  • MetaData.xml: consente di apportare modifiche all'API finale, ad esempio la modifica dello spazio dei nomi dell'associazione generata.

  • EnumFields.xml: contiene il mapping tra costanti Java int e C# enums .

  • EnumMethods.xml: consente di modificare i parametri del metodo e restituire tipi da costanti Java int a C# enums .

Il file MetaData.xml è la maggior parte delle importazioni di questi file perché consente modifiche per utilizzo generico all'associazione, ad esempio:

  • Ridenominazione di spazi dei nomi, classi, metodi o campi in modo che seguano le convenzioni .NET.

  • Rimozione di spazi dei nomi, classi, metodi o campi non necessari.

  • Spostamento di classi in spazi dei nomi diversi.

  • L'aggiunta di classi di supporto aggiuntive per rendere la progettazione dell'associazione seguire i modelli .NET Framework.

Consente di proseguire per discutere Metadata.xml in modo più dettagliato.

Metadata.xml file di trasformazione

Come si è già appreso, il file Metadata.xml viene usato dal generatore di associazioni per influenzare la creazione dell'assembly di associazione. Il formato dei metadati usa la sintassi XPath ed è quasi identico ai metadati GAPI descritti nella guida ai metadati GAPI. Questa implementazione è quasi un'implementazione completa di XPath 1.0 e supporta quindi gli elementi nello standard 1.0. Questo file è un potente meccanismo basato su XPath per modificare, aggiungere, nascondere o spostare qualsiasi elemento o attributo nel file API. Tutti gli elementi della regola nella specifica di metadati includono un attributo path per identificare il nodo a cui applicare la regola. Le regole vengono applicate nell'ordine seguente:

  • add-node : aggiunge un nodo figlio al nodo specificato dall'attributo path.
  • attr : imposta il valore di un attributo dell'elemento specificato dall'attributo path.
  • remove-node : rimuove i nodi corrispondenti a un XPath specificato.

Di seguito è riportato un esempio di file 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>

Di seguito sono elencati alcuni degli elementi XPath più comunemente usati per l'API Java:

  • interface : usato per individuare un'interfaccia Java. ad esempio /interface[@name='AuthListener'].

  • class : usato per individuare una classe . ad esempio /class[@name='MapView'].

  • method : usato per individuare un metodo in una classe o un'interfaccia Java. ad esempio /class[@name='MapView']/method[@name='setTitleSource'].

  • parameter : identificare un parametro per un metodo. Ad esempio: /parameter[@name='p0']

Aggiunta di tipi

L'elemento add-node indicherà al progetto di associazione Xamarin.Android di aggiungere una nuova classe wrapper a api.xml. Ad esempio, il frammento di codice seguente indirizza il generatore di binding a creare una classe con un costruttore e un singolo campo:

<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>

Rimozione dei tipi

È possibile indicare a Xamarin.Android Bindings Generator di ignorare un tipo Java e non associarlo. A tale scopo, aggiungere un remove-node elemento XML al file metadata.xml :

<remove-node path="/api/package[@name='{package_name}']/class[@name='{name}']" />

Ridenominazione dei membri

Non è possibile rinominare i membri modificando direttamente il file api.xml perché Xamarin.Android richiede i nomi JNI (Java Native Interface) originali. Pertanto, l'attributo //class/@name non può essere modificato. In caso affermativo, l'associazione non funzionerà.

Si consideri il caso in cui si vuole rinominare un tipo, android.Manifest. A tale scopo, è possibile provare a modificare direttamente api.xml e rinominare la classe in questo modo:

<attr path="/api/package[@name='android']/class[@name='Manifest']" 
    name="name">NewName</attr>

In questo modo, il generatore binding crea il codice C# seguente per la classe wrapper:

[Register ("android/NewName")]
public class NewName : Java.Lang.Object { ... }

Si noti che la classe wrapper è stata rinominata in NewName, mentre il tipo Java originale è ancora Manifest. Non è più possibile che la classe di associazione Xamarin.Android accinga a qualsiasi metodo in android.Manifest. La classe wrapper è associata a un tipo Java inesistente.

Per modificare correttamente il nome gestito di un tipo di cui è stato eseguito il wrapping (o il metodo), è necessario impostare l'attributo managedName come illustrato in questo esempio:

<attr path="/api/package[@name='android']/class[@name='Manifest']" 
    name="managedName">NewName</attr>

Ridenominazione delle EventArg classi wrapper

Quando il generatore di binding Xamarin.Android identifica un onXXX metodo setter per un tipo di listener, verrà generato un evento E# e EventArgs una sottoclasse C# per supportare un'API con gusto .NET per il modello di listener basato su Java. Si consideri ad esempio la classe e il metodo Java seguenti:

com.someapp.android.mpa.guidance.NavigationManager.on2DSignNextManuever(NextManueverListener listener);

Xamarin.Android eviterà il prefisso on dal metodo setter e userà 2DSignNextManuever invece come base per il nome della EventArgs sottoclasse. La sottoclasse verrà denominata in modo simile a:

NavigationManager.2DSignNextManueverEventArgs

Non si tratta di un nome di classe C# legale. Per risolvere questo problema, l'autore dell'associazione deve usare l'attributo argsType e specificare un nome C# valido per la EventArgs sottoclasse:

<attr path="/api/package[@name='com.someapp.android.mpa.guidance']/
    interface[@name='NavigationManager.Listener']/
    method[@name='on2DSignNextManeuver']" 
    name="argsType">NavigationManager.TwoDSignNextManueverEventArgs</attr>

Attributi supportati

Le sezioni seguenti descrivono alcuni degli attributi per la trasformazione delle API Java.

argsType

Questo attributo viene inserito nei metodi setter per denominare la EventArg sottoclasse che verrà generata per supportare i listener Java. Questo argomento è descritto più dettagliatamente di seguito nella sezione Ridenominazione delle classi wrapper eventArg più avanti in questa guida.

eventName

Specifica un nome per un evento. Se vuoto, impedisce la generazione di eventi. Questo argomento è descritto in modo più dettagliato nel titolo della sezione Ridenominazione delle classi wrapper eventArg.

managedName

Viene usato per modificare il nome di un pacchetto, una classe, un metodo o un parametro. Ad esempio, per modificare il nome della classe MyClass Java in NewClassName:

<attr path="/api/package[@name='com.my.application']/class[@name='MyClass']" 
    name="managedName">NewClassName</attr>

Nell'esempio seguente viene illustrata un'espressione XPath per rinominare il metodo java.lang.object.toString in Java.Lang.Object.NewManagedName:

<attr path="/api/package[@name='java.lang']/class[@name='Object']/method[@name='toString']" 
    name="managedName">NewMethodName</attr>

managedType

managedType viene utilizzato per modificare il tipo restituito di un metodo. In alcune situazioni il generatore di associazioni dedurrà erroneamente il tipo restituito di un metodo Java, che genererà un errore in fase di compilazione. Una possibile soluzione in questa situazione consiste nel modificare il tipo restituito del metodo.

Ad esempio, il generatore binding ritiene che il metodo de.neom.neoreadersdk.resolution.compareTo() Java debba restituire e int accettare Object come parametri, che genera il messaggio di errore Error CS0535: 'DE. Neom.Neoreadersdk.Resolution' non implementa il membro dell'interfaccia 'Java.Lang.IComparable.CompareTo(Java.Lang.Object)'. Il frammento di codice seguente illustra come modificare il tipo del primo parametro del metodo C# generato da a DE.Neom.Neoreadersdk.Resolution un oggetto 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

Modifica il tipo restituito di un metodo. Ciò non modifica l'attributo restituito( poiché le modifiche apportate agli attributi restituiti possono comportare modifiche incompatibili alla firma JNI). Nell'esempio seguente il tipo restituito del append metodo viene modificato da SpannableStringBuilder a IAppendable (tenere presente che C# non supporta i tipi restituiti covarianti):

<attr path="/api/package[@name='android.text']/
    class[@name='SpannableStringBuilder']/
    method[@name='append']" 
    name="managedReturn">Java.Lang.IAppendable</attr>

Offuscato

Gli strumenti che offuscano le librerie Java possono interferire con il generatore di binding Xamarin.Android e la sua capacità di generare classi wrapper C#. Le caratteristiche delle classi offuscate includono:

  • Il nome della classe include , $ad esempio a$.class
  • Il nome della classe è completamente compromesso dai caratteri minuscoli, ad esempio a.class

Questo frammento di codice è un esempio di come generare un tipo C# non offuscato:

<attr path="/api/package[@name='{package_name}']/class[@name='{name}']" 
    name="obfuscated">false</attr>

propertyName

Questo attributo può essere usato per modificare il nome di una proprietà gestita.

Un caso specializzato di uso propertyName implica la situazione in cui una classe Java ha solo un metodo getter per un campo. In questo caso il generatore di binding vuole creare una proprietà di sola scrittura, un elemento sconsigliato in .NET. Il frammento di codice seguente illustra come "rimuovere" le proprietà .NET impostando su propertyName una stringa vuota:

<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>

Si noti che i metodi setter e getter verranno comunque creati dal generatore di associazioni.

mittente

Specifica quale parametro di un metodo deve essere il sender parametro quando il metodo viene mappato a un evento. Il valore può essere true o false. Ad esempio:

<attr path="/api/package[@name='android.app']/
    interface[@name='TimePickerDialog.OnTimeSetListener']/
    method[@name='onTimeSet']/
    parameter[@name='view']" 
    name="sender">true</ attr>

visibility

Questo attributo viene usato per modificare la visibilità di una classe, di un metodo o di una proprietà. Ad esempio, potrebbe essere necessario alzare di livello un protected metodo Java in modo che il wrapper C# corrispondente sia 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 e EnumMethods.xml

In alcuni casi le librerie Android usano costanti integer per rappresentare gli stati passati alle proprietà o ai metodi delle librerie. In molti casi, è utile associare queste costanti integer alle enumerazioni in C#. Per facilitare questo mapping, usare i file EnumFields.xml e EnumMethods.xml nel progetto di associazione.

Definizione di un'enumerazione tramite EnumFields.xml

Il file EnumFields.xml contiene il mapping tra costanti Java int e C# enums. Si esaminerà l'esempio seguente di un'enumerazione C# creata per un set di int costanti:

<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>

In questo caso è stata presa la classe SKRealReachSettings Java e viene definita un'enumerazione C# denominata SKMeasurementUnit nello spazio dei nomi Skobbler.Ngx.Map.RealReach. Le field voci definiscono il nome della costante Java (ad esempio UNIT_SECOND), il nome della voce di enumerazione (esempio Second) e il valore intero rappresentato da entrambe le entità (esempio 0).

Definizione dei metodi Getter/Setter tramite EnumMethods.xml

Il file EnumMethods.xml consente di modificare i parametri del metodo e restituire tipi da costanti Java int a C# enums. In altre parole, esegue il mapping della lettura e della scrittura di enumerazioni C# (definite nel file EnumFields.xml) a metodi e set costanti get Javaint.

Data l'enumerazione SKRealReachSettings definita in precedenza, il file di EnumMethods.xml seguente definirà il getter/setter per questa enumerazione:

<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 prima method riga esegue il mapping del valore restituito del metodo Java getMeasurementUnit all'enumerazione SKMeasurementUnit . La seconda method riga esegue il mapping del primo parametro dell'oggetto setMeasurementUnit alla stessa enumerazione.

Con tutte queste modifiche sul posto, è possibile usare il codice seguente in Xamarin.Android per impostare MeasurementUnit:

realReachSettings.MeasurementUnit = SKMeasurementUnit.Second;

Riepilogo

Questo articolo ha illustrato in che modo Xamarin.Android usa i metadati per trasformare una definizione API dal formato GoogleAOSP. Dopo aver esaminato le modifiche possibili usando Metadata.xml, sono state esaminate le limitazioni rilevate durante la ridenominazione dei membri e l'elenco degli attributi XML supportati, descrivendo quando è necessario usare ogni attributo.