讀取原始資料

以下範例說明如何在常見工作流程中讀取原始資料。

讀取資料

應用程式在前台和背景運作時,都可以透過 健康資料同步 從資料儲存庫讀取資料:

  • 前景讀取:應用程式在前景時,通常可以從健康資料同步讀取資料。在這些情況下,如果使用者或系統在讀取作業期間將應用程式置於背景,您不妨考慮使用前景服務執行這項作業。

  • 背景讀取:向使用者要求額外權限後,您可以在使用者或系統將應用程式置於背景時讀取資料。請參閱完整的背景讀取範例

健康資料同步中的「步數」資料類型會擷取多次讀取的使用者步數。步數代表各健康與健身平台上的常見測量值。「健康資料同步」可讓您讀取及寫入步數資料。

如要讀取記錄,請建立 ReadRecordsRequest,並在呼叫 readRecords 時提供這個項目。

以下範例說明如何讀取使用者在特定時間範圍內的步數資料。如需 SensorManager 的擴充範例,請參閱步數資料指南。

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

您也可以使用 aggregate 以匯總方式讀取資料。

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
}

讀取行動裝置步數

在 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 適用於搭載 Android 14 (API 級別 34) 的裝置,且 SDK 擴充功能版本為 11 以上。

getCurrentDeviceDataSource() API 尚未在 Health Connect 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 在匯總資料中識別的裝置端步驟進行歸因,可以使用裝置中繼資料,例如 modelmanufacturer (來自 DeviceDataSource),也可以使用「您的手機」等一般標籤來標示裝置端步驟。

以下範例說明如何透過篩選 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 感應器。這款感應器可盡可能耗用最少的電量,因此非常適合持續追蹤背景步數。
  • 資料精細度:為節省電量,步數資料通常會批次處理,並寫入「健康資料同步」資料庫,頻率不會超過每分鐘一次。
  • 歸因:這項功能在 2026 年 6 月前記錄的步數會歸因於 DataOrigin 中的 android 套件名稱。在此日期之後,系統會將這些轉換歸因於裝置專屬的 SPN。請參閱「裝置端步驟的歸因變更」。
  • 啟用:只有在裝置上至少有一個應用程式已在「健康資料同步」中獲得 READ_STEPS 權限時,裝置端的步數計算機制才會啟動。

背景讀取範例

如要在背景讀取資料,請在資訊清單檔案中宣告下列權限:

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

以下範例說明如何使用 WorkManager,在背景讀取使用者在特定時間範圍內的步數資料:

class ScheduleWorker(appContext: Context, workerParams: WorkerParameters) :
    CoroutineWorker(appContext, workerParams) {

    override suspend fun doWork(): Result {
        val healthConnectClient = HealthConnectClient.getOrCreate(applicationContext)
        // Perform background read logic here
        return Result.success()
    }
}
@OptIn(ExperimentalFeatureAvailabilityApi::class)
fun enqueueBackgroundReadWorker(context: Context, healthConnectClient: HealthConnectClient) {
    if (healthConnectClient
            .features
            .getFeatureStatus(
                HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_IN_BACKGROUND
            ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE
    ) {

        val periodicWorkRequest = PeriodicWorkRequestBuilder<ScheduleWorker>(1, TimeUnit.HOURS)
            .build()

        WorkManager.getInstance(context).enqueueUniquePeriodicWork(
            "read_health_connect",
            ExistingPeriodicWorkPolicy.KEEP,
            periodicWorkRequest
        )
    }
}

ReadRecordsRequest 參數的預設 pageSize 值為 1000。 如果單一 readResponse 中的記錄數超過要求中的 pageSize,您需要使用 pageToken 疊代回應的所有頁面,才能擷取所有記錄。不過,請小心避免速率限制問題。

pageToken 讀取範例

建議使用 pageToken 讀取記錄,從要求時間範圍內擷取所有可用資料。

以下範例說明如何讀取所有記錄,直到所有分頁權杖都用盡為止:

val type = HeartRateRecord::class
val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofDays(7))

