android wear勉強会2

56
僕がAndroidWearアプリ を何も考えずに作った結果 AndroidWear勉強会#2 夜子まま

Upload: masafumi-terazono

Post on 08-Sep-2014

7 views

Category:

Technology


0 download

DESCRIPTION

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

TRANSCRIPT

Page 1: Android wear勉強会2

僕がAndroidWearアプリを何も考えずに作った結果

AndroidWear勉強会#2 夜子まま

Page 2: Android wear勉強会2

ストップウォッチが 出来るまで

Page 3: Android wear勉強会2

項目• 作ったきっかけとWearアプリへの落とし込み

• AndroidWearアプリの実装で躓いた点

• データ共有処理

• AndroidWearアプリで出来る事

• まとめ

Page 4: Android wear勉強会2

もともとはバスケの練習用

練習メニューの作成

ストップウォッチ、タイマー による計測

日々の記録とグラフ化

Page 5: Android wear勉強会2

今までは 標準のタイマーとEVERNOTEを併用

フォーマットは自由にできるので、とりあえず記録しておいて

いつかPCでなんとかしてやれ感

Page 6: Android wear勉強会2

WorkOutアプリ練習メニューを確認

メニューに応じて、タイマー、ストップウォッチ、回数表示の

機能に

Page 7: Android wear勉強会2

アプリ設計

DB

WorkOut WorkLog

DB

データ通知

WorkOutItem

Page 8: Android wear勉強会2

Wear側の設計ワーク名

タイマー

前回のタイム/最高記録

開始、停止ボタン

次のワークへ

Page 9: Android wear勉強会2

ここで問題に気づく

この仕様だと、設定された順番でしか ワークをこなせない、何らかの方法で

選択をしなければ使いにくい

Page 10: Android wear勉強会2

方法1:スピナー使えた

だけど画面が狭い

Page 11: Android wear勉強会2

方法2:ダイアログ

Sel

使えた だけど画面がごちゃご

ちゃしすぎ

Page 12: Android wear勉強会2

ここでふと別の問題に気づいた

Page 13: Android wear勉強会2

うち、双子だった

Page 14: Android wear勉強会2

この画面で二人分の記録を同時に出来るようにするには?

Page 15: Android wear勉強会2

二人以上の記録

• 記録にタグ付けをする

• Lap機能を追加し、数人の記録ができるようにする

Page 16: Android wear勉強会2

うーんっっ

Page 17: Android wear勉強会2

やめることにした

Page 18: Android wear勉強会2

そもそもWearableは 一人で使うもの

Page 19: Android wear勉強会2

1:1 1:N

こっち

Page 20: Android wear勉強会2

ストップウォッチに特化 してみることにした

Page 21: Android wear勉強会2

ストップウォッチ設計

DB

RecordTag RecordLog

DB

タグの追加

記録にタグ付け

Page 22: Android wear勉強会2

ストップウォッチ設計

DB

RecordTag RecordLog

DB

記録のグラフ表示

外部アプリとの連携

Page 23: Android wear勉強会2

ストップウォッチ設計

必要最低限の機能にとどめて、タグ付けや、削除といった作業はスマホ側

で行う。

Page 24: Android wear勉強会2

スマホとウォッチ間の 情報共有

Page 25: Android wear勉強会2

データ共有

Message Data Layer

Page 26: Android wear勉強会2

Message

指定されたpathにデータを送信する。 送信時に接続がなくても処理が終了するため、

一方通行なデータ送信に適している。

Page 27: Android wear勉強会2

送信実装例 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というけど、送信の可否程度で、結果を受信側で作成する仕組みはなさそう。

Page 28: Android wear勉強会2

リスナー実装例

@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);

Page 29: Android wear勉強会2

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で判断するのがいいみたい。

Page 30: Android wear勉強会2

僕がミスった例リスナーの解除しわすれをしていたため、同じ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にも事例あり

Page 31: Android wear勉強会2

DataLayer

DataItemやAssetsを共有データとしてStoreに登録する。 その後、変更通知を受け取るよう設定されたデバイスは

変更されたデータを取得することができる。 接続されていない場合は一旦待機し、接続された際に

変更を通知するので、同期的なデータ共有に適している。

store

Page 32: Android wear勉強会2

登録実装例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は内部で自動生成

一個ずつしか登録できないみたい

Page 33: Android wear勉強会2

取得実装例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を指定、あるいは全部取得の二択

登録数分配列で帰ってくるので、目的のものを検査して処理す

るみたい

Page 34: Android wear勉強会2

リスナー設定Activityの場合

DataListenerの実装だけでよい

Serviceの場合WearableListenerServieを実施、且つ AndroidManifestに一つFilterを追加する必要あり

Page 35: Android wear勉強会2

リスナーの実装例(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);

Page 36: Android wear勉強会2

リスナーの実装例(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も追加する

Page 37: Android wear勉強会2

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でないのがなんとなくしっくりこない。

Page 38: Android wear勉強会2

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

Page 39: Android wear勉強会2

僕がミスった例

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でも回避できた。

Page 40: Android wear勉強会2

おまけ

Page 41: Android wear勉強会2

幾つかやってて 気づいたこと

Page 42: Android wear勉強会2

Activityの遷移は出来る?

出来ました

Page 43: Android wear勉強会2

Fragmentの遷移は出来る?

出来ました

Page 44: Android wear勉強会2

Dialogは使える?

出来ました

Page 45: Android wear勉強会2

SQLiteは使える?

出来ました

Page 46: Android wear勉強会2

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

Page 47: Android wear勉強会2

SDCardない

“/system/etc/vold.fstab", “/etc/vold.fstab" はともに読み取りできませんでした。

Page 48: Android wear勉強会2

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は成功したけど、そのあと落ちた

Page 49: Android wear勉強会2

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.

Page 50: Android wear勉強会2

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.

Page 51: Android wear勉強会2

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.

Page 52: Android wear勉強会2

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.

Page 53: Android wear勉強会2

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.

Page 54: Android wear勉強会2

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.

へぇー

Page 55: Android wear勉強会2

やってみた感想

Page 56: Android wear勉強会2

思っていた以上になんでもできた。 僕はてっきり、Mobileのサブ画面的な位置付け

で考えていたので、データの同期は高速で、またほとんどの処理をモバイル側で暗黙的に処理されるような仕組みだ

と勝手に想像していた。 だけど実際やっぱり別デバイスなんだなと痛感させられた

だから理解に苦しんだところがあった。 しかし良く考えると、これはやはりAndorid端末なので、 なんでもやれるし、面白いデバイスだと思いました。