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,提问作者رضا پریور
相关产品推荐
相关产品推荐

