通过将您的应用程序扩展到由 Wear OS 支持的可穿戴设备上,增强应用程序的健康与健身体验。
添加 Wear OS 模块
Android Studio 提供了一个方便的向导,可以将 Wear OS 模块添加到您的应用程序。在 **文件 > 新模块** 菜单中,选择 **Wear OS**,如以下图像所示
重要的是要注意,**最小 SDK** 必须为 **API 30** 或更高,以便您可以使用最新版本的健康服务。健康服务通过自动配置健康传感器,使跟踪指标和记录数据变得更加容易。
完成向导后,同步您的项目。以下 **运行** 配置将显示
这使您可以在可穿戴设备上运行 Wear OS 模块。您有两个选项
运行配置将应用程序部署到 Wear OS 模拟器或设备,并显示“hello world”体验。这是使用 Compose for Wear OS 的基本 UI 设置,可帮助您开始使用应用程序。
添加健康服务和 Hilt
将以下库集成到您的 Wear OS 模块中
创建健康服务管理器
为了使使用 健康服务 更方便,并公开更小、更流畅的 API,您可以创建类似于以下代码的包装器
private const val TAG = "WATCHMAIN"
class HealthServicesManager(context: Context) {
private val measureClient = HealthServices.getClient(context).measureClient
suspend fun hasHeartRateCapability() = runCatching {
val capabilities = measureClient.getCapabilities()
(DataType.HEART_RATE_BPM in capabilities.supportedDataTypesMeasure)
}.getOrDefault(false)
/**
* Returns a cold flow. When activated, the flow will register a callback for heart rate data
* and start to emit messages. When the consuming coroutine is canceled, the measure callback
* is unregistered.
*
* [callbackFlow] creates a bridge between a callback-based API and Kotlin flows.
*/
@ExperimentalCoroutinesApi
fun heartRateMeasureFlow(): Flow<MeasureMessage> = callbackFlow {
val callback = object : MeasureCallback {
override fun onAvailabilityChanged(dataType: DeltaDataType<*, *>, availability: Availability) {
// Only send back DataTypeAvailability (not LocationAvailability)
if (availability is DataTypeAvailability) {
trySendBlocking(MeasureMessage.MeasureAvailability(availability))
}
}
override fun onDataReceived(data: DataPointContainer) {
val heartRateBpm = data.getData(DataType.HEART_RATE_BPM)
Log.d(TAG, "💓 Received heart rate: ${heartRateBpm.first().value}")
trySendBlocking(MeasureMessage.MeasureData(heartRateBpm))
}
}
Log.d(TAG, "⌛ Registering for data...")
measureClient.registerMeasureCallback(DataType.HEART_RATE_BPM, callback)
awaitClose {
Log.d(TAG, "👋 Unregistering for data")
runBlocking {
measureClient.unregisterMeasureCallback(DataType.HEART_RATE_BPM, callback)
}
}
}
}
sealed class MeasureMessage {
class MeasureAvailability(val availability: DataTypeAvailability) : MeasureMessage()
class MeasureData(val data: List<SampleDataPoint<Double>>) : MeasureMessage()
}
创建用于管理它的 Hilt 模块后,使用以下代码片段
@Module
@InstallIn(SingletonComponent::class)
internal object DataModule {
@Provides
@Singleton
fun provideHealthServices(@ApplicationContext context: Context): HealthServicesManager = HealthServicesManager(context)
}
您可以像任何其他 Hilt 依赖项一样注入 HealthServicesManager
。
新的 HealthServicesManager
提供了一个 heartRateMeasureFlow()
方法,该方法注册一个用于心脏监视器的侦听器并发出接收到的数据。
在可穿戴设备上启用数据更新
与健身相关的數據更新需要 BODY_SENSORS
权限。如果您尚未这样做,请在应用程序的清单文件中声明 BODY_SENSORS
权限。然后,请求权限,如以下代码片段所示
val permissionState = rememberPermissionState(
permission = Manifest.permission.BODY_SENSORS,
onPermissionResult = { granted -> /* do something */ }
)
[...]
if (permissionState.status.isGranted) {
// do something
} else {
permissionState.launchPermissionRequest()
}
如果您在物理设备上测试应用程序,数据应开始更新。
从 Wear OS 4 开始,模拟器也会自动显示测试数据。在以前的版本中,您可以模拟来自传感器的數據流。在终端窗口中,运行以下 ADB 命令
adb shell am broadcast \
-a "whs.USE_SYNTHETIC_PROVIDERS" \
com.google.android.wearable.healthservices
要查看不同的心率值,请尝试模拟不同的锻炼。此命令模拟步行
adb shell am broadcast \
-a "whs.synthetic.user.START_WALKING" \
com.google.android.wearable.healthservices
此命令模拟跑步
adb shell am broadcast \
-a "whs.synthetic.user.START_RUNNING" \
com.google.android.wearable.healthservices
要停止模拟數據,请运行以下命令
adb shell am broadcast -a \
"whs.USE_SENSOR_PROVIDERS" \
com.google.android.wearable.healthservices
读取心率数据
在获得 BODY_SENSORS
权限后,您可以在 HealthServicesManager
中读取用户的 心率 (heartRateMeasureFlow()
)。在 Wear OS 应用的 UI 中,当前的心率值会显示出来,这是由可穿戴设备上的传感器测量的。
在您的 ViewModel
中,使用心率流对象开始收集数据,如下面的代码片段所示
val hr: MutableState<Double> = mutableStateOf(0.0)
[...]
healthServicesManager
.heartRateMeasureFlow()
.takeWhile { enabled.value }
.collect { measureMessage ->
when (measureMessage) {
is MeasureData -> {
val latestHeartRateValue = measureMessage.data.last().value
hr.value = latestHeartRateValue
}
is MeasureAvailability -> availability.value =
measureMessage.availability
}
}
使用类似于以下内容的可组合对象,在您的应用程序的 UI 中显示实时数据
val heartRate by viewModel.hr
Text(
text = "Heart Rate: $heartRate",
style = MaterialTheme.typography.display1
)
将数据发送到手持设备
要将健康和健身数据发送到手持设备,请在健康服务中使用 DataClient
类。以下代码片段展示了如何发送应用程序之前收集的心率数据
class HealthServicesManager(context: Context) {
private val dataClient by lazy { Wearable.getDataClient(context) }
[...]
suspend fun sendToHandheldDevice(heartRate: Int) {
try {
val result = dataClient
.putDataItem(PutDataMapRequest
.create("/heartrate")
.apply { dataMap.putInt("heartrate", heartRate) }
.asPutDataRequest()
.setUrgent())
.await()
Log.d(TAG, "DataItem saved: $result")
} catch (cancellationException: CancellationException) {
throw cancellationException
} catch (exception: Exception) {
Log.d(TAG, "Saving DataItem failed: $exception")
}
}
}
在手机上接收数据
要在手机上接收数据,请创建一个 WearableListenerService
@AndroidEntryPoint
class DataLayerListenerService : WearableListenerService() {
@Inject
lateinit var heartRateMonitor: HeartRateMonitor
override fun onDataChanged(dataEvents: DataEventBuffer) {
dataEvents.forEach { event ->
when (event.type) {
DataEvent.TYPE_CHANGED -> {
event.dataItem.run {
if (uri.path?.compareTo("/heartrate") == 0) {
val heartRate = DataMapItem.fromDataItem(this)
.dataMap.getInt(HR_KEY)
Log.d("DataLayerListenerService",
"New heart rate value received: $heartRate")
heartRateMonitor.send(heartRate)
}
}
}
DataEvent.TYPE_DELETED -> {
// DataItem deleted
}
}
}
}
}
完成此步骤后,请注意以下几个有趣的事实
@AndroidEntryPoint
注解允许我们在该类中使用 Hilt@Inject lateinit var heartRateMonitor: HeartRateMonitor
将确实在这个类中注入一个依赖项- 该类实现
onDataChanged()
并接收一个您可以解析和使用的事件集合
以下 HeartRateMonitor
逻辑允许您将接收到的心率值发送到应用程序代码库的其他部分
class HeartRateMonitor {
private val datapoints = MutableSharedFlow<Int>(extraBufferCapacity = 10)
fun receive(): SharedFlow<Int> = datapoints.asSharedFlow()
fun send(hr: Int) {
datapoints.tryEmit(hr)
}
}
数据总线从 onDataChanged()
方法接收事件,并使用 SharedFlow
将这些事件提供给数据观察者。
最后一步是在手机应用程序的 AndroidManifest.xml
中声明 Service
<service
android:name=".DataLayerListenerService"
android:exported="true">
<intent-filter>
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<data
android:host="*"
android:pathPrefix="/heartrate"
android:scheme="wear" />
</intent-filter>
</service>
在手持设备上显示实时数据
在运行在手持设备上的应用程序部分,将 HeartRateMonitor
注入到您的视图模型的构造函数中。此 HeartRateMonitor
对象观察心率数据并在需要时发出 UI 更新。