Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
インサイドXamarin(9)

インサイドXamarin(9)

Xamarin.Androidアプリの開発

2016年11月4日 改訂 (初版:2014/4/2)

Xamarin.Androidアプリの作成/ビルド/実行とデバッグに関する重要ポイントついて解説する。

榎本 温(@atsushieno
  • このエントリーをはてなブックマークに追加

 第7回からはXamarin.Androidについて取り上げている。前回の「Xamarin.Androidの基本的な仕組み」に続き、今回はXamarin.Androidアプリケーションの開発について説明する。

Xamarin.Androidのアプリケーションの作成、ビルド、実行とデバッグ

Xamarin.Androidプロジェクトの種類とファイル構成

 Xamarin Studioにおける、次の[新しいソリューション]ダイアログ上のプロジェクトテンプレートを見ると分かるように、Xamarin.Androidのプロジェクトは、大別して以下の3つに分けられる。

  • 1アプリケーション・プロジェクト
  • 2ライブラリ・プロジェクト
  • 3Javaバインディング・プロジェクト
新しいプロジェクトの選択画面

 また、Androidカテゴリーとしては、

  • Aアプリケーション(App)
  • Bライブラリ(Library)
  • Cテスト(Tests)

の3種類に分類されている。

 Androidの1A)アプリケーションとしては、

  • 一般的なアプリケーション(一般的なモバイルWearWebViewといったテンプレートに分かれる)
  • OpenTKを使用したOpenGLアプリケーションGameと名付けられているが、要するにアプリケーションである)

の2種類が用意されている。アプリケーション・プロジェクトでは、IDEによるビルド時に、インストール可能なAndroidアプリケーションパッケージ(=.apkファイル)を作成する。

 2B)ライブラリ・プロジェクトは、Android用のモバイルプロファイルでDLLをビルドするために使う。この中にAndroidのUIで利用できるリソースを含めることなどもできる(再利用可能なウィジェットのライブラリなどで有用だ)。

 3B)Javaバインディング・プロジェクトは、既存のJavaライブラリ(.jarファイル)の.NET用APIを自動生成するためのライブラリ・プロジェクトで、そのビルドは他のプロジェクトとはだいぶ異なる。これについては後で詳しく説明する。

 Cテストには、

  • UIテスト
  • (一般的な)UnitTest

