Androidで歩数計実装を考える
Androidで歩数計を実装して、登竜門Fチームのやつをやりたい
方針:
Foreground Serviceで歩数カウントする。
-
STEP_DETECTOR
センサーで起動する。 - 端末起動時に起動する
調べてると、Google Fitとかから取る方法が出てくるけど、Sensorでできるならそれでええやろの顔をしてる。何かトラップがあるのかな。
Foreground Serviceの調査
Foreground Service
Foreground Serivce
SerivceはActivityが落ちても実行され続けられるやつ。(デフォだとプロセスは同じになるらしい)Foreground ServiceとBackground Serviceがある。
その中でもForeground Serviceはユーザが見えやすいタイプのService。よく見るのだとダウンロード処理とか音楽の再生とかが実装されがち。
Background Serviceは通知がないためユーザがその存在に気づきにくい。そのため最近制限が厳しくなった記憶がある。
なんかカウントしてくれてる感が出るのとカウントされてるされてないのデバッグがしやすいとかあるので今回はForeground Serviceを使いたい。
ドキュメントにForeground Serviceで今回実装したいのが例が紹介されていた。
ユーザーから許可を得た後、フォアグラウンド サービスでユーザーのランニングを記録するフィットネス アプリ。通知には、現在のフィットネス セッション中にユーザーが移動した距離が表示される場合があります。
多くのユースケースでは、フォアグラウンドサービスの代わりに使用できる専用のプラットフォームやJetpack APIがあります。そのようなAPIがある場合、ほとんどの場合、フォアグラウンドサービスの代わりにそれを使用することが望ましいです。詳細については、フォアグラウンドサービスの代わりに専用のAPIを使用するを参照してください。
https://developer.android.com/develop/background-work/services/foreground-services
「大体のAPIがあるならそれを使ったほうがええで」とのこと。
Foreground Serviceの種類と代替のAPIは以下に載ってる。
今回は種類で言ったらHealthだと思うが、Health にはAlternatives はないので、今回は手作りせねばいけなそう。
ユーザーはデフォルトで通知を解除できる
ユーザが通知を解除できないようにするには、Notification.Builderを使用して通知を作成するときに、setOngoing()メソッドにtrueを渡します。
デフォだと削除できるらしい。今回だと (設計にもよるが) setOngoing(true)
しないといけなさそう...?
Services that show a notification immediately
https://developer.android.com/develop/background-work/services/foreground-services#notification-immediate
これ何言ってるかようわからんかったな
作り方
1. AndroidManifest.xmlに定義
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
<application ...>
<service
android:name=".MyMediaPlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="false">
</service>
-
foregroundServiceType
にはここのタイプを設定する - 設定しないと(API Levelによっては) エラーが発生する。
2. 権限の付与
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
実行時の権限リクエスト は要らない。
3. 権限等の準備
Android 14(APIレベル34)以降、フォアグラウンド・サービスを起動すると、サービスの種類に応じて特定の前提条件があるかどうかがチェックされます。
必要な権限はForeground Serviceを起動する前にチェック・取得しておこう。
4. Foreground Service スタート
val intent = Intent(...) // Build the intent for the service
context.startForegroundService(intent)
サービス内部で、通常はonStartCommand()の中で、サービスをフォアグラウンドで実行するように要求できます。そのためには、ServiceCompat.startForeground() を呼び出します(androidx-core 1.12以降で使用可能)。このメソッドは以下のパラメータを取る:
- サービス
- ステータスバーで通知を一意に識別する正の整数
- 通知オブジェクト自体
- サービスによって行われる作業を識別するフォアグラウンドサービスタイプ
5. サービスを実装
val notification = NotificationCompat.Builder(this, "CHANNEL_ID")
// Create the notification to display while the service is running
.build()
ServiceCompat.startForeground(
/* service = */ this,
/* id = */ 100, // Cannot be 0
/* notification = */ notification,
/* foregroundServiceType = */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
} else {
0
},
)
- NotificationChannelって作らなくていいのかな。あとで検証しよう。
6. 終了する
サービスをフォアグラウンドから削除するには、stopForeground() を呼び出します。このメソッドは、ステータス・バー通知も削除するかどうかを示すブール値を取ります。サービスは実行され続けることに注意してください。
サービスがフォアグラウンドで実行されている間にサービスを停止すると、その通知は削除されます。
https://developer.android.com/develop/background-work/services/foreground-services#remove-from-foreground
Foreground Serviceはユーザが止めれるから注意してね
コールバックもないから終わっても受け取れないらしい。(諸々がメモリから削除されるからしゃーないみたいな顔してそう)ApplicationExitInfoを使って再起動した時に理由をチェックすると良さげとのこと。
ユーザーが 停止REASON_USER_REQUESTEDボタンをタップしても、システムはアプリにコールバックを送信しません。アプリが再起動したら、API の一部である理由を確認すると役立ちます ApplicationExitInfo。
https://developer.android.com/develop/background-work/services/foreground-services#handle-user-initiated-stop
その他
長いし、エッジケースの話っぽさそうなので流し読みした。
- https://developer.android.com/develop/background-work/services/foreground-services#exemptions
- https://developer.android.com/develop/background-work/services/foreground-services#purpose-built-apis
- https://developer.android.com/develop/background-work/services/foreground-services#bg-access-restrictions
最後に触れてみて完全に理解したい
サービスの知識が若干なくて辛い...
📝
ServiceのCoroutineScopeとかは と思ったらdeprecatedになってた。androidx.lifecycle:lifecycle-service
についてくるLifecycleServiceを使ってServiceを定義することで取得できる。
多分 androidx.lifecycle:lifecycle-service
を使うと良さそう。
いい感じのサンプル見つけた
ちょっと古くてちょくちょく動かん。
通知が表示されんなー
Android 13(API レベル 33)以降では、ユーザーが通知権限を拒否した場合、フォアグラウンド サービスに関する通知はタスク マネージャーに引き続き表示されますが、通知ドロワーには表示されません。
とのことなので実行時の権限もいるらしいわ。めんどいな。
とりあえず表示できたわ。
Android 14からsetOngoingが意味なくなるというトラップを引いた。
つまりAndroid 14以降Foreground Serviceの通知はユーザによって消すことができてしまうという。
紆余曲折ありましたが、一旦これでdoneかと思われるるる
つづいてはセンサーの話。
この辺りかな?
基本の流れは以下の通りの認識。
// sensorManager, sensorを取得
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
// sensorListenerを登録して歩数が増えたときに何かしらの処理を行う。
val listener: SensorEventListener by lazy {
object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
if (event == null) return
val stepsSinceLastReboot = event.values[0].toLong()
Log.d(TAG, "Steps since last reboot: $stepsSinceLastReboot")
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
Log.d(TAG, "Accuracy changed to: $accuracy")
}
}
}
sensorManager.registerListener(listener,
sensor, SensorManager.SENSOR_DELAY_UI)
onSensorChanged
の event.values[0].toLong()
で最後の起動後からの歩数を取得できるのね fmfm
一旦センサーから取得した値を保存して表示できた
1日分のデータを取るのむずそう
あと、端末起動した時にもFGSを起動しないといけない
ヘルスコネクト使った方がいいよと代表は言うが、ユーザビリティ最悪なんだよな、あれ。
📝 後で読む
ただ歩数以外のセンシティブなデータ (消費カロリーとか)も取れそうだから一度設定してしまえばできることが広がりそう。
取れるデータを見てみて、「これ使ってこんな機能作れそう」とか話してもらうと良さそう。
どんな機能を作りたいかは彼らに一任すべきだと思う。多分以下のいずれかになるとは思うが。
- いろんな情報を取りたいのでヘルスコネクトにしてみる。
- 一旦FGS/STEP_COUNTER Sensorで作って、必要が出てきたらヘルスコネクトにしてみる
- 歩数以外要らなそうだが将来的な幅を持たせておきたいので、ヘルスコネクトで実装
- 歩数以外要らなそうだから実装簡単/シンプル/センサーの勉強になるので、FGS/STEP_COUNTER Sensor なの方が良さそう
- どっちでも作ってみて良さそうな方を採用する
health connect使うとwear osと絡めたりとかもできそうね
health connect
FGS / Sensor
比較表
項目 | health connect | FGS/Sensor |
---|---|---|
機能性 | ⭕️。一度セットアップさえしてしまえば、色んなアプリから取得したデータを簡単に取り込めそうなので将来的に色んな機能を作り込むことができそう。 | ❌。新しい機能を作るためには、都度センサー等から取得する必要がある。他アプリや他のデバイスから取得したいみたいな要件(ex: スマホとWearOS繋ぐとか) があると場合によってはツムツム。 |
保守性[1] | ⭕️? 現状 Googleが推してるっぽい。Android14以降OSに組み込まれてるのでGoogle Fit APIみたいに廃れることはなさそうかな...? | △。今後、Googleがhelth connectを推進するために普通のアプリがセンサーからは取得できないようにしてもおかしくない。 |
実装難易度 | 高。Qiita等の記事が少なめ。 | 比較的 低。昔からある方法なので 記事は多め。FGSやセンサーの勉強にもなるかも。 |
ユーザ視点の使いやすさ | ❌。ユーザ側で権限の設定等 色々やることがある。 | ⭕️。FGSを開始すれば取得できる。 |
-
Googleの匙加減次第で振り回される可能性はありそうという意味ではどちらも変わらない気はするけども。 ↩︎
なんか更新を要求されるんだが?
AQUOSとPixelで検証したがどっちも同じ結果でした
これが原因の香り
<!-- For supported versions through Android 13, create an activity to show the rationale
of Health Connect permissions once users click the privacy policy link. -->
<activity
android:name=".PermissionsRationaleActivity"
android:exported="true">
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
</activity>
<!-- For versions starting Android 14, create an activity alias to show the rationale
of Health Connect permissions once users click the privacy policy link. -->
<activity-alias
android:name="ViewPermissionUsageActivity"
android:exported="true"
android:targetActivity=".PermissionsRationaleActivity"
android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
<intent-filter>
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
</intent-filter>
</activity-alias>
貼って、適当なPermissionsRationaleActivity生やしたが特に変わらず
Codelab頼りにせずに、おとなしくこいつを実装した方が速そう
PixelもAQUOSもAndroid14のはずなのにヘルスコネクトアプリを入れなきゃいけなかったのもモヤモヤポイント
そうだ
フォーム 書かないといけない んだった
パーミッションの宣言
健康とフィットネスのデータへのアクセスは機密です。Health Connectは、読み取りと書き込みの操作にセキュリティ層を実装し、ユーザーの信頼を維持します。必要なデータタイプに基づいて、AndroidManifest.xmlファイルで読み取りと書き込みの権限を宣言します。フォームに記入した後、アクセスを要求したパーミッションのセットを使用していることを確認してください。
Health Connectは、標準のAndroid権限宣言形式を使用します。<uses-permission>タグでパーミッションを割り当てます。<manifest>タグ内にネストします。
https://developer.android.com/health-and-fitness/guides/health-connect/develop/get-started#declare-permissions
リクエストを提出する前に、サードパーティの開発者は Play ストアでアプリを公開する 必要 があります。アプリがまだ開発中であっても、リクエストを提出する必要がある場合は、アプリを公開することをお勧めします。可能な場合は、サードパーティの開発者は、 リクエストの処理がスムーズに行われるように、アプリの Play ストア ページにアプリのプライバシー ポリシーも掲載する必要があります。
https://docs.google.com/forms/d/1LFjbq1MOCZySpP5eIVkoyzXTanpcGTYQH26lKcrQUJo/viewform?edit_requested=true
詰んじゃうくね?
この要請は、Playストアで公開されているアプリにのみ適用されることに注意してください。ただし、サードパーティの開発者は引き続きローカルビルドでデータ型にアクセスできるため、この期間中にローカル環境での開発、統合、テストが制限されたり妨げられたりすることはありません。
とあるので詰んでるわけではなさそう。
多分ローカル環境で実行してることを何らかの方法でヘルスコネクトに教えないといけない
一応フォームに「アプリケーションは現在 Play ストアで公開されていますか?」という欄があるので、変なアプリじゃなければ審査 通るんかなぁシランケド