インサイドXamarin(8)
Xamarin.Androidで使用するライブラリ
Androidの.NET APIに相当する「Mono.Android.dll」の特徴と注意事項、さらにAndroidサポートパッケージやGoogle Play Servicesについて説明する。
第7回(=前回: Xamarin.Androidの基本的な仕組み)からはXamarin.Androidについて取り上げている。今回はXamarin.Androidの主要なクラスライブラリについて説明する。
Xamarin.Androidの主要なクラスライブラリ
Mono.Android.dll: Androidの.NET API
Mono.Android.dllファイルは、Javaで作られたAndroid APIに対する.NETバインディングだ。JavaのAPIになじみがあれば、あまり違和感なく使用できるだろう。この.NET APIの特徴と注意事項をいくつか挙げておこう。
- バインディングAPIの名前空間は、.NETらしいPascal形式の命名規則(PascalCase)になる。名前空間によっては、クラス名と衝突するので、基本的に適宜名前を変更されている(例:
android.view
パッケージはAndroid.Views
名前空間になる。Android.View
だと、その中のView
クラスと衝突してしまうため)。これは、完全に行われていないという問題はあるが、API互換性のために維持されている(Java.Lang.Annotation
名前空間など。C#やF#のような、衝突しても大文字・小文字を区別する言語では、重大な問題にはならない)。名前空間のマッピングは、バインディングのDLL上でNamespaceMappingAttribute
という属性で記録されている。
- Javaインターフェースに対応する型の名前には、“I”がプレフィックスされる(ただし
android.os.IBinder
インターフェースなど、すでに“I”が付いているものは対象外である)。また、後に重要な話になるが、全てのJavaインターフェースはAndroid.Runtime.IJavaObject
インターフェースを継承する。
- メンバーの型に使用されているJavaのプリミティブ型は、対応するC#のプリミティブ型に置き換えられる。また、
java.lang.String
型は、System.String
型に置き換えられる。メソッド呼び出しなどにおいては、これはjava.lang.String
型に変換されてJava側で処理される。なおJava.Lang.String
は、クラスとして存在するし、オブジェクトとして生成され得る。
java.lang.Object
型はJava.Lang.Object
型あるいはAndroid.Runtime.IJavaObject
型にマッピングされる。Java.Lang.Object
クラスはSystem.Object
クラスから派生するが、その使用がSystem.Object
にマッピングされることはない。Java.Lang.Object
は、JavaのAPIには無いSystem.IDisposable
インターフェースの実装となっており、これがオブジェクトリソースの解放を補助する。IJavaObject
インターフェースは、Java上の引数がインターフェースである場合など、Java.Lang.Object
を使用できない場合に使用される。
- Android APIでJavaインターフェースとして定義された引数の型を自分で実装する場合、そのクラスは
Java.Lang.Object
クラスから派生させなければならず、絶対に自分でAndroid.Runtime.IJavaObject
インターフェースのメンバーを実装してはならない。何も考えずにJavaインターフェースを実装しようとすると、IJavaObject
のメンバーが実装されていないためにコンパイルエラーが発生するはずだ。Java.Lang.Object
クラスから派生させるだけで、この問題は解決する。
- Javaの
XxxListener
(以降、「Xxx……」部分は任意の名前)に相当するインターフェースは、その型を引数に取るsetOnXxxxListener()
のようなメソッドで使用されると、そのメソッドに対応するイベント(event)と、そのインターフェースに対応するEventArgs
クラスを自動生成する(メソッドも残るので、リスナーインターフェースを実装するアプローチでコードを書くこともできる)。例:
android.vew.View.
インターフェースOnTouchListener
→Android.Views.View.
クラスの自動生成TouchEventArgs
android.view.View.
メソッド使用setOnTouchListener()
→Android.Views.View.
イベントTouch (= EventHandler<Android.Views.View.
型)の自動生成TouchEventArgs>
- Javaの
getXxx()
は、プロパティ(=「get_」メソッド)に置き換えられる。もし同じ型の引数1つだけを取るsetXxx(...)
メソッドも存在すれば、それも(「set_」メソッドに)置き換えられる。イベントリスナーなど、setXxx(...)
しかないものは変換されない。
- Javaのフィールドについて。
final
フィールド、すなわち定数は、C#のconst
として生成される。const
でない定数は、Dalvikからもmonoからも更新されるかもしれず、Dalvikはmonoの値を見に行くようにはできないので、プロパティとして生成される(プロパティ・アクセサー・メソッドは、JNI(Java Native Interface)経由でフィールドを更新する)。
enum
(=列挙体)の使い方について。Android Java APIではint
型のfinal
フィールドが列挙値の代わりに多用されている。Xamarinでは、これらのうち、enum
に変換した方がよさそうなものについては、enum
型を生成してマッピングしている(マッピング作業自体は自動化できないため、手作業で行っている)。IDEでコードを書いていると、メソッドの引数を指定する場合やプロパティへの代入式を書くときに、そのenum
型から自動補完候補が表示されるだろう。一方、JavaのEnum
(=java.lang.Enum
)は、定数への自動的な変換は一切行っていない。JavaのEnum
は通常のクラス同様、拡張して独自のメソッドを定義可能で、これらを.NETのenum
に変換することはできないためだ。各Enum
型の整数値を返すメソッドやフィールドは、手作業でマッピングされたC#enum
を返すこともある。
- メソッドのいくつかについては“asyncification”(=非同期化)が行われ、C# 5.0の
async
/await
キーワードを使用できるようTask
が返されるXxxAsync()
メソッドが追加されている。メソッドの選択基準は自明ではないが、主にI/Oストリームの読み書きなどに関連するメソッドについて導入されている。
- 基本的に、Javaにジェネリック(Generics)は存在しない(!)。もちろんこれは不正確な表現であり、実際には「Javaのジェネリック情報は消える」と言う方がより正しい(さらに正確を期するなら「ジェネリック情報の一部は消える」となる)。
javac
(=Javaコンパイラー)はジェネリック型を認識し、型引数の一致を検証しながらコンパイルするが、JVMはそうなっていない。これらのジェネリック引数型を使用するバインディングメンバーは、基本的には「Java.Lang.Object
として扱う」。たまにAndroid.Widget.AdapterView<T>
のようなクラスが見つかるが、これは画面設計に際してアダプター(Adapter)の実装を現実的にするために、手作業で追加されたクラスである。
java.util
パッケージに含まれるコレクション型(Collection
インターフェースやDictionary
抽象クラスなど)が使用されている場合、それらは.NETのSystem.Collections.IList
やSystem.Collections.IDictionary
などのインターフェースに変換される。実際に引数としてインスタンスを渡す場合は、Android.Runtime.JavaList<T>
クラスやAndroid.Runtime.JavaDictionary<T>
クラスを使うのが、パフォーマンス上、望ましい。
java.io.InputStream
およびjava.io.OutputStream
は、System.IO.Stream
にマッピングされる(その実装クラスがAndroid.Runtime
名前空間に存在する)。
- 現在のXamarin.Androidのバージョンでは、
org.xmlpull.XmlPullParser
が使用されていると、それらはSystem.Xml.XmlReader
に変換される。これは半ば歴史的な事情でそうなっている。かつてMono.Android.dllは「.NETに対応するAPIが存在するものはバインドしない」という方針で作られていた。XMLPullのAPIはそれに基づいて除外されていた。しかし、この方針によってandroid.content.res.Resource.getAnimation()
などのXmlPullParser
を返すメンバーが存在していない問題が発覚し、XmlPullParser
はXmlReader
にマッピングされることになった。やがてJavaバインディング・プロジェクト(次々回説明)でこのAPI除外ポリシーのデメリットが無視できなくなり、「基本的に、利用可能なAPIはバインドする」方針に転換した。この時点で、これらのAPIもXmlPullParser
に戻せたのだが、APIの互換性を維持するために、現在でもXmlReader
が使用されている。
android.graphics.Color
クラスの定数値が使われるような、色のint
値を使うフィールドやメソッドについては、Android.Graphics.Color
構造体への特殊なマッピングが行われる。
- Javaアノテーション(=
java.lang.annotation.Annotation
インターフェースや、その実装クラス)については、インターフェースやクラスを生成した後、対応する.NETの属性(=System.Attribute
派生クラス)を生成する。この属性は型やメンバーに設定でき、それにより、対応するJavaコードの生成時に、その属性の指定値に相当するアノテーションが生成されることになる。
以上、これらの特徴は、次々回で説明するJavaバインディング・プロジェクトで、自らJavaのAPIに対する.NETバインディングを作成する場合についても当てはまる。
Mono.Android.dllは、Java APIとのマッピングのみを含んでいるわけではない。AndroidManifest.xmlファイルを自動生成するために使用されるActivityAttribute
やServiceAttribute
、JNIの機能を呼び出すためのAndroid.Runtime.JNIEnv
や、コレクションのJNI相互運用に使用されるJavaList
やJavaDictionary
、XmlPullParser
と相互運用するXmlReader
の実装などが含まれている。
Xamarin.Android.Support.v*.dll
Androidの新しいバージョンで追加された機能の中には、実際には古いバージョンのAndroidがインストールされた端末でも動作するべきものが、少なからず存在する。それらが、単に「デバイス上にインストールされているAndroid APIに含まれないから」というだけで利用できない、というのでは、もったいない。
そこで、Androidでは、「サポートパッケージ」として、古い端末でも動作する新しいAPIの互換機能を提供している。具体的には、「android-support-v4.jar」「android-support-v13.jar」といったライブラリが、Android SDKコンポーネントとして存在している。
2016年現在、これらのサポートパッケージについては、NuGetパッケージでバインディングが提供されており、Xamarin StudioやVisual Studioの新規プロジェクトテンプレートからも、これらがデフォルトで参照されている(かつては、Xamarin.Androidに、このサポートパッケージに対応する「Mono.Android.Support.v4.dll」および「Mono.Android.Support.v13.dll」が、各API Levelに対応する形でインストールされていた)。
Google Play Services
Androidのライブラリでもう1つ重要なのは、Google Play Servicesのライブラリだろう。Google Mapsのビュー、Google Driveへのアクセス、Googleアカウント認証などの機能をAndroidクライアントとして利用するために必要になる。これも、NuGetパッケージを検索してインストールするのが一番簡単だ。
サポートライブラリのバインディングも、Google Play Servicesのバインディングも、ソースコードが公開されている。Xamarinがリリースしているバインディングは、XamarinComponentsというリポジトリからリンクされている。バインディングのパッケージは、必ずしも元のライブラリのリリースに合わせてタイムリーに更新されるとは限らないので(大抵の場合はAPIに変更が入るなどして、Xamarinのコンポーネント開発を担当しているエンジニアが互換性をなるべく維持するような調整を加えている)、自分でビルドしたい場合はそうすることも可能だ(もとより、ただのバインディングなので、誰でも自作することができた)。
ただし、Xamarin.Formsなど、他のライブラリと併用する場合は、それらのライブラリが前提としているバージョンのパッケージを使わないと、不整合が生じてビルド時あるいは実行時に予期しないエラーに陥る可能性が少なくない(現実によく観測されている)ので、使用するパッケージのバージョンには注意すべきである。
■
次回は、Xamarin.Androidアプリの作成/ビルド/実行とデバッグに関する重要ポイントついて解説する。
※以下では、本稿の前後を合わせて5回分(第6回~第10回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
6. Xamarin.iOSで使用するライブラリ
Xamarin.iOS解説の後編。iOSの.NET APIである「monotouch.dll」や、Xamarin.iOS向けの追加ライブラリなどについて説明。
8. 【現在、表示中】≫ Xamarin.Androidで使用するライブラリ
Androidの.NET APIに相当する「Mono.Android.dll」の特徴と注意事項、さらにAndroidサポートパッケージやGoogle Play Servicesについて説明する。
10. Xamarin.AndroidにおけるJava相互運用の仕組みと、Javaバインディング・プロジェクト
Xamarin.AndroidでJavaとの相互運用を実現するアーキテクチャについて、さらにメモリ管理などの注意点を説明。さらにXamarin.Androidの制限事項についても解説する。