共用方式為


Java 繫結中繼資料

重要

我們目前正在調查 Xamarin 平臺上的自定義系結使用方式。 請接受 這項調查 ,以通知未來的開發工作。

Xamarin.Android 中的 C# 程式代碼會透過系結呼叫 Java 連結庫,這是一種機制,可抽象化 Java Native Interface (JNI) 中指定的低階詳細數據。 Xamarin.Android 提供產生這些系結的工具。 此工具可讓開發人員控制如何使用元數據建立系結,以允許修改命名空間和重新命名成員等程式。 本文件討論元數據的運作方式、摘要說明元數據支援的屬性,並說明如何藉由修改此元數據來解決系結問題。

概觀

Xamarin.Android Java 系結連結庫會嘗試自動化系結現有 Android 連結庫 所需的大部分工作,有時稱為 系結產生器的工具。 系結 Java 連結庫時,Xamarin.Android 會檢查 Java 類別,並產生要系結的所有套件、類型和成員清單。 此 API 清單會儲存在位於發行組建的 {project directory}\obj\Release\api.xml 和 {project directory}\obj\Debug\api.xml 偵錯組建的 XML 檔案中。

Location of the api.xml file in the obj/Debug folder

系結產生器會使用 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在名為 Manifest 的封裝中android宣告類別,以擴充 java.lang.Object

在許多情況下,需要人為協助,讓 Java API 感覺更像是 .NET,或修正導致系結元件無法編譯的問題。 例如,可能需要將 Java 套件名稱變更為 .NET 命名空間、重新命名類別,或變更方法的傳回類型。

直接修改 api.xml 無法達成這些變更。 相反地,變更會記錄在 Java 系結連結庫範本所提供的特殊 XML 檔案中。 編譯 Xamarin.Android 系結元件時,系結產生器會在建立系結元件時受到這些對應檔的影響

您可以在專案的 Transforms 資料夾中找到這些 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 類型,而不是系結它。 這可藉由將 XML 元素新增 remove-nodemetadata.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 類型。

若要正確變更包裝類型 (或 方法) 的 Managed 名稱,您必須設定 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 會從 setter 方法卸除前置 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>

支援的屬性

下列各節說明轉換 Java API 的一些屬性。

argsType

這個屬性會放在 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>

下一個範例說明將 方法 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 ,並採用 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# 不支援 covariant 傳回型別):

<attr path="/api/package[@name='android.text']/
    class[@name='SpannableStringBuilder']/
    method[@name='append']" 
    name="managedReturn">Java.Lang.IAppendable</attr>

混淆

混淆 Java 連結庫的工具可能會干擾 Xamarin.Android 系結產生器及其產生 C# 包裝函式類別的能力。 模糊類別的特性包括:

  • 類別名稱包含 $,也就是 a$.class
  • 類別名稱完全遭到小寫字元入侵,亦即 a.class

此代碼段是如何產生「未混淆」C# 類型的範例:

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

propertyName

這個屬性可用來變更 Managed 屬性的名稱。

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

請注意,Bindings Generator 仍會建立 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.xmlEnumMethods.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 常數的名稱(範例)、列舉項目的名稱(exampleUNIT_SECONDSecond),以及兩個實體所代表的整數值(範例0)。

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

EnumMethods.xml檔案允許將方法參數和從 Java int 常數傳回類型變更為 C# enums。 換句話說,它會將 C# 列舉的讀取和寫入對應至 Java int 常數getset方法(定義於 EnumFields.xml 檔案中)。

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 屬性清單,描述應該使用每個屬性的時機。