被动数据更新

被动数据更新适用于需要在后台监控健康服务数据的应用。这针对的是长期体验,即数据更新频次不高而且时间跨度较大。如果在系统发送数据更新时,您的应用不处于使用或者运行状态,您就可以使用被动数据更新。借助这些 API,您可以选择接收所有数据点,也可以选择仅接收与特定 DataType 相关的数据点。

请参阅 GitHub 上的被动数据被动目标示例。

添加依赖项

如需添加健康服务的依赖项,您必须将 Google Maven 代码库添加到项目中。如需了解相关信息,请参阅 Google 的 Maven 代码库

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

Groovy

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

Kotlin

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

AndroidManifest.xml 文件中的 manifest 标记内添加以下内容,以便您的应用可以与健康服务交互。如需了解详情,请参阅软件包可见性

<queries>
    <package android:name="com.google.android.wearable.healthservices" />
</queries>

检查功能

在注册数据更新之前,请检查设备是否可以提供您的应用所需的后台数据类型。提前检查功能,您便可以启用或停用某些功能,或修改应用的界面以弥补不可用的功能。

Kotlin

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 in capabilities.supportedDataTypesEvents
}

Java

HealthServicesClient healthClient = HealthServices.getClient(this /*context*/);
PassiveMonitoringClient passiveClient = healthClient.getPassiveMonitoringClient();

ListenableFuture<PassiveMonitoringCapabilities> capabilitiesFuture = passiveClient.getCapabilities();
Futures.addCallback(capabilitiesFuture,
        new FutureCallback<PassiveMonitoringCapabilities>() {
            @Override
            public void onSuccess(@Nullable PassiveMonitoringCapabilities result) {
                supportsHeartRate = result
                        .getSupportedDataTypesPassiveMonitoring()
                        .contains(DataType.HEART_RATE_BPM)
                supportsStepsEvent = result
                        .supportedDataTypesEvents()
                        .contains(DataType.STEPS)
            }

            @Override
            public void onFailure(Throwable t) {
                // display an error
            }
        },
        ContextCompat.getMainExecutor(this /*context*/));

接收被动数据

如需在后台接收数据更新,请使用 PassiveMonitoringClient。 应用的 AndroidManifest.xml 中必须声明 BroadcastReceiver。 当您注册接收来自健康服务的更新后,这些更新将传送至此接收器。

健康服务会批量更新,因此您可能会收到不同类型的数据点或同一类型的多个数据点。此外,数据点和用户活动状态可能会反映过去发生的事件。例如,可能需要一些时间来检测用户是否处于睡眠状态,以及是否以追溯方式发布了用户活动更新。请使用这些对象中包含的时间戳来正确进行评估。

onReceive 中,使用 PassiveMonitoringUpdate.fromIntent(intent) 解压缩数据。返回的 PassiveMonitoringUpdate 包含所请求测量结果的数据点列表。它还包含一个 UserActivityInfo 对象列表,描述用户活动状态的变化以及这些变化的发生时间,如以下示例所示:

class BackgroundDataReceiver : BroadcastReceiver() {
   override fun onReceive(context: Context, intent: Intent) {
       // Check that the Intent is for passive data
       if (intent?.action != PassiveMonitoringUpdate.ACTION_DATA) {
           return
       }
       val update = PassiveMonitoringUpdate.fromIntent(intent) ?: return

       // List of available data points
       val dataPoints = update.dataPoints

       // List of available user state info
       val userActivityInfoList = update.userActivityInfoUpdates
   }
}

将以下接收器添加到 <application> 标记内的 AndroidManifest.xml 中:

<receiver
   android:name=".BackgroundDataReceiver"
   android:exported="true">
   <intent-filter>
       <action android:name="hs.passivemonitoring.DATA" />
   </intent-filter>
</receiver>

创建 BroadcastReceiver 后,请使用 PassiveMonitoringClient 注册更新并提供 PassiveMonitoringConfig。如果您想要停止接收数据,请取消注册。

注册与您的应用相关联,每个应用一次最多只允许一次注册。如果进行了多次注册,则之前的注册将被替换。这意味着您的注册应包括您要接收的所有数据类型,并且您的 BroadcastReceiver 应处理所有这些类型。

val dataTypes = setOf(DataType.HEART_RATE_BPM, DataType.STEPS)
val config = PassiveMonitoringConfig.builder()
   .setDataTypes(dataTypes)
   .setComponentName(ComponentName(context, BackgroundDataReceiver::class.java))
   // To receive UserActivityState updates, ACTIVITY_RECOGNITION permission is required.
   .setShouldIncludeUserActivityState(true)
   .build()