try {
    var pageToken: String? = null
    do {
        val readResponse =
            healthConnectClient.readRecords(
                ReadRecordsRequest(
                    recordType = type,
                    timeRangeFilter = TimeRangeFilter.between(
                        startTime,
                        endTime
                    ),
                    pageToken = pageToken
                )
            )
        val records = readResponse.records
        // Do something with records
        pageToken = readResponse.pageToken
    } while (pageToken != null)
} catch (quotaError: IllegalStateException) {
    // Backoff
}
如要瞭解讀取大型資料集的最佳做法,請參閱「規劃避免頻率限制」。

讀取先前寫入的資料

如果應用程式之前曾將記錄寫入「健康資料同步」,則應用程式可以讀取歷來資料。這適用於使用者重新安裝應用程式後,應用程式需要與「健康資料同步」重新同步的情況。

讀取作業須遵守下列限制:

  • Android 14 以上版本

    • 應用程式讀取自身資料時,沒有歷來限制。
    • 應用程式讀取其他資料的 30 天限制。
  • Android 13 以下版本

    • 應用程式讀取任何資料的 30 天限制。

如要移除限制,請要求讀取權限

如要讀取歷來資料,您需要在 ReadRecordsRequestdataOriginFilter 參數中,將套件名稱指定為 DataOrigin 物件。

以下範例說明如何在讀取心率記錄時指定套件名稱:

try {
    val response =  healthConnectClient.readRecords(
        ReadRecordsRequest(
            recordType = HeartRateRecord::class,
            timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
            dataOriginFilter = setOf(DataOrigin("com.my.package.name"))
        )
    )
    for (record in response.records) {
        // Process each record
    }
} catch (e: Exception) {
    // Run error handling here
}

讀取 30 天前的資料

根據預設,所有應用程式從「健康資料同步」讀取資料時,日期最遠可以溯及首次授予權限前的 30 天。

如需超出預設限制的讀取權限,請提出PERMISSION_READ_HEALTH_DATA_HISTORY要求。否則,如果嘗試讀取 30 天前的記錄,就會發生錯誤。

已刪除應用程式的權限記錄

如果使用者刪除您的應用程式,系統會撤銷所有權限,包括記錄權限。當使用者重新安裝應用程式並再次授予權限後,系統會套用相同的預設限制,應用程式最多便可讀取從這個新日期回推 30 天的健康資料同步資料。

舉例來說,假設使用者在 2023 年 5 月 10 日刪除您的應用程式,然後在 2023 年 5 月 15 日重新安裝應用程式並授予讀取權限。此時,應用程式預設可讀取最遠溯及 2023 年 4 月 15 日的資料。

處理例外狀況

健康資料同步會在發生問題時擲回 CRUD 作業的標準例外狀況。您的應用程式應視情況擷取並處理這些例外狀況。

HealthConnectClient 的每個方法都列出了可以擲回的例外情況。一般來說,應用程式應處理下列例外狀況:

表 1:健康資料同步例外狀況和建議的最佳做法
例外狀況 說明 建議最佳做法
IllegalStateException 發生下列任一情況:

  • 無法使用「健康資料同步」服務。
  • 要求為無效結構。例如定期值區中的匯總要求,Instant 物件在值區中用於 timeRangeFilter

先處理輸入內容的潛在問題,然後再提出要求。建議您為變數指派值,或將其用做自訂函式中的參數,而不要直接在要求中使用,以便套用錯誤處理策略。
IOException 從磁碟讀取及寫入資料時發生問題。為避免這個問題,請參考以下幾點建議:

  • 將所有使用者輸入內容備份。
  • 確認自己有辦法處理大量寫入作業期間發生的任何問題。例如,請確認程序會避開問題,並執行剩餘的作業。
  • 套用重試和輪詢策略來處理要求問題。

RemoteException 在 SDK 連線的基礎服務中發生錯誤,或與該服務進行通訊時發生錯誤。

舉例來說,您的應用程式會嘗試刪除具有指定 uid 的記錄。然而,應用程式在基礎服務中檢查之後,若發現該記錄不存在,就會擲回例外狀況。
為避免這個問題,請參考以下幾點建議:

  • 在應用程式的資料儲存庫和「健康資料同步」間定期執行同步作業。
  • 套用重試和輪詢策略來處理要求問題。

SecurityException 所提要求需要用到未授予的權限時發生問題。為避免這種情況,請確認您已為發布的應用程式宣告使用「健康資料同步」資料類型。此外,您必須在資訊清單檔案Activity中宣告「健康資料同步」權限。