Android wear勉強会2

6,361 views

Published on

android were勉強会#2
東京で行われました。

Published in: Technology
  • Be the first to comment

Android wear勉強会2

  1. 1. 僕がAndroidWearアプリ を何も考えずに作った結果 AndroidWear勉強会#2 夜子まま
  2. 2. ストップウォッチが 出来るまで
  3. 3. 項目 • 作ったきっかけとWearアプリへの落とし込み • AndroidWearアプリの実装で躓いた点 • データ共有処理 • AndroidWearアプリで出来る事 • まとめ
  4. 4. もともとはバスケの練習用 練習メニューの作成 ストップウォッチ、タイマー による計測 日々の記録とグラフ化
  5. 5. 今までは 標準のタイマーとEVERNOTEを併用 フォーマットは自由にできるので、 とりあえず記録しておいて いつかPCでなんとかしてやれ感
  6. 6. WorkOutアプリ 練習メニューを確認 メニューに応じて、タイマー、 ストップウォッチ、回数表示の 機能に
  7. 7. アプリ設計 DB WorkOut WorkLog DB データ通知 WorkOut Item
  8. 8. Wear側の設計 ワーク名 タイマー 前回のタイム/最高記録 開始、停止ボタン 次のワークへ
  9. 9. ここで問題に気づく この仕様だと、設定された順番でしか ワークをこなせない、何らかの方法で 選択をしなければ使いにくい
  10. 10. 方法1:スピナー 使えた だけど画面が狭い
  11. 11. 方法2:ダイアログ Sel 使えた だけど画面がごちゃご ちゃしすぎ
  12. 12. ここでふと別の問題に 気づいた
  13. 13. うち、双子だった
  14. 14. この画面で二人分の記録を同 時に出来るようにするには?
  15. 15. 二人以上の記録 • 記録にタグ付けをする • Lap機能を追加し、数人の記録ができるようにす る
  16. 16. うーんっっ
  17. 17. やめることにした
  18. 18. そもそもWearableは 一人で使うもの
  19. 19. 1:1 1:N こっち
  20. 20. ストップウォッチに特化 してみることにした
  21. 21. ストップウォッチ設計 DB RecordTag RecordLog DB タグの追加 記録にタグ付け
  22. 22. ストップウォッチ設計 DB RecordTag RecordLog DB 記録のグラフ表示 外部アプリとの連携
  23. 23. ストップウォッチ設計 必要最低限の機能にとど めて、タグ付けや、削除 といった作業はスマホ側 で行う。
  24. 24. スマホとウォッチ間の 情報共有
  25. 25. データ共有 Message Data Layer
  26. 26. Message 指定されたpathにデータを送信する。 送信時に接続がなくても処理が終了するため、 一方通行なデータ送信に適している。
  27. 27. 送信実装例 DataMap dataMap = new DataMap(); dataMap.putLong(KEY_START_TIME, startTime); Collection<String> nodes = getNodes(); for (String node : nodes) { Wearable.MessageApi.sendMessage( mGoogleApiClient, node, START_TIMER_PATH, dataMap.toByteArray()).setResultCallback( new ResultCallback<MessageApi.SendMessageResult>() { @Override public void onResult(MessageApi.SendMessageResult sendMessageResult) { if (!sendMessageResult.getStatus().isSuccess()) { Log.e(TAG, "Failed to send message with status code: " + sendMessageResult.getStatus().getStatusCode()); } } } ); } DataMapクラスを 使うとbyte[]化が楽 OnResultというけど、送信の 可否程度で、結果を受信側で作 成する仕組みはなさそう。
  28. 28. リスナー実装例 @Override public void onMessageReceived(MessageEvent messageEvent) { Log.d(TAG, "onMessageReceived: " + messageEvent); } MessageApi.MessageListenerを implementsし 下記のメソッドをOverrideする Wearable.MessageApi.addListener(mGoogleApiClient, this); 実装したリスナーの登録 実装したリスナーの解除 Wearable.MessageApi.removeListener(mGoogleApiClient, this);
  29. 29. onMessageReceiveの実装例 @Override public void onMessageReceived(MessageEvent messageEvent) { Log.d(TAG, "onMessageReceived: " + messageEvent); ! // Check to see if the message is to start an activity if (messageEvent.getPath().equals(START_ACTIVITY_PATH)) { Intent startIntent = new Intent(this, MainWearActivity.class); startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(startIntent); } } MessageEventに登録したPathで 判断するのがいいみたい。
  30. 30. 僕がミスった例 リスナーの解除しわすれをしていたため、同じMessage が何度も飛んできた。 アプリを再起動しても解除されないので???な状況に 07-11 10:54:57.962 5509-5672/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.962 5509-5610/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.962 5509-5660/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.962 5509-5521/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.962 5509-5539/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.972 5509-5570/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.972 5509-5604/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.972 5509-5601/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.972 5509-5566/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.972 5509-5520/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.972 5509-5582/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.972 5509-5567/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:57.982 5509-5587/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:58.002 5509-5581/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:58.052 5509-5581/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:58.052 5509-5587/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! 07-11 10:54:58.062 5509-5570/com.wearTest.message D/DEVELOPER﹕ ......Wear: successfully received phone to wear communication! http://stackoverflow.com/questions/24702402/android-mobile-wear- app-onmessagereceived-called-multiple-times-for-one-message Stackoverflowにも事例あり
  31. 31. DataLayer DataItemやAssetsを共有データとしてStoreに登録する。 その後、変更通知を受け取るよう設定されたデバイスは 変更されたデータを取得することができる。 接続されていない場合は一旦待機し、接続された際に 変更を通知するので、同期的なデータ共有に適している。 store
  32. 32. 登録実装例 PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(COUNT_PATH); putDataMapRequest.getDataMap().putInt(COUNT_KEY, count++); PutDataRequest request = putDataMapRequest.asPutDataRequest(); ! if (!mGoogleApiClient.isConnected()) { return; } Wearable.DataApi.putDataItem(mGoogleApiClient, request) .setResultCallback(new ResultCallback<DataItemResult>() { @Override public void onResult(DataItemResult dataItemResult) { if (!dataItemResult.getStatus().isSuccess()) { Log.e(TAG, "ERROR: failed to putDataItem, status code: " + dataItemResult.getStatus().getStatusCode()); } } }); 登録用のデータを作成 Uriは内部で自動生成 一個ずつしか登録でき ないみたい
  33. 33. 取得実装例 PendingResult<DataItemBuffer> results = Wearable.DataApi.getDataItems(mGoogleApiClient); results.setResultCallback(new ResultCallback<DataItemBuffer>() { @Override public void onResult(DataItemBuffer dataItems) { if (dataItems.getCount() != 0) { DataMapItem dataMapItem = DataMapItem.fromDataItem(dataItems.get(0)); ! // This should read the correct value. int value = dataMapItem.getDataMap().getInt(COUNT_KEY); } dataItems.release(); } }); 登録したUriを指定、あ るいは全部取得の二択 登録数分配列で帰ってくるので、 目的のものを検査して処理す るみたい
  34. 34. リスナー設定 Activityの場合 DataListenerの実装だけでよい Serviceの場合 WearableListenerServieを実施、且つ AndroidManifestに一つFilterを追加する必要あり
  35. 35. リスナーの実装例(Activity) @Override public abstract void onDataChanged (DataEventBuffer dataEvents) { Log.d(TAG, "onDataChanged: " + dataEvents); } Activityの場合はDataApi.DataListenerを implementsし 下記のメソッドをOverrideする Wearable.DataApi.addListener(mGoogleApiClient, this); Activityの場合はさらに、実装したリスナーの登録も必要 登録したリスナーの解除は必ず行うようにすること Wearable.DataApi.removeListener(mGoogleApiClient, this);
  36. 36. リスナーの実装例(Service) @Override public abstract void onDataChanged (DataEventBuffer dataEvents) { Log.d(TAG, "onDataChanged: " + dataEvents); } Serviceの場合はWearableListenerServiceを extendsし 下記のメソッドをOverrideする <service android:name=".HomeListenerService"> <intent-filter> <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> </intent-filter> </service> AndroidManifest.xmlのServiecの定義に下記のFilterも追加する
  37. 37. onDataChangedの実装例 @Override public void onDataChanged(DataEventBuffer dataEvents) { final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents); dataEvents.close(); for (DataEvent event : events) { String path = event.getDataItem().getUri().getPath(); if (event.getType() == DataEvent.TYPE_CHANGED) { Log.d(TAG, "request path:" + path); } else if (event.getType() == DataEvent.TYPE_DELETED) { } else { } } } データの更新種別によって処理を振り分ける。 Createでないのがなんとなくしっくりこない。
  38. 38. Uriの生成private Uri getUriForDataItem() { // ローカルのノード上のデータのUriの場合 String nodeId = getLocalNodeId(); // リモートの場合は getRemoteNodeId()を使う、またはすでに固定で知っているなら直接打ち込んでもいい return new Uri.Builder().scheme(PutDataRequest.WEAR_URI_SCHEME).authority(nodeId).path("/path_to_data").build(); } ! private String getLocalNodeId() { NodeApi.GetLocalNodeResult nodeResult = Wearable.NodeApi.getLocalNode(mGoogleApiClient).await(); return nodeResult.getNode().getId(); } ! private String getRemoteNodeId() { HashSet<String> results = new HashSet<String>(); NodeApi.GetConnectedNodesResult nodesResult = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await(); List<Node> nodes = nodesResult.getNodes(); if (nodes.size() > 0) { return nodes.get(0).getId(); } return null } StackOverflowをみてたら いいのを見つけた。 http://stackoverflow.com/questions/24601251/what-is-the-uri- for-wearable-dataapi-getdataitem-after-using-putdatamaprequest
  39. 39. 僕がミスった例 07-15 19:16:10.185 9549-9569/? D/MainActivity﹕ onDataChanged: com.google.android.gms.wearable.DataEventBuffer@191106b8 07-15 19:16:10.186 9549-9569/? D/MainActivity﹕ request path:/action-add 07-15 19:16:10.203 9549-9569/? W/Binder﹕ Caught a RuntimeException from the binder stub implementation. java.lang.SecurityException: Permission Denial: reading com.kayosystem.android.easystopwatch.RecordContentProvider uri content://com.kayosystem.android.easystopwatch/ tb_recordlog from pid=0, uid=10009 requires the provider be exported, or grantUriPermission() at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:498) at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:425) at android.content.ContentProvider$Transport.query(ContentProvider.java:195) at android.content.ContentResolver.query(ContentResolver.java:466) at android.content.ContentResolver.query(ContentResolver.java:410) at com.kayosystem.android.easystopwatch.MainActivity.registerdCheck(MainActivity.java:326) at com.kayosystem.android.easystopwatch.MainActivity.onDataChanged(MainActivity.java:211) at com.google.android.gms.wearable.internal.av.d(Unknown Source) at com.google.android.gms.wearable.internal.ac$a.onTransact(Unknown Source) at android.os.Binder.execTransact(Binder.java:404) 07 Wear側からDataLayerを使って変更を通知し、Mobile側 でContentProviderでデータ・アクセスしたら落ちた。 OnDataChangeはWearのプロセスIDからの実行になっている。 処理の前に long token =Binder.clearCallingIdentity()をよび、処理がおわっ たら、Binder.restoreCallingIdentity(token)を呼ぶ。 あるいは、runOnUiThreadでも回避できた。
  40. 40. おまけ
  41. 41. 幾つかやってて 気づいたこと
  42. 42. Activityの遷移は出来る? 出来ました
  43. 43. Fragmentの遷移は出来る? 出来ました
  44. 44. Dialogは使える? 出来ました
  45. 45. SQLiteは使える? 出来ました
  46. 46. Environment File data = Environment.getDataDirectory(); File external = Environment.getExternalStorageDirectory(); File download = Environment.getDownloadCacheDirectory(); File root = Environment.getRootDirectory(); ! Log.d(TAG, "data:" + data.getAbsolutePath()); Log.d(TAG, "external:" + external.getAbsolutePath()); Log.d(TAG, "download:" + download.getAbsolutePath()); Log.d(TAG, "root:" + root.getAbsolutePath()); 07-14 11:09:50.600 14757-14757/com.kayosystem.android.wearsample D/Fragment1﹕ data:/data 07-14 11:09:50.610 14757-14757/com.kayosystem.android.wearsample D/Fragment1﹕ external:/storage/emulated/0 07-14 11:09:50.610 14757-14757/com.kayosystem.android.wearsample D/Fragment1﹕ download:/cache 07-14 11:09:50.610 14757-14757/com.kayosystem.android.wearsample D/Fragment1﹕ root:/system
  47. 47. SDCard ない /system/etc/vold.fstab", /etc/vold.fstab" はともに読み取りできませんでした。
  48. 48. URLConnectionは使えるか? 使えない 07-14 11:42:34.495 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ java.io.EOFException 07-14 11:42:34.495 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ at com.android.okhttp.internal.Util.readAsciiLine(Util.java:342) 07-14 11:42:34.495 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ at com.android.okhttp.internal.http.RawHeaders.fromBytes(RawHeaders.java:311) 07-14 11:42:34.495 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ at com.android.okhttp.internal.http.HttpTransport.readResponseHeaders(HttpTransport.java:135) 07-14 11:42:34.495 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:644) 07-14 11:42:34.495 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:353) 07-14 11:42:34.495 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:297) 07-14 11:42:34.495 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ at com.android.okhttp.internal.http.HttpURLConnectionImpl.getHeaderFields(HttpURLConnectionImpl.java:161) 07-14 11:42:34.495 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ at com.kayosystem.android.wearsample.Fragment1.getTechbooster(Fragment1.java:242) 07-14 11:42:34.495 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ at com.kayosystem.android.wearsample.Fragment1.access $300(Fragment1.java:36) 07-14 11:42:34.505 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ at com.kayosystem.android.wearsample.Fragment1$5.run(Fragment1.java:218) 07-14 11:42:34.505 1208-1236/com.kayosystem.android.wearsample W/System.err﹕ at java.lang.Thread.run(Thread.java:841) ペアリングする前だと、UnknownHostでエラーが発生 ペアリング後だと、Connectionは成功したけど、そのあと落ちた
  49. 49. PackageManagerの hasFeatureの結果(LGG) android.software.app_widgets not supported. android.hardware.audio.low_latency not supported. android.software.backup not supported. android.hardware.bluetooth supported. android.hardware.bluetooth_le supported. android.hardware.camera not supported. android.hardware.camera.any not supported. android.hardware.camera.autofocus not supported. android.hardware.camera.external not supported.
  50. 50. PackageManagerの hasFeatureの結果(LGG) android.hardware.camera.flash not supported. android.hardware.camera.front not supported. android.hardware.consumerir not supported. android.software.device_admin not supported. android.hardware.faketouch supported. android.hardware.faketouch.multitouch.distinct not supported. android.hardware.faketouch.multitouch.jazzhand not supported. android.software.home_screen supported. android.software.input_methods not supported.
  51. 51. PackageManagerの hasFeatureの結果(LGG) android.software.leanback not supported. android.software.leanback_only not supported. android.software.live_wallpaper not supported. android.hardware.location supported. android.hardware.location.gps not supported. android.hardware.location.network not supported. android.hardware.microphone supported. android.hardware.nfc not supported. android.hardware.nfc.hce not supported. android.hardware.nfc.hce not supported.
  52. 52. PackageManagerの hasFeatureの結果(LGG) android.software.print not supported. android.hardware.screen.landscape not supported. android.hardware.screen.portrait supported. android.hardware.sensor.accelerometer supported. android.hardware.sensor.barometer not supported. android.hardware.sensor.compass supported. android.hardware.sensor.gyroscope supported. android.hardware.sensor.heartrate not supported. android.hardware.sensor.light not supported.
  53. 53. PackageManagerの hasFeatureの結果(LGG) android.hardware.sensor.proximity not supported. android.hardware.sensor.stepcounter supported. android.hardware.sensor.stepdetector supported. android.software.sip not supported. android.software.sip.voip not supported. android.hardware.telephony not supported. android.hardware.telephony.cdma not supported. android.hardware.telephony.gsm not supported. android.hardware.type.television not supported. android.hardware.touchscreen supported.
  54. 54. PackageManagerの hasFeatureの結果(LGG) android.hardware.touchscreen.multitouch supported. android.hardware.touchscreen.multitouch.distinct not supported. android.hardware.touchscreen.multitouch.jazzhand not supported. android.hardware.usb.accessory supported. android.hardware.usb.host not supported. android.hardware.type.watch supported. android.software.webview not supported. android.hardware.wifi not supported. android.hardware.wifi.direct not supported. へぇー
  55. 55. やってみた感想
  56. 56. 思っていた以上になんでもできた。 僕はてっきり、Mobileのサブ画面的な位置付け で考えていたので、データの同期は高速で、またほとんど の処理をモバイル側で暗黙的に処理されるような仕組みだ と勝手に想像していた。 だけど実際やっぱり別デバイスなんだなと痛感させられた だから理解に苦しんだところがあった。 しかし良く考えると、これはやはりAndorid端末なので、 なんでもやれるし、面白いデバイスだと思いました。

×