センサー マネージャーを使用してモバイル デバイスから歩数を測定します。

モバイルアプリに歩数データを表示するには、こちら ご覧くださいエクササイズ アプリの UI の設計と管理方法について詳しくは、 参照 基本的なフィットネス アプリを作成します

はじめに

お使いのスマートウオッチで基本的な歩数計の歩数測定を開始するには、 依存関係をアプリ モジュールに追加する必要があります。 build.gradle ファイル。最新バージョンの依存関係を使用していることを確認します。 また、アプリのサポートを Wear OS などの他のフォーム ファクタに拡張すると、 これらのフォーム ファクタに必要な依存関係を追加します。

UI の依存関係の例を以下に示します。完全なリストについては UI 要素のガイドをご覧ください。

implementation(platform("androidx.compose:compose-bom:2023.10.01"))
implementation("androidx.activity:activity-compose")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.material:material")

歩数計センサーを取得する

必要な操作の認識の権限をユーザーが付与した後、 歩数計センサーにアクセスできます。

  1. getSystemService() から SensorManager オブジェクトを取得します。
  2. SensorManager から歩数計センサーを取得します。
private val sensorManager by lazy {
        getSystemService(Context.SENSOR_SERVICE) as SensorManager }
private val sensor: Sensor? by lazy {
        sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) }

デバイスによっては、歩数計センサーが搭載されていません。センサーを確認してください デバイスにこのモードがない場合はエラー メッセージが表示されます。

if (sensor == null) {
    Text(text = "Step counter sensor is not present on this device")
}

フォアグラウンド サービスを作成する

基本的なフィットネス アプリでは、ボタン ユーザーが開始イベントと停止イベントを受け取り、ステップを追跡できるようにします。

センサーのベスト プラクティスを念頭に置いてください。 具体的には歩数計センサーは歩数をカウントし、センサーが 登録されます。センサー登録をフォアグラウンドに関連付ける サービスで必要な期間だけセンサーが登録され、 アプリがフォアグラウンドでなくても、登録されたままになります。

次のスニペットを使用して、onPause() メソッドでセンサーの登録を解除します。 使用します。

override fun onPause() {
    super.onPause()
    sensorManager.unregisterListener(this)
}

イベントのデータを分析する

センサーデータにアクセスするには、SensorEventListener インターフェースを実装します。備考 フォアグラウンド サービスの センサーの登録を解除して、サービスの一時停止時または終了時にセンサーの登録を解除します。「 次のスニペットは、SensorEventListener インターフェースを実装する方法を示しています。 Sensor.TYPE_STEP_COUNTER の場合:

private const val TAG = "STEP_COUNT_LISTENER"

context(Context)
class StepCounter {
    private val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
    private val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)

    suspend fun steps() = suspendCancellableCoroutine { continuation ->
        Log.d(TAG, "Registering sensor listener... ")

        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")

                    if (continuation.isActive) {
                        continuation.resume(stepsSinceLastReboot)
                    }
                }

                override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
                      Log.d(TAG, "Accuracy changed to: $accuracy")
                }
            }
       }

        val supportedAndEnabled = sensorManager.registerListener(listener,
                sensor, SensorManager.SENSOR_DELAY_UI)
        Log.d(TAG, "Sensor listener registered: $supportedAndEnabled")
    }
}

センサー イベントのデータベースを作成する

アプリに、歩数の推移を確認できる画面が表示される場合があります。 この機能をアプリで提供するには、Room 永続ライブラリを使用します。

次のスニペットは、歩数のセットを含むテーブルを作成します。 アプリが各測定値にアクセスした時刻とともに表示されます。

@Entity(tableName = "steps")
data class StepCount(
  @ColumnInfo(name = "steps") val steps: Long,
  @ColumnInfo(name = "created_at") val createdAt: String,
)

データアクセス オブジェクト(DAO)を作成する データを読み書きします。

@Dao
interface StepsDao {
    @Query("SELECT * FROM steps")
    suspend fun getAll(): List<StepCount>

    @Query("SELECT * FROM steps WHERE created_at >= date(:startDateTime) " +
            "AND created_at < date(:startDateTime, '+1 day')")
    suspend fun loadAllStepsFromToday(startDateTime: String): Array<StepCount>

    @Insert
    suspend fun insertAll(vararg steps: StepCount)

