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

重启应用后后台BLE扫描线程指向旧ViewModel的问题如何解决?

问题根因

后台线程持有旧ViewModel实例是问题核心。默认Koin提供的ViewModel是和页面(Activity/Fragment)生命周期绑定的,关闭应用再重新打开时,页面实例重建,Koin会注入新的ViewModel实例,而之前启动的死循环线程没有销毁,仍然持有旧ViewModel的引用,所以只能读到旧值,新页面的赋值操作是作用在新ViewModel实例上的,旧线程完全感知不到。同时你的无限循环线程会持续占用CPU资源,还会导致旧页面、旧ViewModel无法被回收,引发内存泄漏。

解决方案
  • 把告警状态从页面级ViewModel迁移到全局单例管理
    BLE扫描、告警属于全局后台业务,和页面生命周期无关,直接用Koin声明一个单例的AlertStateManager类,统一存储stopAlertingAt、isStopAlerting等全局状态,无论页面如何重建,所有逻辑访问的都是同一份状态,不会出现实例不一致的问题。
  • 替换无限轮询线程为延时任务
    无限循环轮询功耗极高,完全可以用延时任务替代。用户点击snooze按钮时,直接启动对应时长的延时任务,时间到后自动修改告警状态,无需持续轮询。可以用协程delay、AlarmManager或者WorkManager实现,其中WorkManager还支持进程被杀后任务仍然生效。
  • 状态持久化防止进程重启丢失数据
    把snooze的时间戳持久化存储到SharedPreferences或DataStore中,应用进程被杀后重启时,读取本地存储的时间戳计算剩余snooze时长,自动恢复状态,避免进程重启后状态丢失。
  • 销毁无用线程
    如果后续还有页面侧启动线程的场景,一定要在页面销毁(onDestroy)时主动终止线程,避免无效线程一直在后台运行占用资源、持有旧实例引发泄漏。
代码示例

全局单例状态管理(Koin声明为single单例)

class AlertStateManager {
    // 用StateFlow或LiveData都可,根据自己的技术栈选择
    val stopAlertingAt = MutableStateFlow<Long?>(null)
    val isStopAlerting = MutableStateFlow(false)
}

snooze按钮点击逻辑

binding.btnDismiss.setOnClickListener {
    val snoozeTime = System.currentTimeMillis()
    // 更新全局状态
    alertStateManager.stopAlertingAt.value = snoozeTime
    alertStateManager.isStopAlerting.value = true
    // 读取配置的snooze时长
    val durationMin = PreferenceManager.getDefaultSharedPreferences(this)
        .getString("dismiss_duration", "1")!!.toInt()
    // 启动延时任务
    CoroutineScope(Dispatchers.IO).launch {
        delay(durationMin * 60 * 1000L)
        alertStateManager.isStopAlerting.value = false
    }
    // 持久化存储snooze时间,进程重启也能恢复
    PreferenceManager.getDefaultSharedPreferences(this)
        .edit()
        .putLong("last_stop_alert_at", snoozeTime)
        .apply()
}

应用启动/后台服务启动时状态恢复逻辑

val lastSnoozeTime = PreferenceManager.getDefaultSharedPreferences(this)
    .getLong("last_stop_alert_at", 0)
val durationMin = PreferenceManager.getDefaultSharedPreferences(this)
    .getString("dismiss_duration", "1")!!.toInt()
val passedTime = System.currentTimeMillis() - lastSnoozeTime
if (passedTime < durationMin * 60 * 1000L) {
    // 还在snooze周期内,恢复状态并补剩余延时
    alertStateManager.isStopAlerting.value = true
    val remainingTime = durationMin * 60 * 1000L - passedTime
    CoroutineScope(Dispatchers.IO).launch {
        delay(remainingTime)
        alertStateManager.isStopAlerting.value = false
    }
} else {
    alertStateManager.isStopAlerting.value = false
}

页面侧的ViewModel只需要监听AlertStateManager的状态变化同步更新UI即可,不需要再单独存储告警相关状态,原有的无限轮询线程可直接删除。

内容的提问来源于stack exchange,提问作者Amine Harbaoui

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 20:09:03