Yahoo! JAPAN Tech Advent Calendar 2014の20日目の記事です。一覧はこちら
はじめまして、音声アシストアプリ開発をしている岩瀬張(いわせばり)です。
今回は音声アシストがAndroid Wearに対応したことから、Android Wear開発について記述したいと思います。
参考:Yahoo! JAPAN、Android Wear対応を開始 / プレスルーム - ヤフー株式会社
Android Wearでは、2種類のアプローチがあると考えています。
Notificationを利用したアプローチ 、と、 Android Wearアプリを作るアプローチ です。
今回は音声アシストが選んだ Android Wearアプリを作るアプローチ する上で必要なクラスやAPIについて記述していきます。
実装などの説明
手順
- 下準備
- Google Play開発者サービス
- Node API
- API
- MessageAPI
- MessageApi.MessageListener
- DataAPI
- DataApi.DataListener
- MessageAPI
- サービス
- WearableListenerService
下準備
Android端末とAndroid Wear端末の間はBluetoothによって接続されていますが、BluetoothのProfileがAndroid Wear用の特別なものになっており、
Android 4.3以降でしか接続できません。
しかし、Google Play開発者サービスが間に立つことで通信プロトコルを自分で導入する必要もなく、簡単に実装することが可能です。
必要に応じて、Google Play開発者サービスのアップデートなどを促す必要がありますが、ここでは割愛します。
Google Play開発者サービスの導入
Google Play開発者サービスのライブラリは、Eclipse、AndroidStudioで導入の方法が違います。
Eclipseでは、SDKディレクトリから /extras/google/google_play_services/libproject/ からインポートしてください。
AndroidStudioでは、Gradleの dependencies に下記の2行を追加してください。
compile 'com.google.android.gms:play-services:6.5.87' compile 'com.google.android.gms:play-services-wearable:6.5.87'
Google Play開発者サービスの機能を使うには、アプリ側でこのサービスに接続する必要があります。
ライブラリの中にあるGoogleApiClientを利用することで簡単に接続できます。
GoogleApiClientを作成するには、GoogleApiClient.Builderを利用して作成しますが、最後の build を忘れないようにしましょう。
ConnectionCallbacksに設定したListenerには、接続が完了した場合は、 onConnected が、一時的に切断されたりした場合には onConnectionSuspended が呼ばれます。
ConnectionFailedListenerに設定したListenerは、接続に失敗したり、Google Play開発者サービスが古い場合などに呼ばれ、 onConnectionFailed が呼び出されます。
必要に応じて処理を記述してください。
mGoogleApiClient = new GoogleApiClient.Builder(mApplicationContext) .addApi(Wearable.API) .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() { @Override public void onConnected(Bundle bundle) { //接続が完了した時に呼び出される } @Override public void onConnectionSuspended(int cause) { //一時的に切断された時の処理を記述する } }).addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() { @Override public void onConnectionFailed(ConnectionResult connectionResult) { } }).build(); mGoogleApiClient.connect();
GoogleApiClient | Android Developers
Node API
Android端末ではWearable端末を、Wearable端末ではAndroid端末をNode APIを介して取得します。
同期的、非同期、の二種類の取り方があります。
UIThreadで処理している場合には非同期を、BackgroundThreadで処理している場合には同期処理を利用すると良いでしょう。
- 同期的
NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await(); for (Node node : nodes.getNodes()) { //ノードに対する処理を記述する //データの送信処理で、node.getId();が必要になる。 sendMessage(node.getId()); }
- 非同期
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient) .setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() { @Override public void onResult(NodeApi.GetConnectedNodesResult nodes) { for (Node node : nodes.getNodes()) { //ノードに対する処理を記述する //データの送信処理で、node.getId();が必要になる。 sendMessage(node.getId()); } } });
送信方法
データの送受信をするのに、MessageAPIとDataAPIの2つのデータ送受信方法があります。
この2つの違いとしては、MessageAPIはbyte配列を渡すのに対して、DataAPIはデバイス間で同期するデータの書き換えを行います。
MessageAPIは片方向通信でのデータ通信です。
DataAPIでは、共有の保存場所を用意してデータを更新し合うような、設定などを置くのに向いていると考えています。
MessageApi
MessageAPIはデータをByte配列で渡す、片方向通信のAPIです。
一方的に送って、受け取れたかどうかだけ把握できます。
どういう処理をすればいいのか判断するためにPATHを設定しておくります。
データはByte配列で送っており、PATHかどのデータに戻せばいいのか判断します。
MessageApi | Android Developers
Wearable.NodeApi.getConnectedNodes(mGoogleApiClient) .setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() { @Override public void onResult(NodeApi.GetConnectedNodesResult nodes) { for(Node node:nodes) { MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), PATH, bytes).await(); } } });
MessageApi.MessageListener
MessageApiの受け取りはMessageApi.MessageListenerで行います。
受け取ったデータがどのデータなのかはPATHで行い、byte配列をどのデータ型に戻せばいいのかの判断にも利用します。
@Override protected void onResume() { super.onResume(); if(!mGoogleApiClient.isConnected()) { mGoogleApiClient.connect(); Wearable.MessageApi.addListener(mGoogleApiClient,mMessageListener); } } @Override protected void onPause() { super.onPause(); if(mGoogleApiClient.isConnected()) { Wearable.MessageApi.removeListener(mGoogleApiClient,mMessageListener); mGoogleApiClient.disconnect(); } } private MessageApi.MessageListener mMessageListener = new MessageApi.MessageListener() { @Override public void onMessageReceived(MessageEvent messageEvent) { String path = messageEvent.getPath(); if(TextUtils.equals(path, MESSAGE_API_PATH)) { String message = new String(messageEvent.getData()); mTextView.setText("message:" + message + "\n"); } } };
DataApi
DataAPIはAndroid端末とWearable端末で同じ場所にデータを保存しておくようなイメージで利用できます。
実際には個別のデータを同期するように利用されています。
データの管理は、URI形式のパスで管理しています。
Android端末とWearable端末で同じパスを指定することで、データを引くことができます。
- データを書き込む
Asset asset = Asset.createFromBytes("Android端末からのデータ".getBytes("UTF-8")); final PutDataRequest req = PutDataRequest.create(DATA_API_PATH); req.putAsset(DATA_API_EXTRA_KEY,asset); NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await(); for (Node node : nodes.getNodes()) { DataApi.DataItemResult result = Wearable.DataApi.putDataItem(mGoogleApiClient,req).await(); }
- データを取得する
DataItemBuffer itemBuffer = Wearable.DataApi.getDataItems(mGoogleApiClient).await(); for(DataItem item : itemBuffer) { if(DATA_API_PATH.equals(item.getUri().getPath())) { DataMap map = DataMap.fromByteArray(item.getData()); String data = map.getString(DATA_API_EXTRA_KEY); mTextView.setText("data:" + data + "\n"); } }
DataApi.DataListener
- 変更を受け取る
変更を受け取るには、Listenerの登録が必要です。
DataApi.DataListenerを用意して受け取れるようにしてください。
Android端末で変更した場合にはWearable端末で、Wearable端末で変更するとAndroid端末で変更された情報が通知されます。
@Override protected void onResume() { super.onResume(); if(!mGoogleApiClient.isConnected()) { mGoogleApiClient.connect(); Wearable.DataApi.addListener(mGoogleApiClient,mDataListener); } } @Override protected void onPause() { super.onPause(); if(mGoogleApiClient.isConnected()) { Wearable.DataApi.removeListener(mGoogleApiClient,mDataListener); mGoogleApiClient.disconnect(); } } DataApi.DataListener dataListener = new DataApi.DataListener() { @Override public void onDataChanged(DataEventBuffer dataEvents) { if(!TextUtils.equals(DATA_API_PATH, event.getDataItem().getUri().getPath())) { return; } for(DataEvent event:dataEvents) { switch(event.getType()) { case DataEvent.TYPE_CHANGED: DataItem item = event.getDataItem(); byte[] bytes = item.getData(); //追加、変更 break; case DataEvent.TYPE_DELETED: //削除 break; } } } };
サービス
WearableListenerService
MessageAPI、DataAPIの利用方法の中で、2種類のListenerを実装する方法を記述していましたが、
WearableListenerServiceを利用すると自前でImplimentsすることなくデータを受信できます。
音声アシストアプリのWearable対応は、このサービスを利用しています。
特に、Android端末でアプリがバックグラウンド、もしくは起動していない場合に、Wearable端末で実行されることを考えているのでそのような対応になっています。
- コード
public class WearService extends WearableListenerService { @Override public void onDataChanged(DataEventBuffer dataEvents) { super.onDataChanged(dataEvents); } @Override public void onMessageReceived(MessageEvent messageEvent) { super.onMessageReceived(messageEvent); } }
- AndroidManifest
Actionに com.google.android.gms.wearable.BIND_LISTENER を設定する必要があります。
<service android:name=".WearService"> <intent-filter> <action android:name="com.google.android.gms.wearable.BIND_LISTENER"/> </intent-filter> </service>
まとめ
音声アシストではMessageAPIのみ利用しています。
理由としては、発話した言葉やAPIの応答データなど揮発性の高い情報のみをやりとりしていたため、両方で保持しつづける必要がなかったからです。
音声アシストでは、Wearable端末側アプリがActivityなのでListenerで受け取り準備をして、Android端末側アプリがWearableListenerServiceを継承したサービスで受け取ってデータ通信を行っています。
しかし、Android端末とWear端末をほぼ同期して利用するようなアプリの場合には、DataAPIを利用するほうがよいでしょう。
用途によって使い分けることが期待されていると思っています。
最後に
今回は端末間の通信について絞りましたが、Wear端末側で利用できる特殊なViewなどもありますが、
Android Wearに対応したアプリを作るのは非常に簡単です。
携帯端末に比べるとまだまだ機能制限はありますが、今後機能が増えていくことが予想されます。
また、Android Wearにかぎらず、Wearableの世界は着実に広がっています。
弊社でもIoTへの取り組みについて発表したばかりです。(参考:Yahoo! IoT)
新しいデバイスの開発にどんどんチャレンジしていきましょう!
参考情報
※ Google、Nexus、Androidは、Google Inc.の登録商標または商標です。
Yahoo! JAPANでは情報技術を駆使して人々や社会の課題を一緒に解決していける方を募集しています。 詳しくは採用情報をご覧ください。