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

Kotlin Android Studio中服务重复启动致短信重复发送问题求助

问题排查与修复方案

核心问题:服务多次启动导致多协程并发执行

你的推测完全正确——每次调用startService()时,onStartCommand()都会被执行一次,而每次执行都会调用startUpdates()启动一个新的无限循环协程。随着服务被多次触发(比如系统回收后重启、其他组件重复调用启动),就会有多个独立的协程同时每隔3秒执行短信获取与发送逻辑,最终导致同一条短信被多协程重复发送,且重复数量随协程数量增加而增多。

具体代码问题点

  • 无重复启动判断:onStartCommand()没有检查是否已经存在运行中的协程,每次启动都会新建协程任务
  • 状态变量局部化:smsId、text、phone都是onStartCommand()内的局部变量,每个协程都维护自己的一套状态,无法跨协程去重
  • 协程管理缺失:没有对已启动的协程job进行跟踪,无法在重复启动时停止旧任务

修复方案

1. 全局管理协程任务与状态变量

将协程job、smsId、apiService等变量改为类成员,避免每次启动服务都新建实例:

class BckService : Service() {
    private lateinit var apiService: RestApiService
    private lateinit var smsManager: SmsManager
    private var job: Job? = null
    private var lastSmsId = 0
    private var lastText = ""
    private var lastPhone = ""

    override fun onCreate() {
        super.onCreate()
        // 仅在服务创建时初始化一次
        apiService = RestApiService()
        smsManager = if (Build.VERSION.SDK_INT >= 23) {
            getSystemService(SmsManager::class.java)
        } else {
            SmsManager.getDefault()
        }
    }

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        // 若已有活跃协程,先取消再启动新任务
        job?.takeIf { it.isActive }?.cancel()
        job = startUpdates {
            apiService.getSmsList { smsList ->
                for (sms in smsList) {
                    // 使用全局状态变量去重
                    if ((sms.text == lastText && sms.phone == lastPhone) || sms.id == lastSmsId) {
                        continue
                    }
                    lastText = sms.text
                    lastPhone = sms.phone
                    val parts = smsManager.divideMessage(sms.text)
                    smsManager.sendMultipartTextMessage(sms.phone, null, parts, null, null)
                    apiService.smsSent(sms.id)
                    lastSmsId = sms.id
                }
            }
        }
        // 返回START_STICKY保证服务被系统回收后可重启,可根据实际需求调整
        return START_STICKY
    }

    fun startUpdates(getData: () -> Unit): Job {
        return CoroutineScope(Dispatchers.IO).launch {
            while (isActive) {
                getData()
                delay(3000)
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // 服务销毁时取消协程,避免内存泄漏
        job?.cancel()
    }
}

2. 优化去重逻辑

原有的去重逻辑依赖lastSmsId,若短信列表返回顺序变化可能出现漏发或重复,建议:

  • 要求后端接口仅返回未发送状态的短信列表,从源头避免重复处理
  • 本地维护已发送短信ID的集合,每次处理前先检查是否已发送过

3. 避免服务重复启动

启动服务前先检查服务运行状态,避免重复调用startService():

private fun isServiceRunning(context: Context): Boolean {
    val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return manager.getRunningServices(Int.MAX_VALUE).any { 
        it.service.className == BckService::class.java.name 
    }
}

// 启动服务前执行判断
if (!isServiceRunning(this)) {
    startService(Intent(this, BckService::class.java))
}

内容的提问来源于stack exchange,提问作者رضا پریور

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 23:37:44