Java 绑定元数据

重要

我们当前正在调查 Xamarin 平台上的自定义绑定使用情况。 请参与此调查,告诉我们将来应该进行哪些开发工作。

Xamarin.Android 中的 C# 代码通过绑定调用 Java 库,这些绑定是一种机制,用于提取 Java 本机接口 (JNI) 中指定的低级别详细信息。 Xamarin.Android 提供生成这些绑定的工具。 使用此工具,开发人员可以控制如何使用元数据创建绑定,进而允许修改命名空间和重命名成员等过程。 本文档讨论元数据的工作原理,概述了元数据支持的属性,并说明如何通过修改此元数据来解决绑定问题。

概述

Xamarin.Android Java 绑定库尝试通过一个有时被称为“绑定生成器”的工具来自动完成绑定现有 Android 库所需的大量工作。 绑定 Java 库时,Xamarin.Android 将检查 Java 类并生成所有要绑定的包、类型和成员的列表。 此 API 列表存储在一个 XML 文件中,该文件可在 {project directory}\obj\Release\api.xml (适用于 RELEASE 版本)和 {project directory}\obj\Debug\api.xml (用于 DEBUG 版本)中找到。

api.xml文件在 obj/Debug 文件夹中的位置

绑定生成器将使用 api.xml 文件作为指导来生成所需的 C# 包装类。 此 XML 文件的内容是 Google Android 开源项目格式的变体。 以下代码片段是 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 在扩展 java.lang.Objectandroid 包中声明一个 Manifest 类。

在许多情况下,需要人工协助使 Java API 感觉更像 .NET 或修正阻止绑定程序集编译的问题。 例如,可能需要将 Java 包名称更改为 .NET 命名空间、重命名类或更改方法的返回类型。

不能通过直接修改 api.xml 来实现这些更改。 而要在“Java 绑定库”模板提供的特殊 XML 文件中记录更改。 在编译 Xamarin.Android 绑定程序集时,绑定生成器在创建绑定程序集时将受到这些映射文件的影响

可以在项目的“转换”文件夹中找到这些 XML 映射文件:

  • 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 元数据指南中介绍的 GAPI 元数据几乎相同。 此实现几乎是 XPath 1.0 的完整实现,因此支持 1.0 标准版中的项。 此文件是一种功能强大的基于 XPath 的机制,可用于更改、添加、隐藏或移动 API 文件中的任何元素或属性。 元数据规范中的所有规则元素都包含一个路径属性,用于标识要向其应用规则的节点。 这些规则按以下顺序应用:

  • add-node – 将子节点追加到由 path 属性指定的节点。
  • attr – 设置由 path 属性指定的元素的属性的值。
  • 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>

下面列出了 Java API 一些比较常用的 XPath 元素:

  • 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 类型,而不绑定它。 这是通过将 remove-node XML 元素添加到 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 setter 方法时,将生成一个 C# 事件和 EventArgs 子类,以支持基于 Java 的侦听器模式的 .NET 风格的 API。 例如,请考虑下面的 Java 类和方法:

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

Xamarin.Android 会将前缀 on 从 setter 方法中删除,将 2DSignNextManuever 用作 EventArgs 子类的名称的基础。 子类名称如下所示:

NavigationManager.2DSignNextManueverEventArgs

这不是合法的 C# 类名。 若要更正此问题,绑定作者必须使用 argsType 属性,并为 EventArgs 子类提供有效的 C# 名称:

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

支持的属性

以下各节介绍用于转换 Java API 的一些属性。

argsType

此属性位于 setter 方法上,用于命名将为支持 Java 侦听器而生成的 EventArg 子类。 这将在本指南后面重命名 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 方法的返回类型,这将导致编译时错误。 在此情况下,一种可行的解决方案是更改方法的返回类型。

例如,绑定生成器认为 Java 方法 de.neom.neoreadersdk.resolution.compareTo() 应返回 int 并采用 Object 作为参数,这将导致错误消息“错误 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# 包装类的功能。 混淆类的特征包括:

  • 类名包括 $,即 a$.class
  • 类名完全由小写字母组成,即 a.class

此代码片段举例说明如何生成“非混淆”C# 类型:

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

propertyName

此属性可用于更改托管属性的名称。

使用 propertyName 的一种特殊情况涉及到 Java 类只有一个 getter 方法用于字段的情况。 在这种情况下,绑定生成器将需要创建只写属性,而在 .NET 中不建议此做法。 以下代码片段演示了如何通过将 propertyName 设置为空字符串来“删除”.NET 属性:

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

请注意,setter 和 getter 方法仍将由绑定生成器创建。

sender

当方法映射到事件时,指定方法的哪个参数应为 sender 参数。 值可以是 truefalse。 例如:

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

可见性

此属性用于更改类、方法或属性的可见性。 例如,可能需要升级 protected Java 方法,使其对应的 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 文件包含 Java int 常量和 C# enums 之间的映射。 下面的示例演示了如何为一组 int 常量创建 C# 枚举:

<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,并在命名空间 Skobbler.Ngx.Map.RealReach 中定义了一个名为 SKMeasurementUnit 的 C# 枚举。 field 条目定义 Java 常量的名称(示例 UNIT_SECOND)、枚举条目的名称(示例 Second)以及由两个实体表示的整数值(例如 0)。

使用 EnumMethods.xml 方法定义 Getter/Setter 方法

EnumMethods.xml 文件允许更改方法参数,并将返回类型从 Java int 常数更改为 C# enums。 换句话说,它将 C# 枚举的读取和写入(在“EnumFields”文件中定义)映射到 Java int 常量 getset 方法。

根据上面定义的 SKRealReachSettings 枚举,以下“EnumMethods.xml”文件将为此枚举定义 getter/setter:

<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 如何使用元数据从 GoogleAOSP 格式转换 API 定义。 在介绍了使用 Metadata.xml 可以进行的更改之后,本文研究了重命名成员时会遇到的限制,并展示了支持的 XML 属性列表,描述应在什么情况下使用相应的属性。