Wear OS环境模式下运动计时停止失效问题求助
问题分析
你遇到的核心问题是进入环境模式后,运动时长检测逻辑完全停止运行:
- 进入环境模式时调用了
unbindViewsFromService(),这个方法会取消uiBindingJob,直接终止了对service.latestMetrics的监听。 - 你的
sixMinTrain()是绑定在latestMetrics.collect回调里触发的,环境模式下这个监听停了,自然不会执行时长检测,更不会触发停止运动的操作。 - 另外,用
totalSecond == second做判断有精度风险——如果系统调度延迟导致某次循环跳过了目标秒数,就永远触发不了停止逻辑。
解决方案
方案1:把时长检测移到ExerciseService内部(推荐)
将自动停止的逻辑放到后台Service中,不受UI层环境模式影响,可靠性更高:
// 在ExerciseService内部实现 private val autoStopJob = Job() fun startExerciseWithAutoStop(targetSeconds: Int) { // 启动原有运动流程 startExercise() // 启动自动停止检测协程 CoroutineScope(Dispatchers.Default + autoStopJob).launch { while (exerciseState.value == ExerciseState.ACTIVE) { val duration = activeDurationCheckpoint.displayDuration(Instant.now(), exerciseState.value) if (duration.seconds >= targetSeconds) { endExercise() // 可通过广播通知UI层执行唤醒、播放声音等操作 sendBroadcast(Intent("com.your.app.AUTO_STOP_TRIGGERED")) break } delay(1000) // 每秒检测一次,可根据需求调整精度 } } } // 停止运动时记得取消检测协程 override fun endExercise() { super.endExercise() autoStopJob.cancel() }
UI层只需调用这个带自动停止参数的启动方法,无需再处理检测逻辑。
方案2:在UI层拆分逻辑,保留检测协程
修改环境模式处理逻辑,只暂停UI更新,保留时长检测的独立协程:
- 拆分UI更新和时长检测的Job:
private var uiUpdateJob: Job? = null private var durationCheckJob: Job? = null private fun bindViewsToService() { if (uiUpdateJob != null && durationCheckJob != null) return val scope = viewLifecycleOwner.lifecycleScope // UI更新Job,环境模式下可取消 uiUpdateJob = scope.launch { serviceConnection.repeatWhenConnected { service -> launch { service.exerciseState.collect { updateExerciseStatus(it) } } launch { service.latestMetrics.collect { it?.let { updateMetrics(it) } } } } } // 时长检测Job,不受环境模式影响 durationCheckJob = scope.launch { serviceConnection.repeatWhenConnected { service -> while (isActive) { sixMinTrain(30) delay(1000) } } } } private fun unbindViewsFromService() { uiUpdateJob?.cancel() uiUpdateJob = null // 不取消durationCheckJob }
- 修改
sixMinTrain()的判断条件,避免错过触发:
private suspend fun sixMinTrain(second: Int = 30) { if (cachedExerciseState != ExerciseState.ACTIVE) return val duration = activeDurationCheckpoint.displayDuration(Instant.now(), cachedExerciseState) val totalSecond = duration.seconds.toInt() Log.d(TAG, "sec = $totalSecond") if (totalSecond >= second) { // 用>=代替==,避免精度问题 Log.d(TAG, "触发自动停止") val service = checkNotNull(serviceConnection.exerciseService) service.endExercise() wakeUpScreen() updateChronometer() playSoundAsync() } }
- 环境模式切换时只处理UI更新Job:
private fun onEnterAmbient() { unbindViewsFromService() // 只取消UI更新 setAmbientUiState(true) performOneTimeUiUpdate() Log.d(TAG, "onEnterAmbient") } private fun onExitAmbient() { performOneTimeUiUpdate() setAmbientUiState(false) bindViewsToService() // 重新启动UI更新和检测(检测Job如果没取消会继续运行) Log.d(TAG, "onExitAmbient") }
额外注意事项
- 环境模式下系统会限制资源,
playSoundAsync()需要确保设备被唤醒,检查wakeUpScreen()是否正确获取了唤醒锁。 - 用固定间隔
delay(1000)触发检测,比依赖latestMetrics更稳定——因为 metrics 的更新频率不一定是每秒一次,可能导致检测延迟。
内容的提问来源于stack exchange,提问作者Johnosn
相关产品推荐
相关产品推荐

