讀取原始資料

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

讀取資料

應用程式在前台和背景運作時,都可以透過 Health Connect 讀取資料儲存庫中的資料:

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

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

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

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

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

suspend fun readStepsByTimeRange(
    healthConnectClient: HealthConnectClient,
    startTime: Instant,
    endTime: Instant
) {
    try {
        val response = healthConnectClient.readRecords(
            ReadRecordsRequest(
                StepsRecord::class,
                timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
            )
        )
        for (record in response.records) {
            // Process each record
        }
    } catch (e: Exception) {
        // Run error handling here
    }
}

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

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)
            )
        )
        // 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
    }
}

讀取行動裝置步數

在 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 篩選,系統會自動將裝置上的步數計入總數。

如果應用程式需要讀取裝置上的步數,或顯示依來源應用程式或裝置細分的步數資料,您可以查詢 DataOriginandroid 的記錄。如果應用程式會顯示步數資料的歸因,您應將 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
    }
}

裝置端步數計算

深入瞭解裝置端步數計算功能:

  • 感應器使用情形:健康資料同步會使用 SensorManagerTYPE_STEP_COUNTER 感應器。這款感應器可盡可能降低耗電量,因此非常適合用於持續追蹤背景步數。
  • 資料精細度:為延長電池續航力,步數資料通常會批次處理,並寫入「健康資料同步」資料庫,頻率不會超過每分鐘一次。
  • 歸因:如先前所述,這項裝置端功能記錄的所有步驟,都會歸因於 DataOrigin 中的 android 套件名稱。
  • 啟用:只有在裝置上至少有一個應用程式已在「健康資料同步」中獲得 READ_STEPS 權限時,裝置上的步數計算機制才會啟用。

背景讀取範例

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

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

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

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

    override suspend fun doWork(): Result {
        // Read data and process it.
        ...

        // Return success indicating successful data retrieval
        return Result.success()
    }
}

if (healthConnectClient
    .features
    .getFeatureStatus(
    HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_IN_BACKGROUND
    ) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE) {

    // Check if necessary permission is granted
    val grantedPermissions = healthConnectClient.permissionController.getGrantedPermissions()

    if (PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND !in grantedPermissions) {
        // Perform read in foreground
        ...
    } else {
        // Schedule the periodic work request in background
        val periodicWorkRequest = PeriodicWorkRequestBuilder<ScheduleWorker>(1, TimeUnit.HOURS)
            .build()

        WorkManager.getInstance(context).enqueueUniquePeriodicWork(
            "read_health_connect",
            ExistingPeriodicWorkPolicy.KEEP,
            periodicWorkRequest
        )
    }
} else {
  // Background reading is not available, perform read in foreground
  ...
}

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 日的資料。

處理例外狀況

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

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

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

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

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

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

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

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

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

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