Android WearのDataLayerAPIを試してみました。 handheld(スマホとかタブレット)とwearable(時計)間でデータをやり取りするためのAPIです。
ドキュメントを読むと大きくわけて以下の2種類の方法があるみたいです。
- Data Items
- Wearable.DataApiクラスを使う
- DataItemクラスを使ってデータを自動的にhandheldとwearable間でデータの同期ができる
- Messages
- Wearable.MessageApiクラスを使う
- 片方向の通信でデータはメッセージといっしょに送ってもいいし送らなくてもいい
データ更新時やメッセージ受信時のハンドリングは、WearableListenerServiceを使うかActivityでリスナーを登録する方法の2通りがあるようですが、今回はActivityでリスナー登録する方法で試しました。
試しに実装したコードは以下にあります。
https://github.com/bati11/wear-datalayer-sample
参考にしたドキュメント
- Sending and Syncing Data | Android Developers
- GoogleApiClient | Android Developers
- com.google.android.gms.wearable | Android Developers
しばらく触らなさそうな気がするので忘れないうちにメモ。 以下はAndroid Studio 0.8.1で新規プロジェクトを作ってからの例です。
Data Itemsを使った実装
android.net.Uriで表される場所にデータを保存できるような感じ。同じURIをhandheldでもwearableでも使うことで同期されたデータを使える。URIはデータを保存というか同期して初めて分かる(作られる)ようになってるっぽいので、データを更新する側のアプリはいいとして、もう一方のアプリはデータの更新をリスナーで処理してURIもしくはデータ自体を取得する処理を書く必要がありそう。
2014/8/2追記
URIが分かってなくてもpathが分かれば以下のようにしてデータを取得することができました。
PendingResult<DataItemBuffer> dataItems = Wearable.DataApi.getDataItems(mGoogleApiClient); dataItems.setResultCallback(new ResultCallback<DataItemBuffer>() { @Override public void onResult(DataItemBuffer dataItems) { for (DataItem dataItem : dataItems) { if (dataItem.getUri().getPath().equals("/path")) { DataMap dataMap = DataMap.fromByteArray(dataItem.getData()); // データを使った処理
追記ここまで。
wearableでデータを更新し、handheldでデータの更新を検知してハンドリングする流れを試した(逆でもコードはいっしょになる、はず...)。
wearable(データを更新する側)
step1
まずはAndroidManifest.xmlを編集する。以下を<application>タグ内に記述する。
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
step2
Data Layer APIを使うにはGoogleApiClientインスタンスが必要になる。ActivityのonCreateで以下のように生成する。
mGoogleApiClient = new GoogleApiClient .Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(Wearable.API) .build();
addConnectionCallbacksメソッドでGoogle Play servicesに接続できたときのリスナーを登録する。ActivityにGoogleApiClient.ConnectionCallbacksインタフェースを実装させ、onConnectedメソッドとonConnectionSuspendedメソッドをオーバーライドする。
同じようにaddOnConnectionFailedListenerメソッドでGoogle Play servicesに接続失敗したときのリスナーを登録する。GoogleApiClient.OnConnectionFailedListenerを実装しonConnectionFailedメソッドをオーバーライドする。
そして、onResumeメソッドでGoogleApiClientインスタンスのconnectメソッドを呼ぶ。onPauseメソッドでdisConnectする。ここまででActivityは以下のようになる。
public class WearActivity extends Activity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { private GoogleApiClient mGoogleApiClient; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_data_item_sample); mGoogleApiClient = new GoogleApiClient .Builder(this) .addConnectionCallbacks(this) .addApi(Wearable.API) .build(); } @Override protected void onResume() { super.onResume(); mGoogleApiClient.connect(); } @Override protected void onPause() { super.onPause(); if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { mGoogleApiClient.disconnect(); } } @Override public void onConnected(Bundle bundle) { Log.d("TAG", "onConnected"); } @Override public void onConnectionSuspended(int i) { Log.d("TAG", "onConnectionSuspended"); } @Override public void onConnectionFailed(ConnectionResult connectionResult) { Log.e("TAG", "onConnectionFailed: " + connectionResult); } }
step3
ボタンのonClick時にonClickメソッドが実行され、そのタイミングでデータを更新するとする。
データの入れ物がDataItemクラスだが、それを使いやすくしたDataMapクラスを使う。PutDataMapRequest.createメソッドで新たなDataMapインスタンスを用意する。このときDataMapを一意に特定できるようにパスを指定する。パスはスラッシュで始める必要がある。
生成したDataMapインスタンスにデータをつめて、DataApiを使って更新する。
public void onClick(View v) { // DataMapインスタンスを生成する PutDataMapRequest dataMapRequest = PutDataMapRequest.create("/datapath"); DataMap dataMap = dataMapRequest.getDataMap(); // データをセットする dataMap.putInt("key", data); // データを更新する PutDataRequest request = dataMapRequest.asPutDataRequest(); PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(mGoogleApiClient, request); pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>() { @Override public void onResult(DataApi.DataItemResult dataItemResult) { Log.d("TAG", "onResult: " + dataItemResult.getStatus()); } }); }
handheld(データの更新を検知する側)
step1,2については同じことをしてGoogleApiClientのconnectメソッドを呼ぶ。
step3
connectが成功するとGoogleApiClient.ConnectionCallbacksインタフェースを実装してオーバーライドしたonConnectedメソッドが呼ばれる。更新を検知して処理をしたい場合は、DataApiに対してonConnectedメソッドでListenerを登録する。onPauseメソッドでListenerを削除する。
@Override public void onConnected(Bundle bundle) { Wearable.DataApi.addListener(mGoogleApiClient, this); } @Override protected void onPause() { super.onPause(); if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { Wearable.DataApi.removeListener(mGoogleApiClient, this); mGoogleApiClient.disconnect(); } }
Listenerとして登録するために、ActivityにDataApi.DataListenerインタフェースを実装させ、onDataChangedメソッドをオーバーライドする。データが更新されると、onDataChangedメソッドが実行される。
@Override public void onDataChanged(DataEventBuffer dataEvents) { for (DataEvent event : dataEvents) { // TYPE_DELETEDがデータ削除時、TYPE_CHANGEDがデータ登録・変更時 if (event.getType() == DataEvent.TYPE_DELETED) { Log.d("TAG", "DataItem deleted: " + event.getDataItem().getUri()); } else if (event.getType() == DataEvent.TYPE_CHANGED) { Log.d("TAG", "DataItem changed: " + event.getDataItem().getUri()); // 更新されたデータを取得する DataMap dataMap = DataMap.fromByteArray(event.getDataItem().getData()); data = dataMap.getInt("key"); } } }
Messagesを使った実装
Messagesは方方向のデータ通信。データを更新・同期するという考え方ではなく、その名の通りメッセージを送信するという考え方。 DataApiの場合と実装方法は結構似てる。
メッセージ送信側
step1,2についてはDataApiの場合と同じことをしてGoogleApiClientのconnectメソッドを呼ぶ。
step3
ボタンのonClick時にonClickメソッドが実行され、そのタイミングhandbeltへメッセージを送るとする。
DataApiのときと違うのが送信先を示すNodeクラスが登場するところ。メッセージを送る端末を選択できるみたい。NodeApiを使ってNodeインスタンスを取得する。
DataApiのときと同じなのがスラッシュで始まるパスでメッセージを識別するところ。以下の例ではデータもいっしょに送ってるけど(byte配列)、送らなくてもいい。
Nodeを取得する処理も非同期なのでコールバック処理が重なって見づらい・・・。工夫する余地きっとあるけどまた今度。。
public void onClick(View view) { PendingResult<NodeApi.GetConnectedNodesResult> nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient); nodes.setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() { @Override public void onResult(NodeApi.GetConnectedNodesResult result) { String messagePayload = "Hello!"; for (Node node : result.getNodes()) { final byte[] bs = (messagePayload + " " + node.getId()).getBytes(); PendingResult<MessageApi.SendMessageResult> messageResult = Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), "/messagesample", bs); messageResult.setResultCallback(new ResultCallback<MessageApi.SendMessageResult>() { @Override public void onResult(MessageApi.SendMessageResult sendMessageResult) { Status status = sendMessageResult.getStatus(); Log.d("TAG", "Status: " + status.toString()); } }); } } }); }
メッセージ受信側
step1,2についてはDataApiの場合と同じことをしてGoogleApiClientのconnectメソッドを呼ぶ。
step3
DataApiのときの検知側とほぼいっしょ。メッセージを受信したい場合は、onConnectedメソッドでMessageApiに対してListenerを登録する。onPauseメソッドでListenerを削除する。
@Override public void onConnected(Bundle bundle) { Wearable.MessageApi.addListener(mGoogleApiClient, this); } @Override protected void onPause() { super.onPause(); if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { Wearable.MessageApi.removeListener(mGoogleApiClient, this); mGoogleApiClient.disconnect(); } }
Listenerとして登録するために、ActivityにMessageApi.MessageListenerインタフェースを実装させ、onMessageReceivedメソッドをオーバーライドする。
@Override public void onMessageReceived(MessageEvent messageEvent) { if (messageEvent.getPath().equals("/messagesample")) { String messagePayload = new String(messageEvent.getData()); Log.d("TAG", messagePayload); } }