You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

三星健康传感器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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 06:04:57