    @Delete
    suspend fun delete(steps: StepCount)
}

DAO をインスタンス化するには、RoomDatabase オブジェクトを作成します。

@Database(entities = [StepCount::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun stepsDao(): StepsDao
}

センサーデータをデータベースに保存する

ViewModel は新しい StepCounter クラスを使用するため、ステップをすぐに保存できます 説明します。

viewModelScope.launch {
    val stepsFromLastBoot = stepCounter.steps()
    repository.storeSteps(stepsFromLastBoot)
}

repository クラスは次のようになります。

class Repository(
    private val stepsDao: StepsDao,
) {

    suspend fun storeSteps(stepsSinceLastReboot: Long) = withContext(Dispatchers.IO) {
        val stepCount = StepCount(
            steps = stepsSinceLastReboot,
            createdAt = Instant.now().toString()
        )
        Log.d(TAG, "Storing steps: $stepCount")
        stepsDao.insertAll(stepCount)
    }

    suspend fun loadTodaySteps(): Long = withContext(Dispatchers.IO) {
        printTheWholeStepsTable() // DEBUG

        val todayAtMidnight = (LocalDateTime.of(LocalDate.now(), LocalTime.MIDNIGHT).toString())
        val todayDataPoints = stepsDao.loadAllStepsFromToday(startDateTime = todayAtMidnight)
        when {
            todayDataPoints.isEmpty() -> 0
            else -> {
                val firstDataPointOfTheDay = todayDataPoints.first()
                val latestDataPointSoFar = todayDataPoints.last()

                val todaySteps = latestDataPointSoFar.steps - firstDataPointOfTheDay.steps
                Log.d(TAG, "Today Steps: $todaySteps")
                todaySteps
            }
        }
    }
}

センサーデータを定期的に取得する

フォアグラウンド サービスを使用する場合、WorkManager を構成する必要はありません なぜなら、アプリがユーザーの歩数をアクティブに追跡している間は、 更新された合計歩数がアプリに表示されます。

ただし、歩数レコードをバッチ処理する場合は、WorkManager を使用して以下を行うことができます。 15 分ごとなど、特定の間隔で歩数を測定します。 WorkManager は、バックグラウンドを実行するコンポーネントです。 確実に実行されます詳しくは、WorkManager Codelab をご覧ください。

データを取得するように Worker オブジェクトを構成するには、doWork() をオーバーライドします。 メソッドを呼び出します。次のコード スニペットをご覧ください。

private const val TAG = " StepCounterWorker"

@HiltWorker
class StepCounterWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    val repository: Repository,
    val stepCounter: StepCounter
) : CoroutineWorker(appContext, workerParams) {

    override suspend fun doWork(): Result {
        Log.d(TAG, "Starting worker...")

        val stepsSinceLastReboot = stepCounter.steps().first()
        if (stepsSinceLastReboot == 0L) return Result.success()

        Log.d(TAG, "Received steps from step sensor: $stepsSinceLastReboot")
        repository.storeSteps(stepsSinceLastReboot)

        Log.d(TAG, "Stopping worker...")
        return Result.success()
    }
}

現在の歩数を 15 分ごとに保存するように WorkManager を設定するには、次のようにします。 次のとおりです。

  1. Application クラスを拡張して Configuration.Provider を実装する 行うことができます。
  2. onCreate() メソッドで、PeriodicWorkRequestBuilder をキューに追加します。

このプロセスを次のコード スニペットに示します。

@HiltAndroidApp
@RequiresApi(Build.VERSION_CODES.S)
internal class PulseApplication : Application(), Configuration.Provider {

    @Inject
    lateinit var workerFactory: HiltWorkerFactory

    override fun onCreate() {
        super.onCreate()

        val myWork = PeriodicWorkRequestBuilder<StepCounterWorker>(
                15, TimeUnit.MINUTES).build()

        WorkManager.getInstance(this)
            .enqueueUniquePeriodicWork("MyUniqueWorkName",
                    ExistingPeriodicWorkPolicy.UPDATE, myWork)
    }

    override val workManagerConfiguration: Configuration
        get() = Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .setMinimumLoggingLevel(android.util.Log.DEBUG)
            .build()
}

アプリのステップへのアクセスを制御するコンテンツ プロバイダを初期化する アプリの起動時に直ちにカウンタ データベースを作成する場合は、 次のように指定します。

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove" />