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 檔案中。
系結產生器會使用 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-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 類型。
若要正確變更包裝類型 (或 方法) 的 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
參數。 值可以為 true
或 false
。 例如:
<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 常數的名稱(範例)、列舉項目的名稱(exampleUNIT_SECOND
Second
),以及兩個實體所代表的整數值(範例0
)。
使用 EnumMethods.xml 定義 Getter/Setter 方法
EnumMethods.xml檔案允許將方法參數和從 Java int
常數傳回類型變更為 C# enums
。 換句話說,它會將 C# 列舉的讀取和寫入對應至 Java int
常數get
和set
方法(定義於 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 如何使用元數據從 Google AOSP 格式轉換 API 定義。 在涵蓋使用 Metadata.xml 的可能變更之後,它會檢查重新命名成員時遇到的限制,並呈現支援的 XML 屬性清單,描述應該使用每個屬性的時機。