XamarinでAndroid WearのMessage APIを使ってみましたが、いろいろとあって忘れそうだったので、長いメモを残します。
なお、Android WearをWear、Android Handheld(今回の場合はNexus7の実機)をHandheldと呼ぶことにします。
また、メソッドなどが属している名前空間で悩んだため、ソースコードにはできるだけ名前空間を付けました。
環境
- Xamarin 評価版
- Xamarin 3.9.344.0
- Xamarin Studio 5.7.2 (build 7)
- Xamarin Android 4.20.0
- Windows7 x64
- Nexus7 2012
- Android 4.4
- Android Wearアプリをインストール済
- Google Playよりダウンロード・インストール
アプリの流れ
Message APIを使って、以下のような機能の流れを持つアプリを作ります。
- Wearにて、Handheldへメッセージを送信
- Handheldにて、メッセージを受信
- Handheldにて、メッセージを受信したタイミングで、Wearへメッセージを送信
- Wearにて、メッセージを受信
Xamarin評価版へのアップグレード
Xamarin Starterの環境にて、Android Wear Applicationのプロジェクトテンプレートをビルドしようとすると、以下のエラーが出ます(手元の環境では一部文字化けしていました)。
C:\Program Files (x86)\MSBuild\Xamarin\Android\Xamarin.Android.Common.targets(253,5): mandroid error XA9005: User code size, 3290112 bytes, is larger than 131072 and requires aツIndieツ(or higher) License. C:\Program Files (x86)\MSBuild\Xamarin\Android\Xamarin.Android.Common.targets(253,5): mandroid error XA9006: Using type `Android.Runtime.JNIEnv` requiresツIndieツ(or higher) License.
StarterではAndroid Wearで使うライブラリのサイズが大きくてビルドできないように見えました。
ただ、さすがにIndie以上のプランにアップグレードするのはいろいろと厳しいので、今回は評価版を使います。
なお、評価版へのアップグレードに少し悩みましたが、以下の記事を参考にしたらアップグレードできました。
Xamarin Studio Windows のみで Business 評価版を開始するには - Xamarin 日本語情報
Android SDK Manager によるインストール
Android Wearアプリを作成するのに必要なモノをインストールします。
Setup and Installation | Xamarin
空ソリューションの作成
深い意味はありませんが、今回はソリューション名とプロジェクト名を分けてみようと思い、その他 > 空のソリューション
で空のソリューションを作ります。
Android Wear側プロジェクトのひな形を作成
プロジェクトテンプレート選択
ソリューションの追加から、C# > Android > Android Wear Application
プロジェクト(今回のプロジェクト名はWear
)を追加します。
なお、Android SDK Platformがインストールされていない場合、このテンプレートが表示されませんでした。
Android manifestの追加
プロジェクトを作成した時点ではAndroid manifestが存在しないため、追加します。
- プロジェクト(Wear)の上で右クリックし、オプションを選択
- ビルド > Android Application
- 右側のペインの「Add Android manifest」ボタンをクリック
- Android manifest が追加されたら、「OK」ボタンを押す
Android manifestの編集
今回使用するMessage APIはGoogle Play Serviceを使いますが、デフォルトのままでは使用することができません。
そのため、以下の作業を行い追記します。
- Propertiesの下にあるAndroidManifest.xmlを選択・表示
- 下側にあるソースタブを押して、xmlソースを表示
- Wearからメッセージを送信するため、
<application android:label="Wear">
タグに、以下を追加
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
android - Adding Google Play services version to your app's manifest? - Stack Overflow
パッケージ名の変更
Message APIを使ってWearとHandheldで通信を行う場合、両プロジェクトで同じパッケージ名を使う必要があります。
そのため、AndroidManifest.xmlを編集するか、以下の作業にてパッケージ名を変更します(Handheld側は後述)。
- プロジェクトの上で右クリック、オプションを選択
- ビルド > Android Application
- Package nameを、
XamarinWearableMessageApiSample
へと変更- 今回の場合、元々は
Wear.Wear
となっているはず
- 今回の場合、元々は
また、クラスの名前空間(namespace
)やApplication nameは特に影響しないので、元のままにしておきます。
Wearアプリに、WearからHandheldへ送信する機能を追加
必要なインタフェースを追加
Android.Gms.Common.Apis
名前空間にあるインタフェースを3つ、MainActivityに実装します。
今のところは特に使わないので、空実装にしておきます。
public class MainActivity : Activity, Android.Gms.Common.Apis.IResultCallback, Android.Gms.Common.Apis.IGoogleApiClientConnectionCallbacks, Android.Gms.Common.Apis.IGoogleApiClientOnConnectionFailedListener { // IGoogleApiClientConnectionCallbacksインタフェース向け public void OnConnected (Android.OS.Bundle connectionHint) {} public void OnConnectionSuspended (int cause) {} // IGoogleApiClientOnConnectionFailedListenerインタフェース向け public void OnConnectionFailed (Android.Gms.Common.ConnectionResult result) {} // IResultCallbackインタフェース向け public void OnResult (Java.Lang.Object result) {} ... }
Google API Clientを追加
Message APIで使う、Google API Clientを追加します。
Clientはインスタンス変数として用意して、OnCreate
の中でGoogleApiClientBuilder
を使ってインスタンス化しています。
# MainActivityのインスタンス変数 Android.Gms.Common.Apis.IGoogleApiClient client; protected override void OnCreate (Bundle bundle) { base.OnCreate (bundle); // Set our view from the "main" layout resource SetContentView (Resource.Layout.Main); client = new GoogleApiClientBuilder (this) .AddApi (Android.Gms.Wearable.WearableClass.Api) .AddConnectionCallbacks (this) // Xamarinの場合はクラスがないので、このクラスのOnConnect系を使う .AddOnConnectionFailedListener (this) // 同上 .Build (); client.Connect (); ... }
今回は生成直後にConnect()
メソッドを使っていますが、場合によっては別のところで記述します(後述の参考資料など)。
また、リスナーやコールバックをMainActivity自身に実装し、それを渡しています。今回の例ではリスナーやコールバックを使っていないため、削除しても良いかもしれません。
WearableClass.MessageApi.SendMessage
の実装前に考えたこと
WearableClass.MessageApi.SendMessage
を実装する場合、送信先のNodeIDを取得する必要があります。
それを
var nodes = WearableClass.NodeApi.GetConnectedNodes(client).Await().JavaCast<INodeApiGetConnectedNodesResult>();
のようにしてUIスレッド上で取得しようとすると、以下のようなエラーが発生します。
[MonoDroid] UNHANDLED EXCEPTION: [MonoDroid] Java.Lang.IllegalStateException: Exception of type 'Java.Lang.IllegalStateException' was thrown. ... [MonoDroid] java.lang.IllegalStateException: await must not be called on the UI thread
そのため、非同期で取得する必要がありますが、今回は以下の2つの方法を試してみます。
MainActivityのLayoutを変更
2つの方法を実装する前に、各方法でメッセージを送信できるようボタンを2つ用意し、クリック時にそれぞれの方法で送信できるようにレイアウトを変更します。
Layoutの修正
layoutディレクトリの下に3ファイルありますが、関係するのは以下の2つになります。
- RectangleMain.axml
- RoundMain.axml
今回のエミュレータはSquare型のSkinにするためRectangleMain.axmlを修正しますが、念のためRoundMain.axmlも書いておきます。なお、内容は両方とも一緒です。
<LinearLayout ... <LinearLayout android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"> <Button android:id="@+id/AsyncAwait" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/async_await" /> <Button android:id="@+id/AsyncTask" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/async_task" /> </LinearLayout> </LinearLayout>
合わせて、ボタンにテキストを付けるために、values\String.xml
も追記します。
<resources> ... <string name="async_await">await</string> <string name="async_task">task</string> ... </resources>
AsyncTaskによるWearableClass.MessageApi.SendMessage
の実装
Android.OS.AsyncTask
を継承したクラスを作成
Javaと同じように、SendMessageAsyncTask
クラスを作成します。
このクラスは、
Android.OS.AsyncTask
を継承- 対象のActivityを入れておく
Activity
プロパティを用意 DoInBackground
メソッドにて、Activityに用意したUIスレッドで動かせないメソッドを実行
という内容を実装します。
public class SendMessageAsyncTask : Android.OS.AsyncTask { public MainActivity Activity { get; set; } protected override Java.Lang.Object DoInBackground (params Java.Lang.Object[] parameters) { if (Activity != null) { var nodes = Activity.NodeIds; foreach (var node in nodes) { Activity.SendMessage (node); } } return null; } }
MainActivityに実装を追加
SendMessageAsyncTaskで必要なプロパティ(NodeIds)とメソッド(SendMessage)を追加します。
NodeIdsプロパティでは、送信先のNodeIdを列挙して返すようにします。
なお、そのままではINodeApiGetConnectedNodesResult
型として取得できないことから、JavaCastを使用してキャストしておきます。
public ICollection<string> NodeIds { get { var results = new HashSet<string> (); var nodes = Android.Gms.Wearable.WearableClass.NodeApi.GetConnectedNodes (client) .Await () .JavaCast<Android.Gms.Wearable.INodeApiGetConnectedNodesResult>(); foreach (var node in nodes.Nodes) { results.Add (node.Id); } return results; } }
SendMessageメソッドでは、列挙されたNodeIdに対してメッセージを送信します。
MessageTagは送受信側で同じ値を使えば、メッセージを識別することができます。
また、送信データはバイト配列にする必要があるため、適当なエンコーディング(今回はUTF8)を使ってバイト配列にします。
private const string MessageTag = "hoge"; public void SendMessage(String node) { WearableClass.MessageApi.SendMessage (client, node, MessageTag, System.Text.Encoding.UTF8.GetBytes ("async_task")); }
以上が、Android.OS.AsyncTaskを使った場合の送信方法となります。
C#のasync/awaitによるWearableClass.MessageApi.SendMessage
の実装
C#っぽくasync/awaitを使います。今回はいずれもMainActivityに実装します。
まず、前述のNodeIdsプロパティでawaitできるよう、TaskでラップしたGetNodeIdsAsyncメソッドを用意します。
public Task<ICollection<string>> GetNodeIdsAsync() { return Task.Run(() => NodeIds); }
次に、awaitするSendMessageAsyncメソッドを用意します。
public async void SendMessageAsync() { var nodeIds = await GetNodeIdsAsync(); foreach (var nodeId in nodeIds) { WearableClass.MessageApi.SendMessage(client, nodeId, MessageTag, Encoding.UTF8.GetBytes("async_await")); } }
以上が、C#のasync/awaitを使った場合の送信方法となります。
ボタンクリック時の動作を追加
レイアウトに追加したボタンを押した時に、メッセージ送信機能が実行されるように実装します。
Xamarinではいくつかの書き方があるようですが、今回はラムダ式にて実装します。
Handle Clicks - Xamarin
// C#のasync/awaitを使う方法 var asyncAwait = FindViewById<Button> (Resource.Id.AsyncAwait); asyncAwait.Click += (sender, e) => SendMessageAsync (); // AndroidのAsyncTaskを使う方法 var asyncTask = FindViewById<Button> (Resource.Id.AsyncTask); asyncTask.Click += (sender, e) => { var task = new SendMessageAsyncTask(){ Activity = this }; task.Execute(); };
ここまでの内容は以下のコミットになります。
Add send message function to Wear project - thinkAmi-sandbox/XamarinWearableMessageApi-sample · GitHub
Android Handheld側プロジェクトのひな形を作成
Wear側のメッセージの送信機能ができたので、今度は受信側であるHandheld側を実装します。
Android Applicationプロジェクトの追加
ソリューションにAndroid Application
プロジェクトを追加します。今回は「Handheld」と名づけます。
AndroidManifest.xmlの追加
Wearと同様にして、AndroidManifest.xmlを追加します。
Xamarin.GooglePlayServices
NuGetパッケージの追加
Handheld側は普通のAndroid Applicationなため、このままではGoogle Play Serviceを使うことができません。
そのため、NuGetパッケージのXamarin.GooglePlayServices
を追加します。なお、Google Play ServicesはNuGetとXamarin Componentの2つがありますが、NuGetのほうがバージョンが微妙に新しいのと、以下の記事があったので、NuGetから導入しました。
Xamarin用のGoogle Play ServicesはComponentsでも提供されている。現在のところ両者に違いはないようだが、最近のXamarinのオンラインセミナーでは「Google Play ServicesはNuGetで提供される」と紹介されているので、ここではNuGetを使う方法を解説した。
通常だとXamarin Studioでは
- プロジェクトを選択して右クリック
- 追加 > Add Nuget Packages... を選択
- 右上の検索欄で、
Xamarin.GooglePlayServices
を入力 Xamarin Google Play Services Binding
にチェックを入れ、「Add Package」ボタンを押す
のようにしてNuGetから導入します。
現在の最新版(v22.0.0.2)への対応
ただ、Xamarin.GooglePlayServicesの最新版(v22.0.0.2)を入れると、ビルド時に100個ほど以下のようなコンパイルエラーが発生するようになります。
%USERPROFILE%\AppData\Local\Xamarin\Android.Support.v7.AppCompat\21.0.3\embedded\.\res\values-v21\values.xml(0,0): Error: Error retrieving parent for item: No resource found that matches the given name 'android:TextAppearance.Material'. (Handheld)
以下によると、原因はビルドツールのせいのようです。そのため、ビルドのバージョンを5.0(API 21)へと変更します。
Xamarin.Android.Support.v7.AppCompat利用時のビルドエラー - 日々のアレコレ(2014-12-25)
再度ビルドすると、以下のコンパイルエラーが出ました。
C:\Program Files (x86)\MSBuild\Xamarin\Android\Xamarin.Android.Common.targets(2,2): Error: Could not find android.jar for API Level 21. This means the Android SDK platform for API Level 21 is not installed. Either install it in the Android SDK Manager (Tools > Open Android SDK Manager...), or change your Xamarin.Android project to target an API version that is installed. (%USERPROFILE%\AppData\Local\Android\android-sdk\platforms\android-21\android.jar missing.) (Handheld)
Android SDKのAPI 21をインストールしていないことから、Android 5.0.1 (API 21)のSDK Platform
のみを追加でインストールします。
実機のNexus7 (Android 4.4)への対応
この状態で実機のNexus7(Android 4.4)へデプロイしようとすると、以下のエラーになります。
Deployment failed because the device does not support the package's minimum Android version. You can change the minimum Android version in the Android Application section of the Project Options. Deployment failed. Minimum Android version not supported by device.
おそらく、ビルドのバージョンをAPI21にしたため、現在のMinimum Android Versionの設定(Automatic)もAPI21相当になったものと考えられます。
そのため、AndroidManifest.xmlを開き、Minimum Android VersionをAutomaticからOverride - Android 4.4 (API level 19)
へと変更します。
ヒープに関するエラーへの対応
再度ビルドすると、別のコンパイルエラーが一つ出ます。
path\to\project\COMPILETODALVIK: Error: (Handheld)
そのため、stackoverflowの回答に従い、以下の作業を行います。
c# - Java heap space OutOfMemoryError when binding a .jar in Xamarin - Stack Overflow
再度ビルドをすると、成功します。
パッケージ名の変更
Wear側同様、Handheld側もパッケージ名をWearと共通のものに変更します。今回は「MessageApiSample」となります。
Serviceとしてメッセージ受信機能を作成
通常のActivityとしてメッセージ受信機能を実装してもよいのですが、せっかくなのでService(今回は「MessageService」という名前)として実装してみます。
大まかな作り方はWearのMainActivityと同じですが、以下の様な点が異なります。
- 空のクラスのテンプレートを元に、必要な機能を追加
Android.Gms.Wearable.WearableListenerService
を継承したクラスに実装- 属性として、以下の2つを追加
[Android.App.Service()]
[Android.App.IntentFilter(new string[] { "com.google.android.gms.wearable.BIND_LISTENER" }) ]
- 受信時の処理は
OnMessageReceived
をオーバーライド実装 - メッセージの受信だけであれば、OnCreate内で
GoogleApiClientBuilder
による生成は不要
また、受信データはToastを使って表示します。
MessageServiceの実装は以下のコミットのMessageService.cs
になります。ソースコードが長いので、ここでは省略します。
Add receive message function to Handheld project - thinkAmi-sandbox/XamarinWearableMessageApi-sample · GitHub
WearからHandheldへのテスト送信
テスト送信をするために、WearとHandheldへアプリをインストールします。
以下の方法でインストールしますが、いろいろと面倒なため、他の方法があるのかもしれません。ご存じの方は教えてくださるとありがたいです。
Handheldへのインストール
以下の流れになります。
- Handheldプロジェクトの上で右クリック
- アプリケーションを選択して開く > Nexus7(実機) を選択
実機を選択すると、ビルドが走り、Nexus7へアプリがインストールされ、Xamarin Studioにも「Deployment completed」と表示されます。
そのままだとアプリの実行が継続するため、停止ボタンで実行を止めます。
Wearへのインストール
WearのエミュレータとNexus7の接続
すでにペアリングをしている場合は不要ですが、行っていない場合は以下を参考にペアリングをします。
【Android Wear】Android SDKを使ったエミュレータでPCでAndroid Wearを試す…2014/7/12更新 | AndMem - Androidのカスタマイズなど
- Nexus7で、Android Wearアプリを起動
- インストールしていない場合は、Google Playからダウンロード・インストール
- Android Wearアプリで、「端末を選択」と表示されているのであれば、右上のメニューより「エミュレータをペア設定」を選択
- Android Wearアプリで、左上が「エミュレータ 接続済」となっていることを確認
デバッグ実行
スタートアッププロジェクトが「Wear」となっていることを確認します。
次に、Debug・VirtualDeviceでWearエミュレータを選択し、デバッグ実行します。
しばらくするとAndroid Wearに以下のような画面が表示されます。
そこで、それぞれのボタンをタップすると、Nexus7に選択した方のメッセージ内容がToastで表示されます。
awaitボタンをタップ
taskボタンをタップ
HandheldからWearへメッセージ送信 (Handheld側)
今度は、Wearからメッセージを受信したタイミングで、HandheldからWearへメッセージ送信を行います。
Wear側の送信処理と同様、
- OnCreateでGoogleApiClientBuilderによる生成とConnectの実行を追加
- OnMessageReceivedにて、メッセージを送信するコードを追加
- JavaCastメソッドを使うために、usingディレクティブに
Android.Runtime
名前空間を追加
を行います。
var nodeIds = NodeIds; foreach (var nodeId in nodeIds) { WearableClass.MessageApi.SendMessage(client, nodeId, MessageTag, System.Text.Encoding.UTF8.GetBytes("こんにちは")); }
なお、AndroidManifest.xmlへの
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
の追加は不要でした。
また、ServiceはUIスレッドはないので、NodeIdを取得する時にasync/awaitとかも不要でした。
HandheldからWearへメッセージ送信 (Wear側)
MainActivityのOnMessageReceivedにてメッセージを受信する処理を実装します。
MainActivityに追加する内容としては以下となります。
- 属性として
[Android.App.IntentFilter(new string[] { "com.google.android.gms.wearable.BIND_LISTENER" })]
を追加 Android.Gms.Wearable.IMessageApiMessageListener
インタフェースを追加- インタフェース実装の
OnMessageReceived
を追加し、受信処理を実装- 受信メッセージを表示するためのToastはUIスレッドで動作するため、
this.RunOnUiThread()
を使う
- 受信メッセージを表示するためのToastはUIスレッドで動作するため、
OnConnected
に、リスナーを追加するコードを実装
public void OnConnected (Android.OS.Bundle connectionHint) { Android.Gms.Wearable.WearableClass.MessageApi.AddListener (client, this); }
この部分も長いので、MainActivityのソースコードは以下のGitHubリンクで確認ください。
テスト実行
先ほどと同じように実行します。
Handheldから送信した「こんにちは」という文字が、Wearで受信・Toast表示できました。
ソースコード
GitHubに置いておきました。
thinkAmi-sandbox/XamarinWearableMessageApi-sample · GitHub
参考
Message APIに関係する公式情報
- Handling Data Layer Events | Android Developers
- WearableListenerService | Android Developers
- MessageApi | Android Developers
WearとHandheld間の通信に関して
- Android Wear開発まとめ - Qiita
- Y.A.M の 雑記帳: Android Wear アプリ開発 その2
- Android Wearの加速度センサーを使う方法 | Workpiles
- Xamarinで作るAndroid Wearアプリ - Qiita
- [android][programming][xamarin]Android-Android Wear間で通信するアプリ開発 - 日々のアレコレ(2014-11-06)
- MessageApi: Simple Conversations with Android Wear | ToastDroid
- Button click in android wear - Stack Overflow
WearableListenerServiceを使ったり、接続/切断を意識しているサンプル
- monodroid-samples/DataLayerListenerService.cs at master · xamarin/monodroid-samples · GitHub
- monodroid-samples/MainActivity.cs at master · xamarin/monodroid-samples · GitHub
Android Wearのサンプル
AndroidのAsyncTask
C#のasync/await
お知らせ
GDG信州によるAndroid Wearに関するイベントが、3/14に開かれます。
GDGShinshu AndroidWear Event in WhiteDay on Zusaar
Android Wearが気になる方の参加をお待ちしています!