Xamarin.Android的c#代码通过绑定调用Java库,这是一种机制,抽象的底层细节中指定 Java Native Interface(JNI)。Xamarin.Android提供了一个工具生成这些绑定。
这个工具允许开发人员使用Metadata 控制如何创建绑定,它允许程序修改命名空间和重命名成员。本文讨论Metadata是如何工作的,简述Metadata支持的 属性,
并解释如何通过修改此Metadata解决绑定问题。
概述:
Xamarin.Android Java绑定库在现有Android库的帮助下绑定生成器工具试图自动化所需的大部分工作。 当绑定一个Java库,Xamarin.Android将检查Java类和生成的列表所有的包、类型和成员被绑定。 这个api列表存储在一个XML文件,在这里:项目路径\obj\Debug\api.xml 和 项目路径\obj\Release\api.xml
绑定生成器将使用api.xml文件作为指导原则生成必要的c#包装类。这个XML文件的内容 是谷歌的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声明一个类在Android包中命名继承java.lang.object的Manifest。 在许多情况下,人们希望Java API更像”.NET“或纠正从编译生成绑定的问题。
例如,可能需要改变Java包名到.Net命名空间、重命名一个类,或者改变方法的返回类型。 这些修改并不是通过直接修改api.xml。相反修改通过特殊的Java绑定库模板XML文件。 当编译Xamarin.Android绑定assembly,绑定生成器将受到这些映射文件的影响。
在创建绑定assembly时这些XML映射文件可以在项目的Transforms目录下找到:
MetaData.xml – 允许更改最后生成的API,如改变绑定生成的命名空间。
EnumFields.xml – 包含在Java常量和C#枚举间映射。
EnumMethods.xml – 允许改变方法参数和返回值类型从Java int型常量到C#枚举。
MetaData.xml 是这些文件中最重要的,因为它允许对绑定的一般更改,如:
重命名命名空间、类、方法或字段以遵从.Net规则。
删除没有必要的命名空间、类、方法或字段。
移动类到不同的命名空间。
添加额外支持类使设计遵从.NET framework模式。
让我们继续讨论 Metadata.xml 的更多细节
Metadata.xml 文件使用绑定生成器来影响绑定的assembly创建。 Metadata格式使用XPath语法和几乎以GAPI Metadata指南中相同的GAPI Metadata描述。该文件是一个 强大的基于XPath的机制改变、添加、隐藏,或在API文件中移到任何元素或属性。Metadata规范所有的规则元素 包括路径属性来确定节点被应用的规则。
按照以下顺序应用规则:
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>
下面的表格列出了为Java API更多通用XPath elements的一部分:
Common Path Description
interface 用于定位一个 Java 接口. e.g. /interface[@name='AuthListener'].
class 用于定位一个类 . e.g. /class[@name='MapView'].
method 用于定位一个类或接口中的方法 . e.g. /class[@name='MapView']/method[@name='setTitleSource'].
parameter 确定方法的一个参数. e.g. /parameter[@name='p0']
增加类型:
添加一个节点将告诉 Xamarin.Android 绑定项目到一个 wrapper class 到 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元素到metadata.xml文件:
<remove-node path="/api/package[@name='{package_name}']/class[@name='{name}']" />
重命名成员:
重命名成员不能通过直接编辑api.xml文件,因为Xamarin.Android需要原始的Java Native Interface(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 { ... }
注意包装类已经被重命名为新名称,而原始的Java类型仍Manifest。它不再是可能的Xamarin.Android绑定类访问android.Manifest上的任何方法。
包装类绑定到一个不存在的Java类型。
改变包装类型的managed名称(或方法),必须设置managedName属性,如本例所示:
<attr path="/api/package[@name='android']/class[@name='Manifest']"
name="managedName">NewName</attr>
重命名EventArg包装类
当Xamarin.Android 绑定生成器确定一个onXXX setter method 为一个侦听器类型,一个c#事件和EventArgs子类将基于java的监听器模式生成一个.NET 风格 API
例如参考以下Java类和方法:
com.someapp.android.mpa.guidance.NavigationManager.on2DSignNextManuever(NextManueverListener listener);
Xamarin.Android 将前缀 on from the setter method and instead use 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.
argTypes
这个属性是放在setter方法名称EventArg子类将生成支持Java侦听器。下面更详细的部分重命名EventArg包装类在本指南后面描述。
eventName
为一个事件指定名称,如果为空将阻止事件产生更多描述见 EventArg 包装类章节。
managedName
用于更改包名、类名、方法名或参数名。例如改变Java类MyClass 为 NewClassName:
<attr path="/api/package[@name='com.my.application']/class[@name='MyClass']"
name="managedName">NewClassName</attr>
The next example illustrates an XPath expression for renaming the method java.lang.object.toString to Java.Lang.Object.NewManagedName:
<attr path="/api/package[@name='java.lang']/class[@name='Object']/method[@name='toString']"
name="managedName">NewMethodName</attr>
managedType
用于改变一个方法的返回值类型。在某些情况下绑定生成器将错误地推断Java方法的返回类型,这将导致编译时错误。
在这种情况下,一个可能的解决办法是改变方法的返回类型。
例如,绑定生成器认为Java方法 de.neom.neoreadersdk.resolution.compareTo() 将返回int类型,这将导致错误消息。
Error CS0535: `DE.Neom.Neoreadersdk.Resolution' does not implement interface member `Java.Lang.IComparable.CompareTo(Java.Lang.Object)'.
下面的代码片段演示了如何改变生成c#方法的返回类型从int到一个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签名)
在接下来的例子中,附加方法的返回类型由SpannableStringBuilder改为IAppendable(回想一下,c#不支持covariant返回类型):
<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 $, i.e. a$.class
类名是小写字符的完全损坏, i.e. a.class
这段代码是如何生成的一个"un-obfuscated" C#类型的例子 :
<attr path="/api/package[@name='{package_name}']/class[@name='{name}']"
name="obfuscated">false</attr>
propertyName
这个属性可以用来改变managed property的名称。使用propertyName的特殊例子涉及一个Java类的情况只有一个字段的getter方法。
在这种情况下绑定生成器想创建一个只写属性,在.net中是让人泄气的。以下代码片段显示了如何“remove”.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>
注意setter和getter方法仍将由绑定生成器创建。
sender
当方法映射到一个事件时指定一个方法的参数是sender参数。值可能为true或false。
例如:
<attr path="/api/package[@name='android.app']/
interface[@name='TimePickerDialog.OnTimeSetListener']/
method[@name='onTimeSet']/
parameter[@name='view']"
name="sender">true</ attr>
visibility
这个属性是用来改变类、方法或property的可见性。例如,可能有必要调整protected型Java方法为相应的c# wrapper 是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 and EnumMethods.xml
有情况下Android库使用整形常量来表示状态传递给属性或库的方法。 在许多情况下,绑定这些整形常量到c#枚举是有用的。为了生成这种映射,使用EnumFields.xml和EnumMethods.xml文件在绑定项目中。
用 EnumFields.xml 定义枚举
EnumFields.xml文件包含在Java 整形常量和 C# 枚举间映射。让我们看下面用c# enum创建一组整数常量的例子:
<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>
这里我们在命名空间Skobbler.Ngx.Map.RealReach中定义一个Java 类 SKRealReachSettings 和一个叫做SKRealReachSettings的C#枚举。
字段定义Java常数的名称(例如UNIT_SECOND),enum条目的名称(Second),和两个实体所表示的整数值(0)。
用 EnumMethods.xml 文件定义Get/Set方法
EnumMethods.xml文件允许改变方法参数和返回类型从Java整形常量到c#枚举。
换句话说,它将映射c#枚举的读和写(EnumFields.xml文件中定义)到Java 整形常量的get和set方法。
鉴于上述SKRealReachSettings枚举定义,以下EnumMethods.xml文件将定义这个枚举的get/set访问器:
<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方法的返回值映射到SKRealReachSettings枚举。
第二个method将setMeasurementUnit第一个参数映射到相同枚举。
所有的这些变化,你可以在Xamarin.Android使用下面代码设置MeasurementUnit:
realReachSettings.MeasurementUnit = SKMeasurementUnit.Second;
总结
本文讨论了如何Xamarin.Android使用metadata将Google AOSP格式变换为API定义的格式。
覆盖变化可以使用metadata.xml,它检查当重命名成员遇到局限性时,提出了支持xml属性的列表的各个属性被应用时的描述。