歩数を記録する

ヘルスコネクトは、StepsRecord を使用して歩数を記録するための歩数データ型を提供します。歩数は、健康とフィットネスのトラッキングにおける基本的な測定値です。

モバイルの歩数を読み取る

Android 14(API レベル 34)と SDK 拡張機能バージョン 20 以降では、ヘルスコネクトがデバイス上の歩数計を提供します。アプリに READ_STEPS 権限が付与されている場合、ヘルスコネクトは Android 搭載デバイスから歩数の取得を開始し、ユーザーはヘルスコネクトの [歩数] エントリに歩数データが自動的に追加されるのを確認できます。

デバイス上の歩数計が利用可能かどうかを確認するには、デバイスが Android 14(API レベル 34)を搭載し、SDK 拡張機能バージョン 20 以上を備えていることを確認します。

val isStepTrackingAvailable =
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
        SdkExtensions.getExtensionVersion(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) >= 20

アプリが aggregate を使用して歩数の合計を読み取り、DataOrigin でフィルタリングしていない場合、デバイス上の歩数は自動的に合計に含まれるため、2026 年 6 月のアップデートで変更は必要ありません。

デバイス上のステップのアトリビューションの変更

2026 年 6 月のアップデート以降、ヘルスコネクトでネイティブにトラッキングされた歩数は、合成パッケージ名(SPN)com.android.healthconnect.phone.jd5bdd37e1a8d3667a05d0abebfc4a89e など)に帰属します。

以前は、組み込みステップはパッケージ名 android に関連付けられていました。2026 年 6 月より前に記録された過去の歩数データは、android パッケージ名を保持します。

SPN はデバイス固有であり、ユーザーのプライバシーを保護するためにアプリケーションごとにスコープが設定されています。

  • 安定: 現在のデバイスの SPN は、アプリにとって安定しています。
  • アプリケーション スコープ: 同じデバイス上の異なるアプリケーションは、デバイス上の歩数データに対して異なる SPN を認識します。

オンデバイスの歩数をクエリする

SPN はスコープが設定され、デバイス固有であるため、SPN 値をハードコードしてはなりません。代わりに、getCurrentDeviceDataSource() API を使用して現在のデバイスの SPN を取得します。

デバイス上の歩数計には SDK 拡張機能バージョン 20 以降が必要ですが、getCurrentDeviceDataSource() API は SDK 拡張機能バージョン 11 以降を搭載した Android 14(API レベル 34)で利用できます。

getCurrentDeviceDataSource() API は、ヘルスコネクト Jetpack ライブラリではまだ利用できません。次の例では、代わりに Android フレームワーク API を使用します。

import android.content.Context
import android.health.connect.HealthConnectManager

val healthConnectManager = context.getSystemService(HealthConnectManager::class.java)
val deviceDataSource = healthConnectManager?.getCurrentDeviceDataSource()
val currentDeviceSpn = deviceDataSource?.deviceDataOrigin?.packageName

アプリがデバイス上の歩数を読み取る必要がある場合、またはソースアプリやデバイスごとに歩数データを表示する場合は、DataOriginandroid または デバイスの SPN と一致するレコードをクエリする必要があります。アプリで歩数データの帰属を表示する場合は、metadata.device を使用して個々のレコードのソースデバイスを特定します。集計データ内の SPN で識別されるオンデバイスの手順については、DeviceDataSourcemodelmanufacturer などのデバイス メタデータを使用してアトリビューションを行うか、「スマートフォン」などの一般的なラベルをオンデバイスの手順に使用できます。

次の例は、android と現在のデバイス SPN の両方でフィルタリングして、デバイス上の集計された歩数データを読み取る方法を示しています。

import android.content.Context
import android.health.connect.HealthConnectManager
import android.os.Build
import android.os.ext.SdkExtensions
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.records.StepsRecord
import androidx.health.connect.client.records.metadata.DataOrigin
import androidx.health.connect.client.request.AggregateRequest
import androidx.health.connect.client.time.TimeRangeFilter
import java.time.Instant

