Xamarin.Android绑定JAR包编译问题分析(一)

戏子无情 提交于 2019-11-27 13:47:13

    在Xamarin.Android中,C#通过绑定调用JAVA类库,一种抽离JAVA原生接口的底层细节机制。Xamarin.Android提供了一个用于生成绑定的工具,此工具允许开发人员控制如何使用元数据创建绑定,比如改变程序的命名空间,成员重命名等。本文主要讨论元数据如何运作,总结元数据支持的属性以及阐述更改元数据解决绑定出现的问题。

概述   

    Xamarin.Android通过绑定生成器,自动完成一些绑定中的问题。当绑定JAVA类库时,Xamarin.Android将会自动检测JAVA类库中的所有类,并生成要绑定的所有包、所有类以及成员的列表。生成的APIs列表一般写在项目XML中,在Debug模式下,保存路径为project \obj\Debug\api.xml;在Release模式下,保存路径为project\obj\Release\api.xml。

194713_JeNy_3332863.png
    绑定生成器将使用api.xml文件作为生成必要的C #包装类指南,此XML文件的内容其实是谷歌Android开放源码的变体。下面的内容是摘自api.xml中的部分内容:

 194828_Zf0s_3332863.png

    在以上的例子中,api.xml在android包中声明了一个继承java.lang.Object的类Manifest。
    但是,在许多情况下,还是需要手动修改Metadata.xml文件,以便让绑定通过编译或者让JAVA更符合c#语法。比如,有必要更改JAVA包名,重命名一个类或者改变方法返回类型。
    然而,这些更改不是直接通过修改api.xml来实现。相反,这些改变一般都被记录在JAVA绑定类库特殊的xml文件中。当编译Xamarin.Android绑定程序集时,绑定生成器在创建绑定程序集时会受这些映射文件的影响。
    相关的XML映射文件一般保存在项目的Transforms文件中。
    MetaData.xml –允许修改最终API文件内容,比如修改已创建好的绑定的名字。
    EnumFields.xml—包含JAVA整型常量与C#枚举类型之间的映射。
    EnumMethods.xml –允许更改JAVA整型常量与C#枚举类型之间的映射方法的参数及返回类型。

    其中,MetaData.xml相对比较重要,因为其允许更改一些编译出现的普遍问题:
    ⦁    根据.NET语法,重命名类、方法以及属性等;
    ⦁    删除无关的类、方法或者属性等;
    ⦁    添加额外的支持类,使绑定符合.NET框架模式的设计。
MetaData.xml转换文件
    通过上面的介绍,我们知道,绑定生成器通过MetaData.xml文件影响绑定组件的创建。MetaData.XML是一个强大基于XPath语法的工具,可以修改、增加、隐藏及移除API文件中任务的元素。MetaData.XML中所有元素都包含一个路径属性,以便标识要应用该元素的节点。规则顺序如下所示:

  •     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接口:/interface[@name='AuthListener']
    定位JAVA类:/class[@name='MapView']
    定位JAVA类或者接口的相关方法:/class[@name='MapView']/method[@name='setTitleSource']

添加类型
    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相关的类型且不绑定。可以在MetaData.XML添加remove-node,代码如下所示:  

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

成员重命名

    直接编辑api.xml文件无法成员重命名,因为Xamarin.Android需要原JAVA原生接口的名称,所以//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,包装类绑定了一个不存在的Java类型,因此将无法访问Manifest的任何方法。正确地改变包装类型(方法)的托管名称,如下例所示使用managedName :  

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

包装类事件参数重命名
    当Xamarin.Android绑定生成器标识一个监听类型的onXXX方法,C#将生成一个子类即.NET API派生于Java监听基类。下面给出了一个Java类及其方法:

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

    Xamarin.Android将会去除以上方法的前缀on,使用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>

支持的属性
managedName
    用于更改包、类、方法或参数的名称,例如改变Java类MyClass的名字为Newclassname: 

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

    下例为将java.lang.object.toString方法重命名为Java.Lang.Object.NewManagedName的XPath表达式:

<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类型,这样将会造成编译错误“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签名不相容的变化)。注:C#不支持协变返回类型。下面是方法名为append ,将SpannableStringBuilder 改成 IAppendable。

<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;
    类名全部为小写字符,例如abc.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
    指定当方法映射到事件时,方法的参数应为发送方参数。例如:   

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

visibility
    此属性用于更改类、方法或属性的可见性。例如,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 and EnumMethods.XML
    有些情况下,Android库使用整型常量来表示传递给库相关属性或方法的状态。在许多情况下,C#中的枚举就是和整型常量一一对应。为了有效使用这一映射,可以在绑定的项目文件使用enumfields.xml和enummethods.xml。
    EnumFields.XML定义枚举,一般是Java整型常量和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 ,并在C#命名空间为 Skobbler.Ngx.Map.RealReach下定义了一个枚举SKRealReachSettings。field 实体中定义了Java常量(例如UNIT_SECOND)和C#枚举实体名(Second),以及两个实体的值。
EnumMethods.XML定义Getter/Setter方法,允许改变Java整型常量和C#枚举之间的映射方法的参数及返回类型。

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


总结
    本文主要讨论的是Xamarin.Android使用元数据解决编译问题。希望大家能够给点改进意见。

转载于:https://my.oschina.net/GloryMK/blog/857179

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!