如何在Watch Face Service中正确获取心率BPM数据?
问题描述
我用Jetpack Watch Face API制作了一款表盘,希望将心率BPM数据转为字符串绘制到画布上。不想使用complication(因其未来可能被弃用,且仅适用于Watch Face Format API),但不确定应直接从传感器还是通过Health Service API获取数据。
尝试了多种方法后选择使用Health Service API的被动监测方案,但该方案无法正常工作:模拟器中设置的BPM数据无法更新,且onNewDataPointsReceived函数从未被调用,Logcat中也未出现任何错误。想知道实现是否存在问题,以及获取这类数据的正确方式。
简化后的代码如下:
class RisingSunCanvasRenderer( /* nothing fancy here */) : Renderer.CanvasRenderer2<RisingSunCanvasRenderer.CSharedAssets>(/* nothing fancy here */) { private val healthClient = HealthServices.getClient(context) private val passiveMonitoringClient = healthClient.passiveMonitoringClient object HeartRate { private var bpm = 0.0f; fun setBpm(newBpm: Float) { bpm = newBpm } fun getBpm(): Float { return bpm } } class PassiveDataService : PassiveListenerService() { // Listener Service override fun onNewDataPointsReceived(dataPoints: DataPointContainer) { val data = dataPoints.getData(DataType.HEART_RATE_BPM) HeartRate.setBpm(data.last().value.toFloat()) Log.d("HEARTBEAT", "value updated") } } init { // I'm creating the listener on init of the renderer val passiveListenerConfig = PassiveListenerConfig.builder() .setDataTypes(setOf(DataType.HEART_RATE_BPM)) .build() passiveMonitoringClient.setPassiveListenerServiceAsync( PassiveDataService::class.java, passiveListenerConfig ) Log.d("MONITORING", "Init monitoring completed") } override fun render(canvas: Canvas, bounds: Rect, zonedDateTime: ZonedDateTime, sharedAssets: CSharedAssets) { canvas.drawColor(Color.BLACK) canvas.drawText(HeartRate.getBpm().toString(), bounds.centerX().toFloat(), bounds.centerY() + 100f, paint) } }
问题分析与修复方案
1. PassiveListenerService的类声明错误
你将PassiveDataService定义为RisingSunCanvasRenderer的内部类,系统无法单独实例化这个Service(内部类依赖外部类实例)。
- 修复:将
PassiveDataService改为顶层类,或添加companion object修饰使其成为静态内部类:
同时必须在// 改为顶层类 class PassiveDataService : PassiveListenerService() { override fun onNewDataPointsReceived(dataPoints: DataPointContainer) { val data = dataPoints.getData(DataType.HEART_RATE_BPM) if (data.isNotEmpty()) { HeartRate.setBpm(data.last().value.toFloat()) Log.d("HEARTBEAT", "value updated") } } }AndroidManifest.xml中注册该Service:<service android:name=".PassiveDataService" android:exported="false"> <intent-filter> <action android:name="com.google.android.gms.health.PASSIVE_LISTENER"/> </intent-filter> </service>
2. 缺失权限配置
获取心率数据需要声明并申请危险权限:
- 在
AndroidManifest.xml中添加权限:<uses-permission android:name="android.permission.health.READ_HEART_RATE"/> - 在运行时请求权限,建议在表盘的配置Activity或
WatchFaceService的onCreate中处理:// 在WatchFaceService中示例 private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> if (granted) { // 权限通过后初始化被动监测 initPassiveMonitoring() } } override fun onCreate() { super.onCreate() if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_HEART_RATE) != PackageManager.PERMISSION_GRANTED) { requestPermissionLauncher.launch(Manifest.permission.READ_HEART_RATE) } else { initPassiveMonitoring() } } private fun initPassiveMonitoring() { val healthClient = HealthServices.getClient(this) val passiveMonitoringClient = healthClient.passiveMonitoringClient val config = PassiveListenerConfig.builder() .setDataTypes(setOf(DataType.HEART_RATE_BPM)) .build() passiveMonitoringClient.setPassiveListenerServiceAsync(PassiveDataService::class.java, config) }
3. 初始化时机错误
你在RisingSunCanvasRenderer的init块中初始化监测,但此时权限可能还未获取,导致配置无效。
- 修复:将初始化逻辑移到权限请求成功之后,如上述
initPassiveMonitoring方法,确保只有权限通过才启动监测。
4. 模拟器数据模拟方式错误
在Wear模拟器中,需通过Extended Controls > Sensors面板发送心率数据,而非其他设置入口。确保在此处触发BPM更新,否则被动监测不会收到数据。
5. 线程安全问题
HeartRate单例的bpm变量在多线程环境下可能存在同步问题,需添加线程安全保障:
object HeartRate { @Volatile private var bpm = 0.0f fun setBpm(newBpm: Float) { bpm = newBpm } fun getBpm(): Float { return bpm } }
正确流程总结
- 声明并申请
READ_HEART_RATE权限。 - 将
PassiveDataService改为顶层类并在Manifest中注册。 - 权限通过后初始化被动监测。
- 在
onNewDataPointsReceived中处理心率数据,更新线程安全的全局变量。 - 在
render方法中读取心率值并绘制到画布。
内容的提问来源于stack exchange,提问作者R3DC0DE
相关产品推荐
相关产品推荐