suspend fun readDeviceStepsByTimeRange(
    healthConnectClient: HealthConnectClient,
    context: Context,
    startTime: Instant,
    endTime: Instant
) {
    // 1. Check if SDK Extension 11+ is available for getCurrentDeviceDataSource()
    val isDataSourceApiAvailable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.U &&
            SdkExtensions.getExtensionVersion(Build.VERSION_CODES.U) >= 11

    try {
        val healthConnectManager = context.getSystemService(HealthConnectManager::class.java)

        // 2. Safely fetch the package name only if API is available and data exists
        val currentDeviceSpn = if (isDataSourceApiAvailable) {
            healthConnectManager?.getCurrentDeviceDataSource()?.deviceDataOrigin?.packageName
        } else {
            null
        }

        val dataOriginFilters = mutableSetOf(DataOrigin("android"))

        // 3. Explicit null-safety check using .let
        currentDeviceSpn?.let {
            dataOriginFilters.add(DataOrigin(it))
        }

        val response = healthConnectClient.aggregate(
            AggregateRequest(
                metrics = setOf(StepsRecord.COUNT_TOTAL),
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
                dataOriginFilter = dataOriginFilters
            )
        )

        val stepCount = response[StepsRecord.COUNT_TOTAL]

    } catch (e: Exception) {
        // Now this catch block only handles actual runtime exceptions, 
        // rather than Errors from missing methods.
    }
}

オンデバイスの歩数カウント

  • センサーの使用状況: ヘルスコネクトは SensorManagerTYPE_STEP_COUNTER センサーを利用します。このセンサーは低消費電力に最適化されているため、バックグラウンドでの継続的な歩数トラッキングに最適です。
  • データの粒度: バッテリーの駆動時間を長くするため、歩数データは通常、バッチ処理され、ヘルスコネクト データベースに 1 分に 1 回以下の頻度で書き込まれます。
  • アトリビューション: この機能で 2026 年 6 月より前に記録されたステップは、DataOriginandroid パッケージ名に帰属します。この日付以降は、デバイス固有の SPN に関連付けられます。オンデバイスの手順のアトリビューションの変更を参照してください。
  • 有効化: デバイス上の歩数カウント メカニズムは、デバイス上の少なくとも 1 つのアプリにヘルスコネクト内で READ_STEPS 権限が付与されている場合にのみ有効になります。

ヘルスコネクトを利用できるか確認する

ヘルスコネクト アプリを使用する前に、ユーザーのデバイスでヘルスコネクトが利用可能であることを確認する必要があります。ヘルスコネクトは一部のデバイスにおいて、プリインストールされていない場合や、無効になっている場合があります。HealthConnectClient.getSdkStatus() メソッドを使用して、利用可能かどうかを確認できます。

ヘルスコネクトを利用できるか確認する方法

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata" // Or get from HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME
    val availabilityStatus = HealthConnectClient.getSdkStatus(context, providerPackageName)

    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE) {
      // Health Connect is not available. Guide the user to install/enable it.
      // For example, show a dialog.
      return // early return as there is no viable integration
    }
    if (availabilityStatus == HealthConnectClient.SDK_UNAVAILABLE_PROVIDER_UPDATE_REQUIRED) {
      // Health Connect is available but requires an update.
      // Optionally redirect to package installer to find a provider, for example:
      val uriString = "market://details?id=$providerPackageName&url=healthconnect%3A%2F%2Fonboarding"
      context.startActivity(
        Intent(Intent.ACTION_VIEW).apply {
          setPackage("com.android.vending")
          data = Uri.parse(uriString)
          putExtra("overlay", true)
          putExtra("callerId", context.packageName)
        }
      )
      return
    }
    // Health Connect is available, obtain a HealthConnectClient instance
    val healthConnectClient = HealthConnectClient.getOrCreate(context)
    // Issue operations with healthConnectClient
}

getSdkStatus() から返されたステータスに応じて、必要に応じて Google Play ストアからヘルスコネクトをインストールまたは更新するようユーザーに案内できます。

必要な権限

歩数へのアクセスは、次の権限によって保護されています。

  • android.permission.health.READ_STEPS
  • android.permission.health.WRITE_STEPS

