在后台监控数据

被动数据更新适用于需要在后台监控健康服务数据的应用程序。它们旨在用于跨越数小时、数天甚至更长时间的用例。如果您需要在应用程序未运行且用户未明确参与运动时存储或处理健康数据,请使用健康服务的被动客户端。

有关被动数据用法的示例,请参阅 GitHub 上的 被动数据被动目标 示例。

添加依赖项

要添加对健康服务的依赖项,您必须将 Google Maven 存储库添加到您的项目中。有关更多信息,请参阅 Google 的 Maven 存储库

在您的模块级 build.gradle 文件中,添加以下依赖项

Groovy

dependencies {
    implementation "androidx.health:health-services-client:1.1.0-alpha03"
}

Kotlin

dependencies {
    implementation("androidx.health:health-services-client:1.1.0-alpha03")
}

检查功能

在注册数据更新之前,请检查设备是否可以提供您的应用程序所需的类型的数据。检查功能可以让您启用或禁用某些功能,或修改应用程序的 UI 以弥补不可用的功能。

val healthClient = HealthServices.getClient(this /*context*/)
val passiveMonitoringClient = healthClient.passiveMonitoringClient
lifecycleScope.launchWhenCreated {
    val capabilities = passiveMonitoringClient.capabilities.await()
    // Supported types for passive data collection
    supportsHeartRate =
        DataType.HEART_RATE_BPM in capabilities.supportedDataTypesPassiveMonitoring
    // Supported types for PassiveGoals
    supportsStepsGoal =
        DataType.STEPS_DAILY in capabilities.supportedDataTypesPassiveGoals
}

注册被动数据

您可以通过服务、回调或两者来接收被动数据。服务可以让您的应用程序在应用程序的任何部分未在前台可见时接收后台数据。当您在后台接收数据时,数据将以批次的形式交付。回调以稍微更快的速度接收数据,但仅在应用程序正在运行且回调成功通知时才接收数据。

无论您使用哪种方法,首先创建一个 PassiveListenerConfig 来确定要接收哪些数据类型,如下面的示例所示

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDataTypes(setOf(DataType.HEART_RATE_BPM))
    .build()

要使用回调接收数据,请定义并注册回调,如下面的示例所示

val passiveListenerCallback: PassiveListenerCallback = object : PassiveListenerCallback {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerCallback(
    passiveListenerConfig,
    passiveListenerCallback
)

// To remove the listener
passiveMonitoringClient.clearPassiveListenerCallbackAsync()

使用服务类似,但不是创建派生自 PassiveListenerCallback 的类,而是派生自 PassiveListenerService,如下面的示例所示

class PassiveDataService : PassiveListenerService() {
    override fun onNewDataPointsReceived(dataPoints: DataPointContainer) {
        // TODO: Do something with dataPoints
    }
}

passiveMonitoringClient.setPassiveListenerServiceAsync(
    PassiveDataService::class.java,
    passiveListenerConfig
)

接下来,在您的 AndroidManifest.xml 文件中声明服务。需要一个健康服务权限,这将确保只有健康服务能够绑定到该服务

<service android:name=".PassiveDataService"
    android:permission="com.google.android.wearable.healthservices.permission.PASSIVE_DATA_BINDING"
    android:exported="true" />

解释时间

您从健康服务接收的数据是批处理的,因此您可能会在同一个批次中接收不同类型的数据点,或同一类型的多个数据点。使用这些对象中包含的时间戳,而不是它们被您的应用程序接收的时间,来确定事件的正确顺序。

通过首先计算引导时间戳来获取每个 DataPoint 的时间戳,如下面的示例所示

val bootInstant =
    Instant.ofEpochMilli(System.currentTimeMillis() - SystemClock.elapsedRealtime())

然后可以将此值传递给 getStartInstant()getEndInstant()

在引导后恢复注册

被动数据注册不会在重启后持久化。要在设备重启后接收数据,请使用一个监听 ACTION_BOOT_COMPLETED 系统广播的 BroadcastReceiver 重新创建注册。

在接收器中,不要尝试直接恢复注册。相反,将此功能委托给一个 WorkManager 工作器。当设备启动时,健康服务可能需要 10 秒或更长时间才能确认被动数据注册请求,这可能会超过 BroadcastReceiver 允许的执行时间。相反,WorkManager 工作器有 10 分钟的执行限制

以下代码片段展示了一个 BroadcastReceiver 可能的样子

class StartupReceiver : BroadcastReceiver() {

   override fun onReceive(context: Context, intent: Intent) {
       if (intent.action != Intent.ACTION_BOOT_COMPLETED) return


       // TODO: Check permissions first
       WorkManager.getInstance(context).enqueue(
           OneTimeWorkRequestBuilder<RegisterForPassiveDataWorker>().build()
       )
   }
}

class RegisterForPassiveDataWorker(
   private val appContext: Context,
   workerParams: WorkerParameters
) : Worker(appContext, workerParams) {

   override fun doWork(): Result {
       runBlocking {
           HealthServices.getClient(appContext)
                .passiveMonitoringClient
                .setPassiveListenerCallback(...)
       }
       return Result.success()
   }
}

为了安排系统在设备启动时执行此代码,请对 AndroidManifest.xml 文件进行两个更改。

首先,将以下权限作为 <manifest> 的子级添加

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

其次,将以下接收器意图过滤器作为 <application> 的子级添加

<receiver
    android:name=".StartupReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

活动状态

被动客户端还可以提供有关用户状态的高级信息,例如用户是否处于睡眠状态。要接收这些更新,请按照以下步骤操作

  1. 请求 ACTIVITY_RECOGNITION 权限。
  2. PassiveListenerConfig 构建器中调用 setShouldUserActivityInfoBeRequested(true)

覆盖回调或服务中的 onUserActivityInfoReceived() 方法,并使用返回的 UserActivityInfo,如下面的示例所示

override fun onUserActivityInfoReceived(info: UserActivityInfo) {
    val stateChangeTime: Instant = info.stateChangeTime // may be in the past!
    val userActivityState: UserActivityState = info.userActivityState
    if (userActivityState == UserActivityState.USER_ACTIVITY_ASLEEP) {
        // ...
    }
}

被动目标

您可以配置被动客户端,以便在达到被动目标时(例如,用户在一天内完成 10,000 步)通知应用。

为此,请创建一个目标,如下面的示例所示

val dailyStepsGoal by lazy {
    val condition = DataTypeCondition(
        dataType = DataType.STEPS_DAILY,
        threshold = 10_000, // Trigger every 10000 steps
        comparisonType = ComparisonType.GREATER_THAN_OR_EQUAL
    )
    PassiveGoal(condition)
}

将此目标添加到您的 PassiveListenerConfig 中,如下面的示例所示

val passiveListenerConfig = PassiveListenerConfig.builder()
    .setDailyGoals(setOf(dailyStepsGoal))
    .build()

覆盖回调或服务中的 onGoalCompleted() 方法,并使用返回的 PassiveGoal,如下面的示例所示

override fun onGoalCompleted(goal: PassiveGoal) {
    when (goal.dataTypeCondition.dataType) {
        DataType.STEPS_DAILY -> {
            // ...
        }
    }
}