の2つがある。UIテストのテンプレートは、Xamarin.UITestというライブラリを使用しており、これはXamarin Test Cloudにもつながるものになるが、ローカルのみで動かすことも可能だ。また、アプリケーションのソリューションを新規作成する時に、UIテストのプロジェクトをチェックボックスひとつで追加することも可能だ。

 UnitTestプロジェクトは、NUnitLiteのUIがアプリケーションとして起動して、参照しているライブラリに含まれるテストを実行できる、という仕組みだ。ただ、これを使用する意義はほぼ無い。Xamarin.AndroidでNUnit3を使用するNuGetパッケージがコミュニティで公開されており、その新しいほうを使った方がよいだろう。

 Androidアプリケーションおよびライブラリで使用される「ビルドアクション」(MSBuildにおけるファイル種別のようなものだと考えてよい)は多数あるが、標準的なもの(CompileEmbeddedResourceNoneなど)に加えて、主に次のものが重要だ。

  • AndroidAsset Androidフレームワークにおけるアセット(Android.Content.Res.AssetManagerクラス経由でオープンできるアセットファイル)となる。
  • AndroidResource Androidフレームワークにおけるリソースとなる。アプリケーションのResource.Idクラスで整数値にマッピングされ、それらはAndroid.Views.View.FindViewById()などのメソッドに渡せる。
  • AndroidJavaLibrary アプリケーションのJavaコードのビルド時にコンパイルするJavaライブラリ
  • AndroidNativeLibrary アプリケーションにバンドルして実行時に動的にロードされるネイティブライブラリ。Android NDKでビルドされたLinux共有ライブラリである必要がある。さらに、ビルドしたターゲットのアーキテクチャ別にフォルダーを構成する必要がある(「libs/armeabi-v7a/*.so」「libs/x86/*.so」など)。
  • Embedded*Library 先頭に「Embedded」と付いているビルドアクションは、ライブラリ・プロジェクトに埋め込んで、アプリケーションのビルド時に含められるように、存在している。アプリケーション・プロジェクトで指定しても、.dllファイルのサイズが増えるだけで何ら意味がない。

 Androidアプリケーション・プロジェクトを新規作成すると、デフォルトで「Hello World」のメッセージを表示するボタンだけが入ったアプリケーションとなる。AndroidのAPIを学習しながら、これに手を加えていけばよいだろう。AndroidのJavaアプリケーションと異なり、Xamarin.AndroidではAndroidManifest.xmlファイルを手作業で書く必要はほとんどない。デフォルトのActivityクラスに以下のようなコードがあるはずだが、

C#
[Acvitity (Label = "Hello", MainActivity = true)]
Activityクラスのコードから抜粋

 こういった属性がAndroidManifest.xmlファイルの要素を自動生成してくれる(他にServiceAttributeUsesPermissionAttributeなどもある)。

 AndroidManifest.xmlファイルは、プロジェクトのプロパティでAndroid Applicationのセクションから生成・編集することもできる。明示的にAndroidManifest.xmlファイルをプロジェクトに含める場合でも、上記の属性に基づくXML要素は生成され、このAndroidManifest.xmlファイルにマージされて、アプリケーションにパッケージされる(ちなみにAndroidManifest.xmlファイルに対応するビルドアクションはなく、ここで生成されたファイルのパスが.csprojファイルのプロパティとして記録されるので、このファイルを移動すべきではない)。

API Levelについて

 Mono.Android.dllファイルは、2016年10月時点では、各API Levelに対して作成されている(全てではなく、10と15以降のみ)。このような構成は、Xamarin.iOSと異なっているが、これはAndroid SDKがAPI Level別にandroid.jarファイルを提供しているためだ。

 標準的なAndroidアプリケーションには「使用するプラットフォームを指定する」に関するオプションが3つ(targetSdkVersionminSdkVersioncompileSdkVersion)あって、ここはXamarin.Androidと少し異なる(ちなみに、実行時にバージョンをチェックする方法も同様だ)。

  • targetSdkVersion ビルド時に参照するandroid.jarのバージョンである。このプロファイルに無いAPIを使用していてもビルドエラーになる。
  • minSdkVersion 最古の対象バージョンを指定する。
  • compileSdkVersion XamarinではtargetSdkVersionがこれを兼ねるため指定しないが、ビルド用のSDKバージョンを指定するために用いられる。

 従来は、Xamarin.AndroidのデフォルトのtargetSdkVersionオプションの値は「Android 2.2(Froyo)」だったが、現在では「最新のプラットフォーム」という指定になっている。このオプションを使って、明示的なバージョンを指定することもできる。

【備考】 one dll to rule all

 Android SDKの「プラットフォーム別にandroid.jarを提供する」仕組みは、実のところ厄介なバージョン依存を引き起こしていた。かつてのアプリケーションは、まれに、しかしごくまれとは言えない範囲で、ビルドしたAPI LevelのMono.Android.dllに依存していた。各API Levelのandroid.jarにバインドするとき、Java側の継承関係の有無によって、Mono.Android.dllのメタデータが変化するためだ。具体的なデメリットとしては、ビルドする対象プラットフォーム(Android用語で言えば「targetSdkVersion」)で機能したコードが、最小レベルのプラットフォーム(Android用語で言う「minSdkVersion」)で機能せず、古いAPI Levelでのビルドが必要になってしまっていた。この問題は、古いプラットフォームにしか存在しないメンバーを新しいプラットフォームのMono.Android.dll上に無理やり復活させるというやり方で、Xamarin.Android 4.1.1で解決している

 開発陣では、Mono.Android.dllが多数配布される現状を好ましくないと考えていて、上記の問題が解決したこともあるので、将来的には、配布されるMono.Android.dllは最新API Levelの1つだけになる可能性もある。ただし、2014年の初稿執筆当時にも同じことを書いていて、いまだに実現していないという現状もある。

ビルドと目的別パッケージング方式

 Xamarin.iOS同様、Xamarin.Androidも、アプリケーションのデバッグビルドとリリースビルドは大きく異なる。Google PlayなどにリリースするAndroidアプリケーションは、限りなく小さい構成になっている必要がある。この最小構成を実現するために、Xamarin.Androidでもアセンブリのリンクを行う、不必要なコードを削除している。Android SDKでいえば、ProGuardがその機能を提供している。

 一方、デバッグビルドは、開発中に頻繁に行うものであり、ビルドしてデバッグするたびにリンクを行っていては、時間が無駄になる。アセンブリのリンクは、アプリケーションで直接/間接を問わず参照しているコードを全て解析することになり、少なからず時間のかかる作業である。そのため、デバッグビルドでは、(ユーザーが明示的に指定しない限り)アセンブリのリンクは行わない。そして、アプリケーションのパッケージにはXamarin.Androidフレームワークのファイルを一切含めない。

 ではフレームワークアセンブリはどのように読み込まれるのか。Xamarin.Androidは、デバッグ時に、「共有ランタイムShared Runtime)」と呼ばれる、ユーザーアプリケーションとは別のパッケージをインストールする。これは.apkファイルで30MB近くあるパッケージで、インストールするだけでもそれなりの時間がかかるが、1つのXamarin.Androidバージョン、1つのAndroidターゲット(エミュレーターあるいはデバイス)に付き、一度だけ行われる。バージョンアップがあれば、また対応する最新版がインストールされるが、それまでは同じものが、全てのアプリケーションのデバッグに使用される。このパッケージは、コンテントプロバイダーとして機能し、デバッグ用アプリケーションからの要求に応じて、必要なアセンブリを渡す仕事をしている。

 また、共有ランタイムとは別に、「プラットフォームパッケージ」というものがあって、これは、「特定のAPI Levelを対象とする」アプリケーション全てに共通して使用される。具体的には、Mono.Android.dllファイルやMono.Android.Support.v4.dllファイルなどが含まれている。これがインストールされるタイミングも、共有ランタイムとほぼ同様だ。

 デバッグビルドで登場するパッケージを簡潔にまとめると、下記のようになる。

  • アプリケーションのapk: アプリケーションのコードが含まれ、これが実行される。
  • 共有ランタイムのapk: 対象プラットフォームと無関係なアセンブリが含まれるコンテントプロバイダーとして機能する。
  • Androidプラットフォームのapk: 特定プラットフォームのみを対象とするアセンブリが含まれるコンテントプロバイダーとして機能する。

 このモデルの良いところは、アプリケーションの.apkファイルが格段に小さくなり、個々のデバッグビルドのデプロイメントが高速化されるということだ。

 さらに、Xamarin.Androidでは、この最適化に加えて、「Fast Deployment」というモードもサポートしている。このモードの実行モデルでは、デバッグ対象のアプリケーションが使用するファイルは、全てターゲット上の記憶領域にキャッシュされ、デバッグ前に、更新されたファイルのみがアップロードされ、アプリケーションパッケージ(apk)自体が更新されないので、デプロイメントはさらに高速になる。デフォルトでは、このFast Deploymentが有効になっている。

サポートしているモード Fast Deployment Embed Bundle
指定方法 デバッグのデフォルト デバッグのFast Deploymentをオフ リリースのデフォルト
想定用途 高速デバッグ (特にない) リリース
依存関係 共有ランタイム 共有ランタイム 無し(単独配布可能)
アセンブリのロード場所 Androidのローカルストレージ、共有ランタイム、プラットフォームapk アプリケーションapk、共有ランタイム、プラットフォームapk アプリケーションapk
アセンブリ縮小 しない しない する
アプリケーションの実行モデル

Xamarin.AndroidとInstant Run

 ちなみに、Android Studio 2.0では、Instant Runという新機能が追加され、これにはFast Deploymentに相当する機能も含まれている(特にcold swappingと呼ばれる部分)。ただし、Android Studioは全てJavaのコードを対象としている。

 Xamarin.Androidは、実のところJavaの生成コードへの変更を伴うFast Deploymentが十分ではない。具体的には、Javaコードに変更があると、APKの再インストールが発生してしまう。Instant RunではJavaの変更がAPK再インストールを引き起こさないため、Xamarin.Androidでもこの技術を調査して、cycle9と呼ばれるリリースから、Instant Runに相当する機能を実現している。ただし、これはstableリリースでは明示的にMSBuildプロパティAndroidFastDeploymentTypeAssemblies:Dexesという値を指定する必要がある。

 Instant Runについては、筆者が以前にまとめた解説を参考にされたい。

実行とデバッグ

 Androidアプリケーションの実行は、AndroidエミュレーターあるいはAndroidデバイス上で行う。アプリケーションのデプロイ方法は、基本的にはJavaアプリケーションと同じで、「adb(Android Debug Bridge)」というAndroid SDKのコマンドラインツールを利用する。これはIDE(Xamarin Studioなど)が行うので、アプリケーション開発者が通常、これを意識する必要はない(それとは無関係に、Android開発においては、adbコマンドの基本的な使い方を覚えておいても損はない)。

 デバッグにはadb logcatが不可欠だ。もっとも、例外メッセージは、IDEのアプリケーション出力のパッドにもAndroid Logのパッドにも出力されるので、やはり手作業でadbを起動する必要はない。

 Xamarin.Androidでのデバッグおよび実行について、念を押して忠告しておきたいことがある。それは、Xamarinのセットアップに含まれているARMベースのエミュレーターは使わない、ということだ。Android開発の最悪の側面は、このARMエミュレーターの致命的な遅さにある。Androidデバイスを使用するのが確実だが、Android SDKの追加コンポーネントである「Intel HAXM Accelerator」をインストールした上で、x86エミュレーターを利用する方がよい(デフォルトではインストールされていないので、別途、インストールする必要がある)。

 現在のXamarin.Androidでは、ARMエミュレーターを使おうとすると、「これを使うのは推奨しない」という警告が出るようになっている。Intel HAXM Acceleratorの存在を知らずに、重すぎるエミュレーターを使用して、それに失望する開発者が多すぎたためだ。

Resource.designer.cs: リソースデザイナーのビルド

 Androidフレームワークにおけるリソース(Resources)の扱いは、やや異色である。そのままアーカイブに含まれるアセットとは異なり、リソースは、全て独自のアーカイブ(=「packaged_resources」ファイル)にまとめられて、最終的なアプリケーションパッケージ(apk)に含まれる。そして、「R.java」というソースファイルが自動生成され、そのJavaパッケージ名は、Androidアプリケーション(あるいはライブラリ)のパッケージ名となり、それぞれのリソースのカテゴリーとファイル名に対応するint型の定数(=final変数)が生成される。例えば、「res/drawable/icon.png」というファイルがあれば、「R.java」ファイルには、R.drawableというクラスが生成され、その中にiconというメンバーが生成される。

 本来なら、このファイル名は小文字・数字・アンダースコア(_)から成る必要があるが、Xamarin.Androidでは、プロジェクトで指定されたファイル名に合わせて、適宜、大文字も含まれる名前を生成する。

 リソースの種類によっては、XMLで記述し、その内容にJavaクラスを要求するものがある。JavaのAndroidアプリケーションでは、拡張子は「.xml」とするが、Xamarin.Androidでは、「.axml」という拡張子が使われている。これは実は「.xml」でもよく、「.axml」だけに特別な処理を加えてビルドするわけではないのだが、Visual Studioではファイルを開くための動作がファイルの拡張子によって決まってしまうため、「.xml」全てをUIデザイナーに関連付けるわけにはいかず、仕方なく新しい拡張子を追加したものである。

 XMLリソースファイルは、そのまま「packaged_resources」ファイルにアーカイブされるわけではない。Xamarinアプリケーションの開発者は「このC#クラスのJava名は何だろう?」と意識してXMLファイルに明示的に記述したくはないだろう。そのため、Xamarin.Androidのビルド過程では、リソースの種類に応じて、XMLファイルを解析して(これは.axmlファイルでも.xmlファイルでも行われる)、その内容をJavaのAndroidフレームワークから問題なく呼び出せるように加工する。

 リソースデザイナー用の「Resource.designer.cs」ファイルは、プロジェクト上でリソースファイルに変更(追加・削除も含む)が加えられると、自動的に再ビルドされる仕組みになっている。また、(2014年2月の現状では)プロジェクトファイル(.csprojファイルなど)に、このファイルを明示的に指定するプロパティが存在しており、単純にIDE上で、手作業で削除・再追加などしても自動調整はされないので、注意が必要だ。

 また、F#では、JavaやC#のようなネストしたクラスを実現する方法が無いため、「Resource.designer.fs」ファイル上で定義される型の構成が異なる。

 次回は、Xamarin.AndroidにおけるJava相互運用の仕組みと、Javaバインディング・プロジェクトついて解説する。

※以下では、本稿の前後を合わせて5回分(第7回~第11回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

インサイドXamarin(9)
7. Xamarin.Androidの仕組みと、ソフトウェア構成

いよいよXamarin.Androidを取り上げる(全4回)。今回は、その基本的な仕組みやソフトウェア構成を説明。

インサイドXamarin(9)
8. Xamarin.Androidで使用するライブラリ

Androidの.NET APIに相当する「Mono.Android.dll」の特徴と注意事項、さらにAndroidサポートパッケージやGoogle Play Servicesについて説明する。

インサイドXamarin(9)
9. 【現在、表示中】≫ Xamarin.Androidアプリの開発

Xamarin.Androidアプリの作成/ビルド/実行とデバッグに関する重要ポイントついて解説する。

インサイドXamarin(9)
10. Xamarin.AndroidにおけるJava相互運用の仕組みと、Javaバインディング・プロジェクト

Xamarin.AndroidでJavaとの相互運用を実現するアーキテクチャについて、さらにメモリ管理などの注意点を説明。さらにXamarin.Androidの制限事項についても解説する。

インサイドXamarin(9)
11. Xamarin Studio/MonoDevelopの基本機能と、C#コーディング補助機能

MonoDevelopとXamarin Studioはどう違うのか? MonoDevelopの基本的な機能を解説。C#コーディング補助機能についても紹介する。

サイトからのお知らせ

Twitterでつぶやこう!