Metadane powiązań języka Java
Ważne
Obecnie badamy użycie powiązań niestandardowych na platformie Xamarin. Weź udział w tej ankiecie , aby poinformować o przyszłych wysiłkach programistycznych.
Kod języka C# na platformie Xamarin.Android wywołuje biblioteki Java za pomocą powiązań, które są mechanizmem, który stanowi abstrakcję szczegółów niskiego poziomu określonych w interfejsie natywnym języka Java (JNI). Platforma Xamarin.Android udostępnia narzędzie, które generuje te powiązania. To narzędzie pozwala deweloperowi kontrolować sposób tworzenia powiązania przy użyciu metadanych, które umożliwiają procedury, takie jak modyfikowanie przestrzeni nazw i zmienianie nazw elementów członkowskich. W tym dokumencie omówiono sposób działania metadanych, podsumowują atrybuty obsługiwane przez metadane i wyjaśniono, jak rozwiązywać problemy z powiązaniem, modyfikując te metadane.
Omówienie
Biblioteka powiązań języka Java platformy Xamarin.Android próbuje zautomatyzować większość pracy niezbędnej do powiązania istniejącej biblioteki systemu Android za pomocą narzędzia nazywanego czasami generatorem powiązań. Po powiązaniu biblioteki Języka Java platforma Xamarin.Android sprawdzi klasy Języka Java i wygeneruje listę wszystkich pakietów, typów i elementów członkowskich, które mają być powiązane. Ta lista interfejsów API jest przechowywana w pliku XML, który można znaleźć w katalogu {project directory}\obj\Release\api.xml dla kompilacji RELEASE i w katalogu {project directory}\obj\Debug\api.xml dla kompilacji DEBUGowania .
Generator powiązań użyje pliku api.xml jako wytycznych dotyczących generowania niezbędnych klas otoki języka C#. Zawartość tego pliku XML jest odmianą formatu projektu Open Source systemu Google. Poniższy fragment kodu jest przykładem zawartości 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>
W tym przykładzie api.xml deklaruje klasę w android
pakiecie o nazwie Manifest
, która rozszerza klasę java.lang.Object
.
W wielu przypadkach pomoc człowieka jest wymagana, aby interfejs API Języka Java czuł się bardziej jak .NET" lub aby rozwiązać problemy, które uniemożliwiają kompilowanie zestawu powiązania. Na przykład może być konieczne zmianę nazw pakietów Java na przestrzenie nazw platformy .NET, zmianę nazwy klasy lub zmianę zwracanego typu metody.
Te zmiany nie są osiągane przez bezpośrednie modyfikowanie api.xml . Zamiast tego zmiany są rejestrowane w specjalnych plikach XML udostępnianych przez szablon biblioteki powiązań Języka Java. Podczas kompilowania zestawu powiązań Xamarin.Android generator powiązań będzie mieć wpływ na te pliki mapowania podczas tworzenia zestawu powiązania
Te pliki mapowania XML można znaleźć w folderze Przekształcenia projektu:
MetaData.xml — umożliwia wprowadzanie zmian w ostatnim interfejsie API, takich jak zmiana przestrzeni nazw wygenerowanego powiązania.
EnumFields.xml — zawiera mapowanie między stałymi języka Java
int
i C#enums
.EnumMethods.xml — umożliwia zmianę parametrów metody i zwracanych typów z stałych Języka Java
int
do języka C#enums
.
Plik MetaData.xml jest najbardziej importem tych plików, ponieważ umożliwia zmianę ogólnego przeznaczenia w powiązaniu, takie jak:
Zmiana nazw przestrzeni nazw, klas, metod lub pól, tak aby były zgodne z konwencjami platformy .NET.
Usuwanie przestrzeni nazw, klas, metod lub pól, które nie są potrzebne.
Przenoszenie klas do różnych przestrzeni nazw.
Dodanie dodatkowych klas obsługi w celu wykonania projektu powiązania zgodnie z wzorcami platformy .NET Framework.
Przejdźmy do bardziej szczegółowego omówienia Metadata.xml .
plik przekształcenia Metadata.xml
Jak już dowiedzieliśmy się, plik Metadata.xml jest używany przez generator powiązań w celu wywierania wpływu na tworzenie zestawu powiązania. Format metadanych używa składni XPath i jest prawie identyczny z metadanymi GAPI opisanymi w przewodniku po metadanych GAPI. Ta implementacja jest prawie kompletną implementacją środowiska XPath 1.0 i dlatego obsługuje elementy w standardzie 1.0. Ten plik jest zaawansowanym mechanizmem opartym na programie XPath umożliwiającym zmianę, dodawanie, ukrywanie lub przenoszenie dowolnego elementu lub atrybutu w pliku interfejsu API. Wszystkie elementy reguły w specyfikacji metadanych zawierają atrybut ścieżki w celu zidentyfikowania węzła, do którego ma zostać zastosowana reguła. Reguły są stosowane w następującej kolejności:
- add-node — dołącza węzeł podrzędny do węzła określonego przez atrybut path.
- attr — ustawia wartość atrybutu elementu określonego przez atrybut path.
- remove-node — usuwa węzły zgodne z określoną ścieżką XPath.
Poniżej przedstawiono przykład pliku 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>
Poniżej wymieniono niektóre z najczęściej używanych elementów XPath dla interfejsu API języka Java:
interface
— służy do lokalizowania interfejsu Java. na przykład/interface[@name='AuthListener']
.class
— służy do lokalizowania klasy . na przykład/class[@name='MapView']
.method
— służy do lokalizowania metody w klasie lub interfejsie Języka Java. na przykład/class[@name='MapView']/method[@name='setTitleSource']
.parameter
— Zidentyfikuj parametr dla metody. Na przykład:/parameter[@name='p0']
Dodawanie typów
Element add-node
poinformuje projekt powiązania platformy Xamarin.Android, aby dodać nową klasę otoki do api.xml. Na przykład poniższy fragment kodu przekierowuje generator powiązań, aby utworzyć klasę z konstruktorem i pojedynczym polem:
<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>
Usuwanie typów
Istnieje możliwość poinstruowania generatora powiązań platformy Xamarin.Android, aby zignorował typ języka Java i nie powiązał go. W tym celu należy dodać remove-node
element XML do pliku metadata.xml :
<remove-node path="/api/package[@name='{package_name}']/class[@name='{name}']" />
Zmienianie nazw członków
Zmiana nazw elementów członkowskich nie może być wykonywana bezpośrednio przez edytowanie pliku api.xml, ponieważ platforma Xamarin.Android wymaga oryginalnych nazw interfejsu natywnego Java (JNI). W związku z tym nie można zmienić atrybutu //class/@name
; jeśli tak jest, powiązanie nie będzie działać.
Rozważmy przypadek, w którym chcemy zmienić nazwę typu , android.Manifest
.
W tym celu możemy spróbować bezpośrednio edytować api.xml i zmienić nazwę klasy w następujący sposób:
<attr path="/api/package[@name='android']/class[@name='Manifest']"
name="name">NewName</attr>
Spowoduje to utworzenie przez generator powiązań następującego kodu języka C# dla klasy otoki:
[Register ("android/NewName")]
public class NewName : Java.Lang.Object { ... }
Zwróć uwagę, że nazwa klasy otoki została zmieniona na NewName
, a oryginalny typ języka Java to nadal Manifest
. Nie jest już możliwe, aby klasa powiązania Xamarin.Android uzyskiwała dostęp do dowolnych metod w android.Manifest
systemie ; klasa otoki jest powiązana z nieistniejący typem języka Java.
Aby poprawnie zmienić nazwę zarządzaną typu opakowanego (lub metody), należy ustawić managedName
atrybut, jak pokazano w tym przykładzie:
<attr path="/api/package[@name='android']/class[@name='Manifest']"
name="managedName">NewName</attr>
EventArg
Zmienianie nazw klas otoki
Gdy generator powiązań platformy Xamarin.Android zidentyfikuje onXXX
metodę ustawiającą dla typu odbiornika, zdarzenie języka C# i EventArgs
podklasa zostaną wygenerowane w celu obsługi interfejsu API o smaku platformy .NET dla wzorca odbiornika opartego na języku Java. Rozważmy na przykład następującą klasę i metodę Języka Java:
com.someapp.android.mpa.guidance.NavigationManager.on2DSignNextManuever(NextManueverListener listener);
Platforma Xamarin.Android usunie prefiks on
z metody setter i zamiast tego użyje 2DSignNextManuever
jako podstawy nazwy podklasy EventArgs
. Podklasa będzie mieć nazwę podobną do następującej:
NavigationManager.2DSignNextManueverEventArgs
To nie jest legalna nazwa klasy języka C#. Aby rozwiązać ten problem, autor powiązania musi użyć atrybutu argsType
i podać prawidłową nazwę języka C# dla podklasy EventArgs
:
<attr path="/api/package[@name='com.someapp.android.mpa.guidance']/
interface[@name='NavigationManager.Listener']/
method[@name='on2DSignNextManeuver']"
name="argsType">NavigationManager.TwoDSignNextManueverEventArgs</attr>
Obsługiwane atrybuty
W poniższych sekcjach opisano niektóre atrybuty służące do przekształcania interfejsów API języka Java.
argsType
Ten atrybut jest umieszczany w metodach ustawiających, aby nazwać podklasę EventArg
, która zostanie wygenerowana w celu obsługi odbiorników Języka Java. Opisano to bardziej szczegółowo poniżej w sekcji Zmiana nazwy klas otoki EventArg w dalszej części tego przewodnika.
eventName
Określa nazwę zdarzenia. Jeśli jest pusty, hamuje generowanie zdarzeń. Opisano to bardziej szczegółowo w tytule sekcji Zmiana nazwy klas otoki EventArg.
managedName
Służy do zmiany nazwy pakietu, klasy, metody lub parametru. Aby na przykład zmienić nazwę klasy MyClass
Java na NewClassName
:
<attr path="/api/package[@name='com.my.application']/class[@name='MyClass']"
name="managedName">NewClassName</attr>
W następnym przykładzie pokazano wyrażenie XPath służące do zmiany nazwy metody java.lang.object.toString
na Java.Lang.Object.NewManagedName
:
<attr path="/api/package[@name='java.lang']/class[@name='Object']/method[@name='toString']"
name="managedName">NewMethodName</attr>
managedType
managedType
Służy do zmiany zwracanego typu metody. W niektórych sytuacjach generator powiązań niepoprawnie wywnioskuje zwracany typ metody Java, co spowoduje błąd czasu kompilacji. Jednym z możliwych rozwiązań w tej sytuacji jest zmiana typu zwracanego metody.
Na przykład generator powiązań uważa, że metoda de.neom.neoreadersdk.resolution.compareTo()
Java powinna zwrócić int
parametr i przyjąć Object
jako parametry, co powoduje komunikat o błędzie Błąd CS0535: "DE. Neom.Neoreadersdk.Resolution" nie implementuje elementu członkowskiego interfejsu "Java.Lang.IComparable.CompareTo(Java.Lang.Object)".
Poniższy fragment kodu pokazuje, jak zmienić typ pierwszego parametru wygenerowanej metody języka C# z klasy na DE.Neom.Neoreadersdk.Resolution
: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
Zmienia zwracany typ metody. Nie powoduje to zmiany atrybutu zwracanego (ponieważ zmiany zwracanych atrybutów mogą spowodować niezgodne zmiany w podpisie JNI). W poniższym przykładzie zwracany typ append
metody jest zmieniany z SpannableStringBuilder
na IAppendable
(przypomnij sobie, że język C# nie obsługuje kowariantnych typów zwracanych):
<attr path="/api/package[@name='android.text']/
class[@name='SpannableStringBuilder']/
method[@name='append']"
name="managedReturn">Java.Lang.IAppendable</attr>
Ukrywane
Narzędzia, które zaciemniają biblioteki Języka Java, mogą zakłócać działanie generatora powiązań platformy Xamarin.Android i możliwość generowania klas otoki języka C#. Cechy zaciemnionych klas obejmują:
- Nazwa klasy zawiera element $, tj. $.class
- Nazwa klasy jest całkowicie naruszona z małymi literami, tj. klasa
Ten fragment kodu to przykład generowania typu C# "bez zaciemnionego":
<attr path="/api/package[@name='{package_name}']/class[@name='{name}']"
name="obfuscated">false</attr>
propertyName
Ten atrybut może służyć do zmiany nazwy właściwości zarządzanej.
Wyspecjalizowany przypadek użycia propertyName
obejmuje sytuację, w której klasa Java ma tylko metodę getter dla pola. W takiej sytuacji generator powiązań chciałby utworzyć właściwość tylko do zapisu, co jest odradzane na platformie .NET. Poniższy fragment kodu pokazuje, jak "usunąć" właściwości platformy .NET przez ustawienie wartości na propertyName
pusty ciąg:
<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>
Należy pamiętać, że metody ustawiające i getter będą nadal tworzone przez generator powiązań.
nadawca
Określa, który parametr metody powinien być parametrem sender
, gdy metoda jest mapowana na zdarzenie. Wartość może mieć wartość true
lub false
. Na przykład:
<attr path="/api/package[@name='android.app']/
interface[@name='TimePickerDialog.OnTimeSetListener']/
method[@name='onTimeSet']/
parameter[@name='view']"
name="sender">true</ attr>
widoczność
Ten atrybut służy do zmiany widoczności klasy, metody lub właściwości. Na przykład może być konieczne podwyższenie poziomu protected
metody Java, tak aby odpowiednia otoka języka C# to 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 i EnumMethods.xml
Istnieją przypadki, w których biblioteki systemu Android używają stałych całkowitych do reprezentowania stanów przekazywanych do właściwości lub metod bibliotek. W wielu przypadkach warto powiązać te stałe całkowite z wyliczeniami w języku C#. Aby ułatwić to mapowanie, użyj plików EnumFields.xml i EnumMethods.xml w projekcie powiązania.
Definiowanie wyliczenia przy użyciu EnumFields.xml
Plik EnumFields.xml zawiera mapowanie między stałymi języka Java int
i C# enums
. Przyjrzyjmy się następującemu przykładowi wyliczenia języka C# tworzonego dla zestawu int
stałych:
<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>
W tym miejscu zajęliśmy klasę SKRealReachSettings
Java i zdefiniowaliśmy wyliczenie języka C# o nazwie SKMeasurementUnit
w przestrzeni nazw Skobbler.Ngx.Map.RealReach
. Wpisy field
definiują nazwę stałej Java (przykład UNIT_SECOND
), nazwę wpisu wyliczenia (przykład Second
) i wartość całkowitą reprezentowaną przez obie jednostki (przykład 0
).
Definiowanie metod Getter/Setter przy użyciu EnumMethods.xml
Plik EnumMethods.xml umożliwia zmianę parametrów metody i zwracanych typów z stałych Java int
na C# enums
. Innymi słowy, mapuje odczyt i zapis wyliczenia języka C# (zdefiniowane w pliku EnumFields.xml) na stałe get
i set
metody języka Javaint
.
Biorąc pod uwagę wyliczenie SKRealReachSettings
zdefiniowane powyżej, następujący plik EnumMethods.xml zdefiniuje metodę getter/setter dla tego wyliczenia:
<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>
Pierwszy method
wiersz mapuje wartość zwracaną metody Java getMeasurementUnit
na wyliczenie SKMeasurementUnit
. Drugi method
wiersz mapuje pierwszy parametr obiektu setMeasurementUnit
na ten sam wyliczenie.
Po wprowadzeniu wszystkich tych zmian można użyć następującego kodu na platformie Xamarin.Android, aby ustawić element MeasurementUnit
:
realReachSettings.MeasurementUnit = SKMeasurementUnit.Second;
Podsumowanie
W tym artykule omówiono sposób, w jaki platforma Xamarin.Android używa metadanych do przekształcania definicji interfejsu API z formatu Google AOSP. Po pokryciu zmian, które są możliwe przy użyciu Metadata.xml, zbadał ograniczenia napotkane podczas zmiany nazwy elementów członkowskich i przedstawił listę obsługiwanych atrybutów XML, opisując, kiedy należy użyć każdego atrybutu.