걸음 수 추적

헬스 커넥트는 걸음 수를 기록하는 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으로 식별되는 기기 내 걸음 수의 경우 귀속에 DeviceDataSourcemodel 또는 manufacturer와 같은 기기 메타데이터를 사용하거나 기기 내 걸음 수에 '내 휴대전화'와 같은 일반적인 라벨을 사용할 수 있습니다.

다음 예는 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.
    }
}

기기 내 걸음 수 계산

  • 센서 사용량: 헬스 커넥트는 TYPE_STEP_COUNTER 센서를 SensorManager에서 활용합니다. 이 센서는 전력 소비를 최소화하도록 최적화되어 있어 지속적인 백그라운드 걸음 수 추적에 적합합니다.
  • 데이터 세분성: 배터리 수명을 보존하기 위해 걸음 수 데이터는 일반적으로 일괄 처리되어 헬스 커넥트 데이터베이스에 분당 한 번 이하로 기록됩니다.
  • 귀속: 2026년 6월 이전에 이 기능으로 기록된 걸음 수는 android 패키지 이름에 귀속됩니다. DataOrigin. 이 날짜 이후에는 기기별 SPN에 귀속됩니다. 기기 내 걸음 수의 귀속 변경사항을 참고하세요.
  • 활성화: 기기 내 걸음 수 계산 메커니즘은 기기의 애플리케이션 중 하나 이상에 헬스 커넥트 내에서 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 */
}