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 にあり、デバッグ ビルドの場合は {project directory}\obj\Debug\api.xml にあります。
バインド ジェネレーターでは、必要な C# ラッパー クラスを生成するためのガイドラインとして、api.xml ファイルが使用されます。 この 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.Object
を拡張する Manifest
という名前の android
パッケージ内のクラスを宣言しています。
多くの場合、Java API が ".NET と同様" に感じられるようにするために、またはバインド アセンブリがコンパイルされない問題を修正するために、人間の介入が必要です。 たとえば、Java パッケージ名を .NET 名前空間に変更したり、クラスの名前を変更したり、メソッドの戻り値の型を変更したりすることが必要になる場合があります。
これらの変更は、api.xml を直接変更しても実現されません。 代わりに、変更は、Java バインド ライブラリ テンプレートによって提供される特殊な XML ファイルに記録されます。 Xamarin.Android バインド アセンブリをコンパイルするときに、バインド ジェネレーターは、バインド アセンブリを作成するときにこれらのマッピング ファイルの影響を受けます。
これらの XML マッピング ファイルは、プロジェクトの Transforms フォルダーにある場合があります。
MetaData.xml – 生成されたバインドの名前空間の変更など、最終的な API に変更を加えることができます。
EnumFields.xml – Java の
int
定数と C# のenums
の間のマッピングが含まれています。EnumMethods.xml – メソッドのパラメーターと戻り値の型を Java の
int
定数から C# のenums
に変更できます。
MetaData.xml ファイルは、バインドに対して次のような汎用的な変更を可能にするため、これらのファイルの中で最も重要です。
名前空間、クラス、メソッド、またはフィールドの名前を変更して、.NET の規則に従うようにする。
不要な名前空間、クラス、メソッド、またはフィールドを削除する。
クラスを別の名前空間に移動する。
バインドの設計が .NET のフレームワーク パターンに従うように追加のサポート クラスを追加する。
Metadata.xml について詳しく説明します。
Metadata.xml 変換ファイル
既に説明したように、ファイル Metadata.xml はバインド ジェネレーターによって使用され、バインド アセンブリの作成に影響を及ぼします。 メタデータ形式では XPath 構文が使用され、GAPI メタデータ ガイドで説明されている GAPI メタデータとほぼ同じです。 この実装は、XPath 1.0 のほぼ完全な実装であるため、1.0 標準の項目をサポートします。 このファイルは、API ファイル内の要素または属性を変更、追加、非表示、または移動するための強力な XPath ベースのメカニズムです。 メタデータ仕様のすべてのルール要素には、ルールが適用されるノードを識別するパス属性が含まれています。 ルールは以下の順序で適用されます。
- 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
要素は、api.xml に新しいラッパー クラスを追加するように Xamarin.Android バインド プロジェクトに指示します。 たとえば、次のスニペットは、コンストラクターと 1 つのフィールドを持つクラスを作成するようにバインド ジェネレーターに指示します。
<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>
型の削除
Java の型を無視してバインドしないように、Xamarin.Android のバインド ジェネレーターに指示することができます。 これを行うには、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
セッター メソッドを識別すると、Java ベースのリスナー パターン用の .NET 形式の API をサポートするために、C# イベントと EventArgs
サブクラスが生成されます。 例として、次の Java クラスとメソッドを考えます。
com.someapp.android.mpa.guidance.NavigationManager.on2DSignNextManuever(NextManueverListener listener);
Xamarin.Android では、セッター メソッドからプレフィックス on
が削除され、代わりに EventArgs
サブクラスの名前の基準として 2DSignNextManuever
が使用されます。 サブクラスには次のような名前が付けられます。
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
この属性は、Java リスナーをサポートするために生成される EventArg
サブクラスに名前を指定するセッター メソッドに配置されます。 詳細については、このガイドで後述する「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 メソッドの戻り値の型を誤って推論し、コンパイル時エラーが発生することがあります。 このような状況で考えられる解決策の 1 つは、メソッドの戻り値の型を変更することです。
たとえば、バインド ジェネレーターが、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 クラスにフィールドのゲッター メソッドのみが含まれている場合があります。 このような状況では、バインド ジェネレーターは書き込み専用のプロパティを作成する必要がありますが、これは .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>
セッターおよびゲッター メソッドは、バインド ジェネレーターによって引き続き作成されることに注意してください。
センダー
メソッドがイベントにマップされるときに、メソッドのどのパラメーターを 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
この属性は、クラス、メソッド、またはプロパティの可視性を変更するために使用されます。 たとえば、対応する C# ラッパーが public
になるように、protected
Java メソッドを昇格する必要がある場合があります。
<!-- 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 を使用したゲッター/セッター メソッドの定義
EnumMethods.xml ファイルでは、メソッドのパラメーターと戻り値の型を Java の int
定数から C# の enums
に変更できます。 言い換えると、C# 列挙型 (EnumFields.xml ファイルで定義されている) の読み取りと書き込みを、Java の int
定数の get
と set
メソッドにマップします。
上で定義されている SKRealReachSettings
列挙型を使用すると、次の EnumMethods.xml ファイルにより、この列挙型のゲッター/セッターが定義されます。
<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
列挙型にマップします。 2 番目の method
行は、setMeasurementUnit
の最初のパラメーターを同じ列挙型にマップします。
これらの変更をすべて設定すると、Xamarin.Android で次のコードを使用して、MeasurementUnit
を設定できます。
realReachSettings.MeasurementUnit = SKMeasurementUnit.Second;
まとめ
この記事では、Xamarin.Android でメタデータを使用して、API 定義を "Google AOSP 形式" から変換する方法について説明しました。 Metadata.xml を使用して可能な変更について説明した後は、メンバーの名前を変更するときに発生する制限を確認し、サポートされている XML 属性の一覧と、各属性を使用するタイミングを示しました。