lifecycleScope.launch {
   HealthServices.getClient(context)
       .passiveMonitoringClient
       .registerDataCallback(config)
       .await()
}

使用被动数据

PassiveMonitoringUpdate 中的 dataPoints 属性包含订阅的所有类型的数据点列表。通过首先计算启动时间戳获取每个 DataPoint 的时间戳,如以下示例所示:

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

然后,您可以将此值与每个数据点对应的 getStartInstant()getEndInstant() 一起使用。

使用 UserActivityState 信息

PassiveMonitoringUpdate 可以提供关于用户状态的概要信息,例如用户处于睡眠状态还是锻炼状态。如需接收这些更新,请执行以下操作:

  1. 确保已授予 ACTIVITY_RECOGNITION 权限。
  2. PassiveMonitoringConfig 构建器中设置 setShouldIncludeUserActivityState(true)

userActivityInfoUpdates 属性会返回与变化相关联的状态和时间戳列表。如果自上次 PassiveMonitoringUpdate 以来未发生任何状态变化,返回的列表可能为空。

// Inspect the last reported state change, if present
passiveMonitoringUpdate.userActivityInfoUpdates.lastOrNull()?.let { userActivityInfo ->
   // When the transition to this state took place
   val stateChangeInstant = userActivityInfo.stateChangeTime

   // The high-level state of the user, e.g. USER_ACTIVITY_ASLEEP, USER_ACTIVITY_EXERCISE
   val state = userActivityInfo.userActivityState

   if (state == UserActivityState.USER_ACTIVITY_EXERCISE) {
       // Obtain info about the exercise and whether it is owned by the app
       val exerciseInfo = userActivityInfo.exerciseInfo
   }
}

订阅被动目标

创建和注册被动目标的流程与注册数据类似。不过,与注册数据不同的是,每个事件都是一项单独的注册,应用可以注册多个被动目标。仅当事件与同一应用请求的先前事件匹配时,系统才会替换注册。

将以下代码添加到 AndroidManifest.xml 文件中的 <application> 标记内:

<receiver
   android:name=".BackgroundGoalsReceiver"
   android:exported="true">
   <intent-filter>
       <action android:name="hs.passivemonitoring.GOAL" />
   </intent-filter>
</receiver>

注册时,请定义您感兴趣的事件并注册每个事件。取消注册事件时,请使用相同的事件定义。

PassiveGoals 可以是一次性目标,也可以设置为重复目标。以下示例是达到 1000 步时的一次性目标。但是,将 TriggerType 更改为 REPEATED 会导致每一千步广播一次事件。

对于每日指标(例如 DAILY_STEPS 和 DAILY_CALORIES),将其设置为 REPEATED 会使目标每天触发一次。例如,10000 步的 DAILY_STEPS 目标不会在用户行走了 20000 步的情况下触发两次,并且会在当地时间的午夜重置。

// Create a goal for when 1000 steps are reached.
val thousandStepGoal = PassiveGoal(
   DataTypeCondition(
       DataType.STEPS,
       Value.ofLong(1000),
       ComparisonType.GREATER_THAN_OR_EQUAL),
   PassiveGoal.TriggerType.ONCE
)
lifecycleScope.launch {
    passiveClient.registerPassiveGoalCallback(
        thousandStepGoal,
        ComponentName(context, BackgroundGoalReceiver::class.java)
    ).await()
}

在对应的接收器代码中,请先检查 intent 操作以确保它适用于 PassiveGoal,然后再根据 intent 重构 PassiveGoal,以确定是哪些条件导致了事件:

class BackgroundGoalReceiver : BroadcastReceiver() {
   override fun onReceive(context: Context, intent: Intent) {
       // Check that the Intent is for a passive goal
       if (intent?.action != PassiveGoal.ACTION_GOAL) {
           return
       }
       // Obtain the goal to determine which goal caused the event
       val goal = PassiveGoal.fromIntent(intent) ?: return
       // Check against goals defined by the app
       if (goal == thousandStepGoal) {
           // Take appropriate action on reaching goal
       }
   }
}

启动后恢复注册

被动数据和目标注册在设备重启后不会保留。如果您的应用需要在设备重启后保持被动数据注册,您可以使用接收 ACTION_BOOT_COMPLETED 系统广播的 BroadcastReceiver 来实现此目标。

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

当设备开启时,注册数据时可能会出现延迟(10 秒或更长时间)。这可能会超出 BroadcastReceiver 的允许执行时间。因此,请使用 WorkManager 安排在未来注册数据,而不是立即进行注册。如需了解详情,请参阅开始使用 WorkManager

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
               .registerDataCallback(...)
               .await()
       }
       return Result.success()
   }
}

将以下接收器添加到 <application> 标记内的 AndroidManifest.xml 中:

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