歩数機能をアプリに追加するには、まず、Steps データ型の権限をリクエストします。

歩数を書き込むために宣言する必要がある権限は次のとおりです。

<application>
  <uses-permission
android:name="android.permission.health.WRITE_STEPS" />
...
</application>

歩数を読み取るには、次の権限をリクエストする必要があります。

<application>
  <uses-permission
android:name="android.permission.health.READ_STEPS" />
...
</application>

ユーザーに権限をリクエストする

クライアント インスタンスを作成した後、アプリはユーザーに権限をリクエストする必要があります。ユーザーがいつでも権限を付与または拒否できるようにする必要があります。

そのためには、必要なデータ型の権限セットを作成します。まず、セット内の権限が Android マニフェストで宣言されていることを確認します。

// Create a set of permissions for required data types
val PERMISSIONS =
    setOf(
  HealthPermission.getReadPermission(StepsRecord::class),
  HealthPermission.getWritePermission(StepsRecord::class)
)

getGrantedPermissions を使用して、アプリが必要な権限をすでに持っているかどうかを確認します。持っていない場合は、createRequestPermissionResultContract を使用して権限をリクエストします。ヘルスコネクトの権限画面が表示されます。

// Create the permissions launcher
val requestPermissionActivityContract = PermissionController.createRequestPermissionResultContract()

val requestPermissions = registerForActivityResult(requestPermissionActivityContract) { granted ->
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions successfully granted
  } else {
    // Lack of required permissions
  }
}

suspend fun checkPermissionsAndRun(healthConnectClient: HealthConnectClient) {
  val granted = healthConnectClient.permissionController.getGrantedPermissions()
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions already granted; proceed with inserting or reading data
  } else {
    requestPermissions.launch(PERMISSIONS)
  }
}

ユーザーはいつでも権限を付与または取り消すことができるため、アプリは権限を使用するたびに権限をチェックし、権限が失われた状況に対応できるように設計する必要があります。

歩数レコードに含まれる情報

StepsRecord には次の情報が含まれています。

  • count: 時間間隔内の歩数(Long)。
  • startTime: 測定期間の開始時刻。
  • endTime: 測定間隔の終了時刻。
  • startZoneOffset: 開始時間のゾーン オフセット。
  • endZoneOffset: 終了時間のゾーン オフセット。

サポートされている集計

StepsRecord で使用できる集計値は次のとおりです。

StepsCadenceRecord で使用できる集計値は次のとおりです。

使用例

以降のセクションでは、StepsRecord データの読み取りと書き込みの方法について説明します。

歩数のデータを書き込む

アプリは、StepsRecord インスタンスを挿入することで、歩数データを書き込むことができます。次の例は、ユーザーが歩いた 1,000 歩を記録する方法を示しています。

val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(startTime)
val stepsRecord = StepsRecord(
    count = 120,
    startTime = startTime,
    endTime = endTime,
    startZoneOffset = zoneOffset,
    endZoneOffset = zoneOffset,
    metadata = Metadata(
        device = Device(type = Device.TYPE_WATCH),
        recordingMethod = Metadata.RECORDING_METHOD_AUTOMATICALLY_RECORDED
    )
)
healthConnectClient.insertRecords(listOf(stepsRecord))

集計データを読み取る

歩数データを読み取る最も一般的な方法は、一定期間の総歩数を集計することです。次の例は、特定の期間内のユーザーの合計歩数を読み取る方法を示しています。

suspend fun readStepsAggregate(startTime: Instant, endTime: Instant): Long {
    val response = healthConnectClient.aggregate(
        AggregateRequest(
            metrics = setOf(StepsRecord.COUNT_TOTAL),
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
        )
    )
    return response[StepsRecord.COUNT_TOTAL] ?: 0L
}

元データを読み取る

次の例は、開始時刻と終了時刻の間の未加工の StepsRecord データを読み取る方法を示しています。

val response = healthConnectClient.readRecords(
    ReadRecordsRequest(
        StepsRecord::class,
        timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
    )
)
response.records.forEach { record ->
    /* Process records */
}