求助:解决Context.startForegroundService()未调用Service.startForeground()崩溃问题
咱们来拆解下你遇到的这个偶发崩溃问题——虽然你已经做了不少优化,但从日志和代码来看,还有几个潜在的问题点可能导致这个崩溃反复出现。
先从日志和崩溃栈分析现状
从你提供的日志来看:
2019-07-29 21:41:27,146 [[BaseOverlayService:62 onCreate]]: onCreate
2019-07-29 21:41:27,146 [[BaseOverlayService:142 b]]: BEFORE moveToForeground (called by onCreate)
2019-07-29 21:41:27,152 [[BaseOverlayService:159 b]]: AFTER moveToForeground (called by onCreate) - moved to foreground: true
2019-07-29 21:41:27,176 [[BaseOverlayService:79 onStartCommand]]: onStartCommand: isForeground: true | action: null | isStopping: false
2019-07-29 21:41:27,945 [[BaseOverlayService:142 b]]: BEFORE moveToForeground (called by updateNotification [OverlayService [onInitFinished]])
2019-07-29 21:41:27,947 [[BaseOverlayService:159 b]]: AFTER moveToForeground (called by updateNotification [OverlayService [onInitFinished]]) - moved to foreground: false
服务明明已经在onCreate阶段成功调用startForeground()并标记为前台状态,但还是触发了RemoteServiceException。这说明系统可能收到了多次startForegroundService()请求,但其中某次请求对应的startForeground()没有被系统检测到。
代码中的潜在问题点
1. 重复调用startForegroundService()而非startService()
在sendAction方法中,你对已运行的服务仍然使用ContextCompat.startForegroundService():
val intent = Intent(context, T::class.java) intent.action = action intentUpdater(intent) ContextCompat.startForegroundService(context, intent)
根据Android的规则:
startForegroundService()仅用于启动新服务,并且要求服务在5秒内调用startForeground()- 服务已经运行时,应该使用
startService()来传递指令,不需要再次触发前台服务的强制检测
如果对已处于前台的服务调用startForegroundService(),系统会再次启动一个“前台服务检测流程”,但此时你的代码因为isForeground为true不会调用startForeground(),这就会触发系统的崩溃检测。
2. isForeground变量非线程安全
isForeground是普通的布尔变量,没有任何线程同步措施:
protected var isForeground = false private set
当多个线程(比如主线程和系统服务线程)同时访问这个变量时,可能出现竞态条件:比如服务刚调用startForeground()但还没更新isForeground,此时新的onStartCommand触发,代码会认为服务不在前台,重复调用startForeground();或者反过来,isForeground被提前设为true,但startForeground()还没执行完成,导致系统检测超时。
3. 停止服务的流程存在时序问题
在stopService()方法中,你先调用moveToBackground(true)将isForeground设为false,再调用stopSelf():
onStopEvent() isStopping = true moveToBackground(true) stopSelf()
如果此时有一个延迟的startForegroundService()请求进入,onStartCommand会看到isForeground为false,尝试调用startForeground(),但此时服务正在停止,这会导致异常或者系统检测失败。
针对性修复方案
1. 区分服务启动和指令传递的调用方式
修改sendAction方法,当服务已运行时使用startService():
inline fun <reified T : BaseOverlayService<T>> sendAction(context: Context, checkIfServiceIsRunning: Boolean, action: String, intentUpdater: ((Intent) -> Unit) = {}) { if (checkIfServiceIsRunning && !isRunning<T>(context)) { L.logIf { DEBUG }?.d { "IGNORED action intent - action: $action" } return } L.logIf { DEBUG }?.d { "send action intent - action: $action" } val intent = Intent(context, T::class.java) intent.action = action intentUpdater(intent) // 已运行的服务用startService,避免触发前台检测 if (isRunning<T>(context)) { context.startService(intent) } else { ContextCompat.startForegroundService(context, intent) } }
2. 用原子变量保证isForeground的线程安全
将isForeground替换为AtomicBoolean,避免竞态条件:
protected val isForeground = AtomicBoolean(false) private set
然后修改moveToForeground方法中的状态判断和更新逻辑:
private fun moveToForeground(caller: String): Boolean { L.logIf { DEBUG }?.d { "BEFORE moveToForeground (called by $caller)" } val notification = notificationCreator(this as T) return if (!isForeground.get()) { // 先调用startForeground,再更新原子变量 startForeground(foregroundNotificationId, notification) isForeground.set(true) L.logIf { DEBUG }?.d { "AFTER moveToForeground (called by $caller) - moved to foreground: true" } true } else { notificationManager.notify(foregroundNotificationId, notification) L.logIf { DEBUG }?.d { "AFTER moveToForeground (called by $caller) - moved to foreground: false" } false } }
同时修改onStartCommand中的判断:
if (!isForeground.get()) { moveToForeground("onStartCommand") }
3. 优化停止服务的流程,阻止停止期间的前台操作
在moveToForeground中先检查isStopping状态,避免在停止过程中调用startForeground():
private fun moveToForeground(caller: String): Boolean { if (isStopping) { L.logIf { DEBUG }?.d { "IGNORED moveToForeground - service is stopping" } return false } // 原有的逻辑... }
同时调整stopService的时序,确保先标记停止状态,再处理前台切换:
protected fun stopService() { L.logIf { DEBUG }?.d { "stopService | isStopping: $isStopping" } if (isStopping) { L.logIf { DEBUG }?.d { "IGNORED stopService" } return } isStopping = true // 先标记停止状态 onStopEvent() moveToBackground(true) stopSelf() L.logIf { DEBUG }?.d { "stopService finished" } }
4. 处理服务重启的边界情况
因为你使用了START_STICKY,当服务被系统回收重启时,onStartCommand的intent可能为null。此时要确保无论之前的状态如何,都强制调用moveToForeground(),避免系统检测超时:
final override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val returnValue = START_STICKY L.logIf { DEBUG }?.d { "onStartCommand: isForeground: ${isForeground.get()} | action: ${intent?.action} | isStopping: $isStopping" } if (isStopping) { return returnValue } // 强制调用moveToForeground,即使isForeground为true也没关系(内部会判断) moveToForeground("onStartCommand") onStartCommandEvent(intent, flags, startId) return returnValue }
总结
这个偶发崩溃的核心原因大概率是重复调用startForegroundService()导致的系统检测触发,加上线程安全问题放大了偶现概率。按照上面的方案修复后,应该能彻底解决这个问题。
内容的提问来源于stack exchange,提问作者prom85




