Метаданные привязок Java
Внимание
В настоящее время рассматривается возможность использования настраиваемых привязок на платформе Xamarin. Примите участие в этом опросе, чтобы помочь определить дальнейшие направления разработки.
Код C# в Xamarin.Android вызывает библиотеки Java с помощью привязок, которые являются механизмом для абстрагирования низкоуровневых сведений, указанных в собственном интерфейсе Java (JNI). Xamarin.Android предоставляет средство, создающее эти привязки. С помощью этого средства разработчик может управлять способами создания привязок с использованием метаданных, что позволяет выполнять такие процедуры, как изменение пространств имен и переименование членов. В этом документе описывается, как работают метаданные, перечислены атрибуты, поддерживаемые метаданными, и объясняется, как устранить проблемы привязки путем изменения этих метаданных.
Обзор
Библиотека привязки Java Xamarin.Android предназначена автоматизировать большую часть работы по привязке существующей библиотеки Android с помощью средства, которое иногда называют генератором привязок. Если выполняется привязка библиотеки Java, Xamarin.Android проверяет классы Java и создает список всех пакетов, типов и элементов, которые должны быть привязаны. Этот список API хранится в XML-файле, который можно найти в {каталоге проекта}\obj\Release\api.xml для сборки RELEASE и по адресу {project directory}\obj\Debug\api.xml для сборки DEBUG .
Генератор привязок будет использовать файл api.xml как основу для создания необходимых классов-оболочек C#. Содержимое этого файла XML является разновидностью формата Android Open Source Project Google. Ниже приведен пример содержимого файла 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>
В этом примере файл api.xml объявляет класс в пакете android
с именем Manifest
, который расширяет java.lang.Object
.
Во многих случаях требуется участие человека, чтобы сделать API Java похожим на .NET или устранить проблемы, препятствующие компиляции сборки привязки. Например, может потребоваться изменить имена пакетов Java на пространства имен .NET, переименовать класс или изменить тип возвращаемого значения метода.
Эти изменения не внедряются путем прямого изменения файла api.xml. Вместо этого изменения записываются в специальные файлы XML, предоставляемые шаблоном библиотеки привязки Java. На генератор привязок будут влиять файлы сопоставления при создании и компиляции сборки привязки Xamarin.Android.
Эти файлы сопоставления XML можно найти в папке проекта Transforms:
MetaData.xml— позволяет вносить изменения в окончательный API, например изменение пространства имен созданной привязки.
EnumFields.xml — содержит сопоставление констант Java
int
и C#enums
.EnumMethods.xml— позволяет изменять параметры метода и возвращать типы из констант Java
int
на C#enums
.
Файл MetaData.xml — самый важный, так как он позволяет вносить изменения общего назначения в привязку, например:
переименовывать пространства имен, классы, методы или поля, чтобы они соответствовали соглашениям .NET;
удалять пространства имен, классы, методы или поля, которые больше не требуются;
перемещать классы в разные пространства имен;
добавлять дополнительные классы поддержки для обеспечения соответствия структуры привязки шаблонам .NET Framework.
Рассмотрим Metadata.xml более детально.
Файл преобразования Metadata.xml
Как уже указано, файл Metadata.xml использует генератор привязок, чтобы повлиять на создание сборки привязки. В формате метаданных используется синтаксис XPath и он практически идентичен метаданным GAPI, которые описаны в этом разделе руководства. Эта реализация является почти полной реализацией XPath 1.0 с такой же поддержкой элементов в стандарте 1.0. Этот файл является мощным механизмом на основе XPath для изменения, добавления, скрытия или перемещения любого элемента или атрибута в файле API. Все элементы правил в спецификации метаданных включают в себя атрибут path (пути) для указания узла, к которому должно применяться правило. Правила применяются в следующем порядке:
- add-node — добавляет дочерний узел к узлу, указанному атрибутом пути.
- attr — задает значение атрибута элемента, указанного атрибутом пути.
- remove-node — удаляет узлы, соответствующие указанному XPath.
Далее приведен пример файла 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>
Ниже перечислены некоторые из наиболее часто используемых элементов XPath для API Java.
interface
— используется для поиска интерфейса Java. Например,/interface[@name='AuthListener']
.class
— используется для поиска класса. Например,/class[@name='MapView']
.method
— используется для поиска метода в классе или интерфейсе Java. Например,/class[@name='MapView']/method[@name='setTitleSource']
.parameter
— определите параметр для метода. Например,/parameter[@name='p0']
Добавление типов
Элемент add-node
сообщает проекту привязки Xamarin.Android о добавлении нового класса-оболочки в api.xml. Например, следующий фрагмент кода будет направлять генератор привязки для создания класса с конструктором и одним полем:
<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>
Удаление типов
Можно указать генератору привязок Xamarin.Android игнорировать тип Java, а не привязывать его. Это можно сделать, добавив XML-элемент remove-node
в файл metadata.xml:
<remove-node path="/api/package[@name='{package_name}']/class[@name='{name}']" />
Переименование членов
Нельзя переименовать члены, напрямую отредактировав файл api.xml, так как для Xamarin.Android требуются исходные имена собственного интерфейса Java (JNI). Поэтому атрибут //class/@name
изменить нельзя. Если сделать это, привязка не будет работать.
Рассмотрим случай, когда нужно переименовать тип android.Manifest
.
Для этого мы можем попытаться напрямую изменить api.xml и переименовать класс следующим образом:
<attr path="/api/package[@name='android']/class[@name='Manifest']"
name="name">NewName</attr>
Это приведет к созданию в генераторе привязок следующего кода C# для класса-оболочки:
[Register ("android/NewName")]
public class NewName : Java.Lang.Object { ... }
Обратите внимание, что класс-оболочка переименован в NewName
, а исходный тип Java — все еще Manifest
. Класс привязки Xamarin.Android больше не может получить доступ к каким-либо методам android.Manifest
. Класс-оболочка привязан к несуществующему типу Java.
Чтобы правильно изменить управляемое имя упакованного типа (или метода), необходимо задать атрибут managedName
, как показано в этом примере:
<attr path="/api/package[@name='android']/class[@name='Manifest']"
name="managedName">NewName</attr>
Переименование классов-оболочек EventArg
Если генератор привязки Xamarin.Android определяет метод задания onXXX
для типа прослушивателя, то создается событие и подкласс C# EventArgs
для поддержки в API с чертами .NET прослушивателей на основе Java. В качестве примера рассмотрим следующий класс и метод Java:
com.someapp.android.mpa.guidance.NavigationManager.on2DSignNextManuever(NextManueverListener listener);
Xamarin.Android удалит префикс on
из метода задания, а вместо этого использует 2DSignNextManuever
в качестве основы для имени подкласса EventArgs
. Подкласс будет называться примерно так:
NavigationManager.2DSignNextManueverEventArgs
Это имя класса C# недопустимо. Чтобы устранить эту проблему, автор привязки должен использовать атрибут argsType
и указать допустимое имя C# для подкласса EventArgs
:
<attr path="/api/package[@name='com.someapp.android.mpa.guidance']/
interface[@name='NavigationManager.Listener']/
method[@name='on2DSignNextManeuver']"
name="argsType">NavigationManager.TwoDSignNextManueverEventArgs</attr>
Поддерживаемые атрибуты
В следующих разделах описаны некоторые атрибуты для преобразования API Java.
argsType
Этот атрибут помещается в методы задания, чтобы присвоить имя подклассу EventArg
, который будет создан для поддержки прослушивателей Java. Более подробно это описано в разделе Переименование классов-оболочек EventArg этого руководства.
eventName
Задает имя для события. Если значение пустое, то событие не создается. Более подробно это описано в разделе Переименование классов-оболочек EventArg.
managedName
Используется для изменения имени пакета, класса, метода или параметра. Например, чтобы изменить имя класса Java MyClass
на NewClassName
:
<attr path="/api/package[@name='com.my.application']/class[@name='MyClass']"
name="managedName">NewClassName</attr>
В следующем примере показано выражение XPath для переименования метода java.lang.object.toString
на Java.Lang.Object.NewManagedName
:
<attr path="/api/package[@name='java.lang']/class[@name='Object']/method[@name='toString']"
name="managedName">NewMethodName</attr>
ManagedType
managedType
используется для изменения типа возвращаемого значения метода. В некоторых ситуациях генератор привязок неправильно определяет тип возвращаемого значения метода Java, что приводит к ошибке времени компиляции. Одним из возможных решений в этой ситуации является изменение типа возвращаемого значения метода.
Например, генератор привязок считает, что метод de.neom.neoreadersdk.resolution.compareTo()
Java должен возвращать int
и принимать Object
в качестве параметров, что приводит к ошибке error Error CS0535: 'DE. Neom.Neoreadersdk.Resolution не реализует член интерфейса Java.Lang.IComparable.CompareTo(Java.Lang.Object)".
В следующем фрагменте кода показано, как изменить тип первого параметра, созданного методом C#, с 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
Изменяет тип возвращаемого значения метода. Это не изменит атрибут возвращаемого значения (так как изменения таких атрибутов могут привести к несовместимости изменений в сигнатуре JNI). В следующем примере тип возвращаемого значения метода append
изменяется с SpannableStringBuilder
на IAppendable
(повторный вызов C# не поддерживает ковариантные типы возвращаемых данных):
<attr path="/api/package[@name='android.text']/
class[@name='SpannableStringBuilder']/
method[@name='append']"
name="managedReturn">Java.Lang.IAppendable</attr>
obfuscated
Средства обфускации кода в библиотеках Java могут конфликтовать с генератором привязок Xamarin.Android и помешать ему создать классы-оболочки C#. Характеристики классов obfuscated включают в себя:
- имя класса содержит символ $, например a$.class;
- имя класса написано полностью строчными буквами, например a.class.
Ниже приведен пример того, как создать "немаскированный" тип C#:
<attr path="/api/package[@name='{package_name}']/class[@name='{name}']"
name="obfuscated">false</attr>
propertyName
Этот атрибут можно использовать для изменения имени управляемого свойства.
Особым случаем использования propertyName
является ситуация, когда у класса Java есть только метод получения для поля. В этом случае генератору привязки нужно создать свойство доступное только для записи, что не рекомендуется в .NET. В следующем фрагменте кода показано, как "удалить" свойства .NET, установив для propertyName
пустую строку:
<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>
Обратите внимание, что генератор привязок по-прежнему будет создавать методы задания и получения.
отправитель
Указывает, какой параметр метода должен быть параметром sender
, если метод сопоставлен с событием. Значением может быть true
или false
. Например:
<attr path="/api/package[@name='android.app']/
interface[@name='TimePickerDialog.OnTimeSetListener']/
method[@name='onTimeSet']/
parameter[@name='view']"
name="sender">true</ attr>
видимость
Этот атрибут используется для изменения видимости класса, метода или свойства. Например, может потребоваться повысить уровень метода Java protected
таким образом, чтобы у него была соответствующая программа-оболочка C# 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 и EnumMethods.xml
Бывают случаи, когда библиотеки Android используют целочисленные константы для представления состояний, передаваемых в свойства или методы библиотек. Во многих случаях полезно привязать такие константы к перечислениям в C#. Чтобы упростить это сопоставление, используйте файлы EnumFields.xml и EnumMethods.xml в проекте привязки.
Определение перечисления с помощью файла EnumFields.xml
Файл EnumFields.xml содержит сопоставление констант int
Java и enums
C#. Рассмотрим следующий пример перечисления C#, создаваемого для набора констант int
:
<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>
Здесь мы воспользовались классом Java SKRealReachSettings
и определили перечисление C# с именем SKMeasurementUnit
в пространстве имен Skobbler.Ngx.Map.RealReach
. Записи field
определяют имя константы Java (например, UNIT_SECOND
), имя записи перечисления (например, Second
) и целочисленное значение, представленное обеими сущностями (например, 0
).
Определение методов получения и задания с помощью файла EnumMethods.xml
Файл EnumMethods.xml позволяет изменять параметры метода и типы возвращаемых значений из констант int
Java на enums
C#. Другими словами, он сопоставляет чтение и запись перечислений C# (определенных в файле EnumFields.xml) с методами get
и set
константы Java int
.
Учитывая выше заданное перечисление SKRealReachSettings
, файл EnumMethods.xml определяет метод получения или задания для этого перечисления:
<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>
Первая строка method
сопоставляет возвращаемое значение метода Java getMeasurementUnit
с перечислением SKMeasurementUnit
. Вторая строка method
сопоставляет первый параметр setMeasurementUnit
с тем же перечислением.
После внесения всех этих изменений можно использовать следующий код в Xamarin.Android, чтобы задать MeasurementUnit
:
realReachSettings.MeasurementUnit = SKMeasurementUnit.Second;
Итоги
В этой статье мы рассмотрели, как Xamarin.Android использует метаданные для преобразования определения API из формата AOSP Google. После внесения изменений, которые можно выполнить с помощью файла Metadata.xml, рассмотрены ограничения, обнаруженные при переименовании элементов и представлен список поддерживаемых атрибутов XML с описанием случаев, когда следует использовать каждый из них.