Android Wearアプリケーション開発(2)
Android Wearのアプリの作り方
Android Wearアプリの基本的な開発方法を、サンプルコードを交えながら解説。Wear用に拡張されたAPIの中からNotificationとデータ送受信について説明する。
はじめに
前回はAndroid Wearの基本的な機能や特徴を紹介した。今回はAndroid Wearのアプリの作り方を、サンプルコードを交えながら解説する。
開発環境のセットアップ
6月末のGoogle I/Oで、今までプレビュー版であったAndroid Studioがベータ版へアップデートされ、Android WearやGoogle Glassなどのアプリ開発もサポートされた。ここ最近はEclipse ADTと比較してAndroid Studioの進化が著しく、Wearアプリ開発でもAndroid Studioを利用するのが妥当だろう。
Android Studioの最新版は以下のURLからダウンロードできる。
インストール方法については以下のページか、その他の解説ページを参考にしてほしい。
プロジェクトの作成
Android Studioで新規プロジェクト作成を行うと、「Phone and Table」や「Wear」用のプロジェクトなどからプロジェクトテンプレートを選択できる(図1)。
前回解説した通り、Android Wearアプリを配布する際は、Handheld(WearとペアになるPhone/Tablet)用のアプリとWear用のアプリをバンドルして配布することになる。今回も「Phone and Tablet」用のアプリと「Wear」用のアプリを、同一プロジェクト内に作っている前提で解説する。
Wear上でのデバッグ方法
Android Studioから開発中のアプリをWear上に転送してテストするためには、以下の手順が必要だ。
USBデバッグの有効化
USB経由でアプリの転送・テストを行うのは、通常のAndroidデバイスと同様の手順となる。
まずWearのキューカード(前回を参照)から[設定]を選択する。[端末情報]を選び、[ビルド番号]が表示された箇所を7回タップする。成功すると、開発者向けオプションが有効になったことがトーストで通知されるので、[設定]のメニューに戻って、[開発者向けオプション]が追加されていることを確認しよう(図2)。
[開発者向けオプション]を選択後、[ADBデバッグ]の項目をタップして「有効」に切り替える(図3)。
これでPCからUSB経由でアプリを転送して動かすことが可能になった。
Bluetoothデバッグの有効化
上の手順でUSBケーブルを介してデバッグが可能だが、毎回、WearをUSB接続するのが面倒な場合は、HandheldデバイスからBluetoothを介してデバッグを行うことも可能だ。この場合はHandheldデバイスをPCにUSBケーブルで接続しておく必要がある。
Bluetoothデバッグを有効化するには、まず[設定]の[開発者向けオプション]から[Bluetooth経由でデバッグ]をタップし、有効に切り替える。
次にHandheldデバイス側でAndroid Wearアプリを開き、[設定]から[Bluetooth経由のデバッグ]を同じく有効にする。この時点で当該項目は「ホスト:未接続」「ターゲット:接続済み」と表示されるはずだ(図4)。
その後、ターミナル(Windowsの場合はコマンドプロンプト)で以下のコマンドを実行する(Android SDK内のplatform-toolsにパスが通っていることを前提としている)。
adb forward tcp:4444 localabstract:/adb-hub; adb connect localhost:4444
|
コマンド実行後、「connected to localhost:4444」と表示されれば成功で、Android Wearの[設定]画面も「ホスト:接続済み」に変わっているはずだ。
Wearアプリの作り方
ここからは、Wearアプリを実際に作るために基本となる「Notification」と「データ送受信」の2つの機能について、サンプルコードを交えながら解説する。このサンプルコードを含むプロジェクト全体は、以下のURLからダウンロードできる(※ダウンロードしたプロジェクトをAndroid Studioで開くには、[Import Project]を行えばよい)。
以下では、「どのようなクラスを作成しているのか」など、アプリの作成手順についての説明は割愛する。ダウンロードしたサンプルプログラムのコード内容と照らし合わせながら読むと、よりプログラムの全体像を理解しやすいだろう。
Notification
Android Wearは特に何もしなくても、ペアリングされたHandheldデバイスの通知を受け取るようになっている。しかし、拡張されたAPIを使うことで、よりリッチな通知をWear上に表示できる。
事前準備
新しく追加されたNotificationのAPIを使用するために、最新のAndroid Support Libraryをプロジェクトに追加しておこう。
実際に追加するには、「mobile」モジュール(=Handheld側)と「wear」モジュール(=Wear側)の中にあるbuild.gradleファイルに、それぞれリスト2.1とリスト2.2のように追記する。なお、この記述は、最新のAndroid Studioでプロジェクトを新規作成した場合は自動的に追加されているはずだ。
dependencies {
……省略……
compile 'com.android.support:support-v4:20.+'
……省略……
}
|
dependencies {
……省略……
compile 'com.google.android.support:wearable:+'
……省略……
}
|
アクションボタンを追加したNotificationの作成
Notificationにアクションボタンを追加することで、Wear上でのアクションをHandheldに伝えることができる。
以下は、アクションボタンでHandheld(=「mobile」モジュール)上のActivityを開く例である(※実行例と、それを実現しているコードの内容を示している。次節以降でも、同じように「実行例+コード内容」というパターンで示していく)。
Intent intent = new Intent(context, NotificationLandingActivity.class);
PendingIntent pIntent = PendingIntent.getActivity(context, 0, intent, 0);
Notification n = new NotificationCompat.Builder(context)
.setContentTitle("新着メッセージがあります")
.setContentText("xxさんからメッセージが届いています")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pIntent)
.addAction(R.drawable.ic_launcher, "返信", pIntent) //…… 1
.addAction(R.drawable.ic_launcher, "転送", pIntent)
.build();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(0, n);
|
NotificationLandingActivity
については、ダウンロードしたソースコードを参照されたい。
- 1アクションボタンのアイコンと表示文字列、アクションボタンタップ時に発行されるPendingIntentを指定する。
音声入力の受け取り
Notificationに対して、Wearから音声入力で返信するには、RemoteInputクラスを使用する。
RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY) //…… 1
.setLabel("xx会議に参加しますか?") //…… 2
.setChoices(new String[] {"はい", "いいえ"}) //…… 3
.build();
Intent replyIntent = new Intent(this, VoiceInputReceiverActivity.class);
PendingIntent replyPendingIntent =
PendingIntent.getActivity(this, 0, replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Action action =
new NotificationCompat.Action.Builder(R.drawable.ic_launcher, "返信", replyPendingIntent)
.addRemoteInput(remoteInput) //…… 4
.build();
Notification notification =
new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("出席確認")
.setContentText("xxカンファレンスの出席確認です")
.extend(new NotificationCompat.WearableExtender().addAction(action)) //…… 5
.build();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(200, notification);
|
VoiceInputReceiverActivity
クラスについては後述する。
- 1
RemoteInput.Builder
クラスのコンストラクターで渡すキー(=クラス内に文字列として定義している)は、後で音声入力結果を取り出すときにも使用する。 - 2音声入力画面で表示されるメッセージを指定する。
- 3音声入力画面で、ユーザーがタップで選択できる文字列リストを指定する。
- 4Notificationに
addRemoteInput
メソッドで作成しておいたRemoteInputオブジェクトを指定する。 - 5WearableExtenderに対して
addAction
することで、Wear上にだけこの音声入力用のアクションを表示できる。
音声認識の結果は、PendingIntentで指定したActivityで受け取ることになる(リスト5)。
public class VoiceInputReceiverActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_voice_input_receiver);
Bundle remoteInput = RemoteInput.getResultsFromIntent(getIntent()); //…… 1
CharSequence message = remoteInput.getCharSequence(NotificationActivity.EXTRA_VOICE_REPLY); //…… 2
((TextView) findViewById(R.id.message)).setText(String.format("音声入力メッセージは「%s」です。", message));
}
}
|
- 1Intentからの音声入力結果が格納されたBundleを取り出す。
- 2Notification作成時に
RemoteInput.Builder
に渡したキーで音声入力結果を取り出せる。
Notificationにページを追加する
Notificationに対し、より詳細な情報を追加するために、2枚目以降のページを追加できる。追加されるページは、それぞれ個別のNotificationとして作成する。
Notification page =
new NotificationCompat.Builder(context)
.setStyle(new NotificationCompat.BigTextStyle().bigText("メンチカツ定食 $7.99")) //…… 1
.build();
Notification notification =
new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("xxレストラン通信")
.setContentText("本日の日替わりランチ")
.extend(new NotificationCompat.WearableExtender().addPage(page)) //…… 2
.build();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(300, notification);
|
- 12枚目のページに表示される内容を指定する。ここでは
setStyle
メソッドでBigTextStyleを指定することで、トップの通知とは見た目を変えている。 - 2
addPage
メソッドで2枚目以降のページに表示するNotificationを指定する。ここでもWearableExtenderを使うことでWear上にだけ表示されるようにしている。
Notificationにスタックを追加する
複数のNotificationをスタックとしてひとまとめにすることもできる。以下のように、まとめたいNotificationに対してsetGroup
メソッドで同一キーを指定する。
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
// まとめる対象となるNotificationその1
Notification card1 = new NotificationCompat.Builder(context)
.setContentText("何してますか?")
.setSmallIcon(R.drawable.ic_launcher)
.setGroup(GROUP_KEY) //…… 1
.setSortKey("0") //…… 2
.build();
notificationManager.notify(101, card1);
// まとめる対象となるNotificationその2
Notification card2 = new NotificationCompat.Builder(context)
.setContentText("手伝ってもらってもいいですか?")
.setSmallIcon(R.drawable.ic_launcher)
.setGroup(GROUP_KEY) //…… 3
.setSortKey("1") //…… 4
.build();
notificationManager.notify(102, card2);
// サマリー用のNotification(Handheldだけに表示される)
Notification summary = new NotificationCompat.Builder(context)
.setContentTitle("新着メッセージ2件")
.setSmallIcon(R.drawable.ic_launcher)
.setGroup(GROUP_KEY) //…… 5
.setGroupSummary(true) //…… 6
.build();
notificationManager.notify(100, summary);
|
- 135まとめたいNotificationに対して同一のキー(=クラス内に文字列として定義している)を指定する。
- 24
setSortKey
メソッドで指定した値を基にスタック上の表示順が決定される。 - 6Handheldに表示されるサマリー用のNotificationを発行するために、
setGroupSummary(true)
を指定する。
Wear上のActivityを開く
今までのNotificationは、全てHandheldからWearにNotificationを送る例だったが、Wearデバイス上で自分自身に対しNotificationを送ることもできる。
HandheldからNotificationを送る場合に指定するPendingIntentには、Handheld側のActivityしか指定できない(つまりWearに表示されるNotificationから何らかのActivityを開きたいと思っても、Handheld側のActivityしか開けない)という制約があるが、Wearから自分自身にNotificationを送る場合、Wear上のActivityをPendingIntentとして指定できる。
Intent viewIntent = new Intent(context, WatchActivity.class); //…… 1
PendingIntent pendingViewIntent = PendingIntent.getActivity(context, 0, viewIntent, 0);
Notification notification = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("Wearableから送信")
.setContentText("Wearableから送信したNotificationです。")
.addAction(R.drawable.ic_launcher, "Open", pendingViewIntent)
.setLocalOnly(true)
.extend(new NotificationCompat.WearableExtender().setContentAction(0)) //…… 2
.build();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(3000, notification);
|
- 1
Intent
クラスのコンストラクターにWearアプリで定義されたActivityを指定する。 - 2
setContentAction(0)
を指定することで、通知がクリックされた際のデフォルトの挙動が、最初のアクションボタンを押した時の挙動と一緒になる(つまりこの場合はWatchActivityが開く)。
NotificationにActivityを埋め込む
前項と同様、Wearから自分自身にNotificationを送る際にsetDisplayIntent
メソッドを使うことで、ActivityをNotificationに埋め込むことができる。
Intent intent = new Intent(this, NotificationEmbeddedActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.extend(new Notification.WearableExtender().setDisplayIntent(pendingIntent)) //…… 1
.build();
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1000, notification);
|
NotificationEmbeddedActivity
については、ダウンロードしたソースコードを参照されたい。
- 1埋め込むActivityに対するPendingIntentを、
setDisplayIntent
メソッドで指定する。
また、対象となるActivityには、AndroidManifest.xmlファイル中でandroid:allowEmbedded="true"
を指定しておく必要がある(リスト10)。
<activity
android:name=".NotificationEmbeddedActivity"
android:allowEmbedded="true" />
|
データ送受信
Notificationに加えて、Wearのアプリを作るために重要なのが、WearとHandheldデバイスとの間のデータ送受信だ。Wearは単体ではインターネット接続ができないのと、複雑なUI(ユーザーインターフェース)や機能には向いていないため、「Handheld側で処理を行い、その結果をWear側で受け取りたい」、また、「Wear上の情報をHandheld側に送りたい」といったケースは頻繁に発生する。
WearとHandheldでデータ送受信を行うには、Message APIとData APIという2種類のAPIが存在するが、以下それぞれの特徴とサンプルコードを解説する。
データ送信のための準備
HandheldとWear間のデータ送受信は、それぞれのデバイス上のGoogle Play Servicesを介して行われる。データ送受信APIを利用するための準備として、以下のようにGoogleApiClient
(=Google API Client)というGoogle Play Servicesとのインターフェースとなるクラスのインスタンスを作成する必要がある。
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle bundle) {
// 接続時に行いたい処理を記述する
}
@Override
public void onConnectionSuspended(int cause) {
// 一時的に切断された際に行いたい処理を記述する
}
}).build();
mGoogleApiClient.connect();
|
Message APIによるデータ送受信
Message APIはFire-and-forgot式のメッセージ送信の方法で、WearからHandheldまたはHandheldからWearへの一方向へのメッセージ送信が可能だ。データ内容としては、送信先のデバイスで何らかの処理を行うためのパス(文字列)を送ることになる。
使い方の例としては、Handheld側の音楽プレイヤーをWearでコントロールしたり、Handheld側の操作でWearのActivityを開いたりするなどの使い方ができる。
メッセージの送信
Message APIを使ってデータを送信するためには、相手側のノードIDを指定する必要がある。ノードIDはHandheldやWearなど全てのデバイスに割り当てられる一意なIDで、Node APIを使って、今、接続中のデバイスのIDや自分自身のIDを取得できる。
private Collection<String> getNodes() {
HashSet<String> results = new HashSet<String>();
NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await(); //…… 1
for (Node node : nodes.getNodes()) {
results.add(node.getId());
}
return results;
}
private void sendMessageToStartActivity() {
Collection<String> nodes = getNodes();
for (String node : nodes) {
MessageApi.SendMessageResult result =
Wearable.MessageApi.sendMessage(mGoogleApiClient, node, START_ACTIVITY_PATH, null).await(); //…… 2
if (!result.getStatus().isSuccess()) {
Log.e(TAG, "ERROR: failed to send Message: " + result.getStatus());
}
}
}
|
- 1Node APIを使って現在接続しているWearデバイスのノードID一覧を取得する。
- 2取得したノードIDと接続済みのGoogle API Client(この例では
mGoogleApiClient
)を使って、文字列(この例ではWearデバイス上でActivityを開くためのパス文字列)を送っている。またawait
を実行して、処理が完了するまで待機している。
メッセージの受信
相手側のデバイスでメッセージを受信するには、Serviceを使う方法とListenerを使う方法があるが、ここではServiceを使う方法を紹介する。もう1つのListenerを使う方法はこちらのページ(英語)を参照してほしい。
Serviceを使ってメッセージを受信するには、WearableListenerService
抽象クラスを継承したサービスを作り、onMessageReceived
メソッドをOverrideする(リスト14)。以下のように作成したサービスに対して、AndroidManifest.xmlファイルの中で、BIND_LISTENERアクションをIntentFilterとして指定する(リスト13)。これにより、メッセージを受信した際に、onMessageReceived
メソッドが呼び出されるようになる。
<service android:name=".DataLayerListenerService">
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
|
public class DataLayerListenerService extends WearableListenerService { //…… 1
……省略……
@Override
public void onMessageReceived(MessageEvent messageEvent) { //…… 2
// 送られてきたパスの値がSTART_ACTIVITY_PATHに等しければ、Activityを起動する
if (START_ACTIVITY_PATH.equals(messageEvent.getPath())) { //…… 3
Intent intent = new Intent(this, WatchActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return;
}
}
……省略……
}
|
- 12
WearableListenerService
抽象クラスを継承し、onMessageReceived
メソッドをOverrideする。 - 3
MessageEvent
オブジェクトのgetPath
メソッドで、メッセージ送信時にセットしたパスの値を取得できる。
Data APIによるデータ送受信
Data APIは、HandheldとWearの間で「Date Item」と呼ばれるデータを同期する方法だ。Data Itemの中身はバイト配列だが、「Data Map」を通じてMapのようにデータ内容にアクセスできる。各Data Itemは100KBytesまでデータを保持でき、そのサイズ内であれば画像などのデータもやりとり可能だ。
Message APIと異なり、ノードを指定する必要はなく、接続中の全てのノードにデータが同期される他、後から接続されたノードにも接続時にデータが同期される。
Date Itemを変更する
PutDataMapRequest dataMap = PutDataMapRequest.create(COUNT_PATH); //…… 1
dataMap.getDataMap().putInt(COUNT_KEY, ++count); //…… 2
PutDataRequest request = dataMap.asPutDataRequest();
PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(mGoogleApiClient, request); //…… 3
pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
@Override
public void onResult(DataApi.DataItemResult dataItemResult) {
Log.d(TAG, "count updated:" + count);
}
});
|
COUNT_PATH
とCOUNT_KEY
は、DataActivity
クラス内で文字列として定義されている。
- 1Data Itemを識別するためのキーとしてパスを指定する。
- 2
PutDataMapRequest
というMapとして扱えるクラスに、COUNT_KEY
というキーで値をセット。 - 3接続済みのGoogle API Client(この例では
mGoogleApiClient
)とPutDataRequestオブジェクトを指定してデータを更新する。
Data Itemの変更を検知する
Message APIのときと同様、WearableListenerService
抽象クラスを継承したサービスを作ることで、Data Itemの変更を検知できる。Date Itemの場合は、onDataChanged
メソッドが呼び出されるため、それをOverrideしておく。
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
for (DataEvent event : dataEvents) {
DataItem dataItem = event.getDataItem();
if (COUNT_PATH.equals(dataItem.getUri().getPath())) { //…… 1
DataMap dataMap = DataMapItem.fromDataItem(dataItem).getDataMap(); //…… 2
int count = dataMap.getInt(COUNT_KEY);
……省略……
}
}
}
|
COUNT_PATH
とCOUNT_KEY
は、DataLayerListenerService
クラス内で文字列として定義されているが、その内容はリスト15と完全に一致させている。
- 1パスを確認することで、どのData Itemが更新されたのかをチェックする。
- 2
DataMapItem.fromDataItem
メソッドによってマップ形式のデータに変換する。そして、変更時に指定したCOUNT_KEY
でデータにアクセスする。
Notificationとデータ送受信の組み合わせ
Notificationとデータ送受信のAPIを紹介してきたが、これらを組み合わせることで、より面白い機能を実装できる。
例えばグーグルが公開しているカメラアプリでは、Wearデバイス上でシャッターを切れる機能を提供している(図11)。カメラアプリが立ち上がると、Data APIでカメラの準備が整った旨がWearに送信される。次にWearはそのデータを受け取ると、Wear上でリモートシャッター用のActivityを立ち上げるために、自分自身に対しNotificationを発行する。最後にリモートシャッター画面でタップすると、クリックイベントがMessage APIでHandheldのカメラアプリに送信され、実際のシャッターが切られる。
もう1つ面白い例としてGoogle Mapアプリのナビゲーション通知が挙げられる(図12)。Mapアプリでナビゲーションを開始すると、Wearの通知でもナビゲーションが開始されるが、これは埋め込みActivityになっている。HandheldからWearにData APIで現在位置やナビゲーションに関わる情報を都度送信し、Wearから自分自身にNotificationを発行することで、埋め込まれたActivityの情報を更新し続けているのだ。
まとめ
Android Wearアプリの開発方法と、Wear用に拡張されたAPIの中からNotificationとデータ送受信の部分を紹介した。これらの機能は一見地味に見えるが、Wearアプリの肝となる部分であり、後半に例で示したように工夫次第で面白いアプリを作ることができる。良いアイデアが思い付いたら、ぜひWear用のアプリを作ってPlay Storeに公開してほしい。
1. Android Wearの基礎知識
グーグルが開発する腕時計型のウェアラブルデバイス「Android Wear」用のアプリの開発を解説する連載がスタート。Wearの基本的な機能や特徴を紹介する。