インサイド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の制限事項についても解説する。