追蹤步數

健康資料同步 提供步數資料類型,可使用 StepsRecord 記錄步數。步數是健康與健身追蹤的基本測量指標。

本指南適用於 Health Connect 1.1.0-alpha12 以上版本。

讀取行動裝置步數

在 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 月更新起,Health Connect 原生追蹤的步數會歸因於合成套件名稱 (SPN),例如 com.android.healthconnect.phone.jd5bdd37e1a8d3667a05d0abebfc4a89e。

先前,內建步數會歸因於套件名稱 android。2026 年 6 月前記錄的歷來步數資料會保留 android 套件名稱。

SPN 僅限於特定裝置,且範圍以個別應用程式為準,以保護使用者隱私:

  • 穩定:目前裝置的 SPN 對應用程式來說很穩定。
  • 應用程式範圍:同一裝置上的不同應用程式,會看到不同的裝置端步數資料 SPN。

查詢裝置端步數

由於 SPN 具有範圍且因裝置而異,因此不得對 SPN 值進行硬式編碼。請改用 getCurrentDeviceDataSource API 擷取目前裝置的 SPN。

雖然裝置端計步功能需要 SDK 擴充功能 20 以上版本,但 getCurrentDeviceDataSource() API 適用於 Android 14 (API 級別 34),且 SDK 擴充功能版本為 22 以上。如要使用這個 API,請在模組層級 build.gradle.kts 或 build.gradle 檔案中,將 compileSdkExtension 設為 22 以上:

Kotlin

android {
    compileSdk = 35
    compileSdkExtension = 22
}

Groovy

android {
    compileSdk 35
    compileSdkExtension 22
}

Health Connect Jetpack 程式庫尚未提供 getCurrentDeviceDataSource() API。以下範例使用 Android 架構 API,需要 Executor 和 OutcomeReceiver 回呼:

import android.content.Context
import android.health.connect.DeviceDataSource
import android.health.connect.HealthConnectException
import android.health.connect.HealthConnectManager
import android.os.OutcomeReceiver

val healthConnectManager = context.getSystemService(HealthConnectManager::class.java)
healthConnectManager?.getCurrentDeviceDataSource(
    context.mainExecutor,
    object : OutcomeReceiver<DeviceDataSource, HealthConnectException> {
        override fun onResult(result: DeviceDataSource) {
            val currentDeviceSpn = result.deviceDataOrigin.packageName
        }

        override fun onError(error: HealthConnectException) {
            // Handle error
        }
    }
)

如果應用程式需要讀取裝置端的步數,或顯示依來源應用程式或裝置細分的步數資料,您必須查詢 DataOrigin 為 android「或」符合裝置 SPN 的記錄。如果應用程式會顯示步數資料的歸因,請使用 metadata.device 識別個別記錄的來源裝置。對於匯總資料中由 SPN 識別的裝置端步驟,您可以使用裝置中繼資料 (例如 DeviceDataSource 的 model 或 manufacturer) 進行歸因,或使用「您的手機」等一般標籤來表示裝置端步驟。

以下範例說明如何篩選 android 和目前裝置 SPN,讀取匯總的裝置端步數資料:

import android.content.Context
import android.health.connect.DeviceDataSource
import android.health.connect.HealthConnectException
import android.health.connect.HealthConnectManager
import android.os.Build
import android.os.OutcomeReceiver
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
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine

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

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

        // 2. Safely fetch the package name only if the API is available
        val currentDeviceSpn = if (isDataSourceApiAvailable && healthConnectManager != null) {
            suspendCancellableCoroutine { continuation ->
                healthConnectManager.getCurrentDeviceDataSource(
                    context.mainExecutor,
                    object : OutcomeReceiver<DeviceDataSource, HealthConnectException> {
                        override fun onResult(result: DeviceDataSource) {
                            continuation.resume(result.deviceDataOrigin.packageName)
                        }

                        override fun onError(error: HealthConnectException) {
                            continuation.resumeWithException(error)
                        }
                    }
                )
            }
        } 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.
    }
}

裝置端步數計算

  • 感應器使用情形:健康資料同步會使用 SensorManager 的 TYPE_STEP_COUNTER 感應器。這個感應器經過最佳化,耗電量低,非常適合在背景持續追蹤步數。
  • 資料精細程度:為節省電池電力,步數資料通常會批次處理,並寫入健康資料同步資料庫,頻率不超過每分鐘一次。
  • 歸因:2026 年 6 月前,這項功能記錄的步數會歸因於 DataOrigin 中的 android 套件名稱。之後,這些資料會歸因於裝置專屬的 SPN。請參閱「裝置端步數的歸因變更」。
  • 啟用:只有在裝置上至少有一個應用程式,已在健康資料同步中獲得 READ_STEPS 權限時,裝置端計步機制才會啟用。

查看健康資料同步的適用情形

嘗試使用健康資料同步前,應用程式應先確認使用者裝置上是否支援健康資料同步。部分裝置可能未預先安裝健康資料同步,或可能已停用。您可以使用 HealthConnectClient.getSdkStatus() 方法檢查是否可用。

如何查看健康資料同步的適用情形

fun checkHealthConnectAvailability(context: Context) {
    val providerPackageName = "com.google.android.apps.healthdata"
    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 資訊清單中聲明該組權限。

val permissions =
    setOf(
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getWritePermission(StepsRecord::class)
    )
使用 getGrantedPermissions 查看應用程式是否已取得所需權限。如果沒有,請使用 createRequestPermissionResultContract 要求這些權限。系統接著會顯示 Health Connect 權限畫面。
val permissions = setOf(
        HealthPermission.getReadPermission(StepsRecord::class),
        HealthPermission.getWritePermission(StepsRecord::class),
        HealthPermission.getReadPermission(HeartRateRecord::class),
        HealthPermission.getWritePermission(HeartRateRecord::class)
    )

val requestPermissionsLauncher = rememberLauncherForActivityResult(
    contract = PermissionController.createRequestPermissionResultContract()
) { grantedPermissions ->
    if (grantedPermissions.containsAll(permissions)) {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions granted!") }
    } else {
        coroutineScope.launch { snackbarHostState.showSnackbar("Permissions denied.") }
    }
}
使用者隨時可以授予或撤銷權限,因此應用程式每次使用權限前都必須檢查權限,並處理權限遺失的情況。

步數記錄包含的資訊

每個 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.autoRecorded(
        device = Device(type = Device.TYPE_WATCH)
    )
)
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 */
}