「健康資料同步」提供「步數」資料類型,可使用 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
「健康資料同步」擷取的行動步數會將 DataOrigin 設為套件名稱 android。如果應用程式只是使用 aggregate 讀取步數總計,且未依 DataOrigin 篩選,系統會自動將裝置上的步數計入總數。
如果應用程式需要讀取裝置上的步數,或顯示依來源應用程式或裝置細分的步數資料,您可以查詢 DataOrigin 為 android 的記錄。如果應用程式會顯示步數資料的歸因,您應將 Android 套件的資料歸因至目前裝置。方法包括使用「你的手機」等標籤、透過 Settings.Global.getString(resolver, Settings.Global.DEVICE_NAME) 擷取裝置名稱,或檢查記錄中繼資料的 Device 欄位。
以下範例說明如何篩選 android 資料來源,讀取匯總的行動裝置步數資料:
suspend fun readStepsByTimeRange(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
try {
val response = healthConnectClient.aggregate(
AggregateRequest(
metrics = setOf(StepsRecord.COUNT_TOTAL),
timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
dataOriginFilter = setOf(DataOrigin("android"))
)
)
// The result may be null if no data is available in the time range
val stepCount = response[StepsRecord.COUNT_TOTAL]
} catch (e: Exception) {
// Run error handling here
}
}
裝置端步數計算
深入瞭解裝置端步數計算功能:
- 感應器使用情形:健康資料同步會使用
SensorManager的TYPE_STEP_COUNTER感應器。這款感應器可盡可能降低耗電量,因此非常適合用於持續追蹤背景步數。 - 資料精細度:為節省電量,步數資料通常會批次處理,並寫入「健康資料同步」資料庫,頻率不會超過每分鐘一次。
- 歸因:如先前所述,這項裝置端功能記錄的所有步驟,都會歸因於
DataOrigin中的android套件名稱。 - 啟用:只有在裝置上至少有一個應用程式已在「健康資料同步」中獲得
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_STEPSandroid.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 要求這些權限。系統接著會顯示 Health Connect 權限畫面。
// 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 執行個體,寫入步數資料。以下範例說明如何記錄使用者走了 1000 步:
suspend fun writeStepsData(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant,
startZoneOffset: ZoneOffset,
endZoneOffset: ZoneOffset
) {
try {
val stepsRecord = StepsRecord(
startTime = startTime,
startZoneOffset = startZoneOffset,
endTime = endTime,
endZoneOffset = endZoneOffset,
count = 1000
)
healthConnectClient.insertRecords(listOf(stepsRecord))
} catch (e: Exception) {
// Run error handling
}
}
讀取匯總資料
讀取步數資料最常見的方式,是匯總一段時間內的總步數。以下範例說明如何讀取使用者在特定時間範圍內的總步數:
suspend fun readStepsAggregate(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
try {
val response = healthConnectClient.aggregate(
AggregateRequest(
metrics = setOf(StepsRecord.COUNT_TOTAL),
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
// The result may be null if no data is available in the time range
val stepCount = response[StepsRecord.COUNT_TOTAL]
} catch (e: Exception) {
// Run error handling here
}
}
讀取原始資料
以下範例說明如何讀取開始時間和結束時間之間的原始 StepsRecord 資料:
suspend fun readStepsRaw(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
try {
val response = healthConnectClient.readRecords(
ReadRecordsRequest(
recordType = StepsRecord::class,
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
for (record in response.records) {
// Process each record
}
} catch (e: Exception) {
// Run error handling here
}
}