三星健康传感器API:Galaxy Watch后台IBI数据采集异常问题
问题描述
使用Samsung Health Sensor API(v1.3.0)在Galaxy Watch上采集心跳间隔(IBI)数据,需求是每小时采集10分钟,且在应用未激活、屏幕关闭的后台状态下完成采集。
当前问题:仅当屏幕点亮或应用前台运行时,IBI数据采集正常;一旦屏幕关闭或应用进入后台,API停止推送数据,无法按计划采样。已尝试前台服务、唤醒锁,检查过权限设置,均未解决。
采集数据的Worker代码
class IBIDataCollectionWorker( private val context: Context, workerParams: WorkerParameters ) : CoroutineWorker(context, workerParams) { private var healthService: HealthTrackingService? = null private var healthTracker: HealthTracker? = null private val ibiDataList = mutableListOf<Int>() private val dateFormat = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault()) private val trackerEventListener = object : HealthTracker.TrackerEventListener { override fun onDataReceived(dataPoints: List<DataPoint>) { for (dataPoint in dataPoints) { val validIbiList = getValidIbiList(dataPoint) ibiDataList.addAll(validIbiList) } } override fun onError(error: HealthTracker.TrackerError) { Log.e("IBIWorker", "Error: $error") } override fun onFlushCompleted() {} } private fun isIBIValid(ibiStatus: Int, ibiValue: Int): Boolean { return ibiStatus == 0 && ibiValue != 0 } private fun getValidIbiList(dataPoint: DataPoint): List<Int> { val ibiValues = dataPoint.getValue(ValueKey.HeartRateSet.IBI_LIST) val ibiStatuses = dataPoint.getValue(ValueKey.HeartRateSet.IBI_STATUS_LIST) return ibiValues.filterIndexed { index, value -> isIBIValid(ibiStatuses[index], value) } } override suspend fun doWork(): Result = withContext(Dispatchers.IO) { return@withContext try { val connectionDeferred = CompletableDeferred<Boolean>() healthService = HealthTrackingService(object : ConnectionListener { override fun onConnectionSuccess() { connectionDeferred.complete(true) } override fun onConnectionFailed(e: HealthTrackerException) { Log.e("IBIWorker", "Connection Failed: $e") connectionDeferred.complete(false) } override fun onConnectionEnded() {} }, context) healthService?.connectService() if (!connectionDeferred.await()) { return@withContext Result.failure(workDataOf("error" to "Connection failed")) } startTracking() delay(600000) // collect data for 10 min stopTracking() saveDataToFile() Result.success() } catch (e: Exception) { Log.e("IBIWorker", "Error during work execution", e) Result.failure(workDataOf("error" to e.message.toString())) } finally { healthService?.disconnectService() healthService = null } } private fun startTracking() { try { healthTracker = healthService?.getHealthTracker(HealthTrackerType.HEART_RATE_CONTINUOUS) if (healthTracker == null) { Log.e("IBIWorker", "Failed to create HealthTracker") return } healthTracker?.setEventListener(trackerEventListener) Log.d("IBIWorker", "Tracking started") } catch (e: Exception) { Log.e("IBIWorker", "Error starting tracking", e) } } private fun stopTracking() { healthTracker?.unsetEventListener() } private fun saveDataToFile() { val fileName = "ibi_data_worker.txt" val file = File(context.getExternalFilesDir(null), fileName) try { FileOutputStream(file, true).use { outputStream -> val timestamp = dateFormat.format(Date()) outputStream.write("${timestamp}:\n".toByteArray()) ibiDataList.forEach { ibiValue -> outputStream.write("${ibiValue},".toByteArray()) } } } catch (e: IOException) { Log.e("IBIWorker", "Error saving to file", e) } finally { ibiDataList.clear() } } }
MainActivity中启动任务的代码
private fun requestPermissions() { val permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> permissionGranted = permissions.all { it.value } if (permissionGranted) { scheduleIBIDataCollection() } else Toast.makeText(this, "permission denied", Toast.LENGTH_LONG).show() } permissionLauncher.launch(arrayOf(android.Manifest.permission.BODY_SENSORS)) } private fun scheduleIBIDataCollection() { val ibiWorkRequest = PeriodicWorkRequestBuilder<IBIDataCollectionWorker>( repeatInterval = 60, repeatIntervalTimeUnit = TimeUnit.MINUTES ).build() WorkManager.getInstance(applicationContext).enqueueUniquePeriodicWork( "ibi_data_collection", ExistingPeriodicWorkPolicy.KEEP, ibiWorkRequest ) }
可行解决方案
针对Galaxy Watch的低功耗策略和Samsung Health API的限制,以下是针对性解决步骤:
1. 补充三星专属后台传感器权限
除BODY_SENSORS外,在AndroidManifest.xml中添加三星专属权限:
<uses-permission android:name="com.samsung.android.health.permission.READ_HEART_RATE"/> <uses-permission android:name="com.samsung.android.permission.SENSORS_BACKGROUND"/>
运行时动态申请这些权限,确保权限全部通过。
2. 在Worker内部启动ForegroundService
WorkManager的CoroutineWorker在后台易被系统限制,需启动前台服务并显示通知(Wear OS强制要求):
override suspend fun doWork(): Result = withContext(Dispatchers.IO) { startForegroundService() // 原有连接、采集逻辑... } private fun startForegroundService() { // 提前创建通知渠道(Android O+) val channel = NotificationChannel( "IBI_COLLECTION_CHANNEL", "IBI数据采集", NotificationManager.IMPORTANCE_LOW ) val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) val notification = NotificationCompat.Builder(context, "IBI_COLLECTION_CHANNEL") .setContentTitle("IBI数据采集") .setContentText("正在采集心跳间隔数据") .setSmallIcon(R.drawable.ic_notification) .build() // 启动独立的ForegroundService val serviceIntent = Intent(context, IBICollectionForegroundService::class.java) ContextCompat.startForegroundService(context, serviceIntent) }
3. 配置HealthTracker后台采集模式
显式启用API的后台追踪配置,修改startTracking()方法:
private fun startTracking() { try { val config = HealthTrackerConfig.builder() .setDataTypes(setOf(DataType.HEART_RATE_SET)) .setBackgroundTrackingEnabled(true) // 关键:开启后台采集 .build() healthTracker = healthService?.getHealthTracker( HealthTrackerType.HEART_RATE_CONTINUOUS, config ) healthTracker?.setEventListener(trackerEventListener) Log.d("IBIWorker", "Tracking started with background mode") } catch (e: Exception) { Log.e("IBIWorker", "Error starting tracking", e) } }
4. 申请电池优化豁免
将应用加入Galaxy Watch的电池优化豁免名单:
- 在
AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
- 运行时请求豁免:
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) intent.data = Uri.parse("package:" + context.packageName) context.startActivity(intent)
同时引导用户手动确认Samsung Health应用的电池优化设置为“不优化”。
5. 改用AlarmManager调度任务
WorkManager在Wear OS后台调度可靠性不足,替换为AlarmManager触发定时任务:
private fun scheduleIBICollection() { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(context, IBICollectionReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) // 每小时触发一次,使用RTC_WAKEUP确保设备被唤醒 alarmManager.setRepeating( AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 60 * 60 * 1000, pendingIntent ) }
在IBICollectionReceiver中启动ForegroundService开始采集,10分钟后自动停止服务。
内容的提问来源于stack exchange,提问作者Abasin Ab
相关产品推荐
相关产品推荐

