运行中前台服务因权限撤销触发SecurityException问题处理
问题分析
崩溃的核心原因是API 34+ 对前台服务(FGS)的权限校验逻辑:当用户撤销位置权限后,WorkManager 会根据自身的任务恢复机制重启之前的位置 Worker,但此时调用setForeground()时,系统会校验FOREGROUND_SERVICE_LOCATION和位置访问权限,权限缺失直接抛出SecurityException导致崩溃。这种恢复是系统级的,仅在APP启动时取消任务可能赶不上Worker的恢复执行时机。
解决方案
1. 在Worker内部前置权限检查(最关键)
在调用setForeground()之前必须重新检查权限,一旦发现权限缺失,立即取消自身任务并返回失败,从根源避免崩溃:
override suspend fun doWork(): Result { return try { // 前置权限检查:同时校验前台服务权限和位置权限 val hasFgsPermission = ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.FOREGROUND_SERVICE_LOCATION) == PackageManager.PERMISSION_GRANTED val hasLocationPermission = ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED if (!hasFgsPermission || !hasLocationPermission) { // 权限缺失,取消当前Worker任务 WorkManager.getInstance(applicationContext).cancelWorkById(id) notificationHelper.hideNotification(notificationId) return Result.failure() } // 权限正常,继续执行逻辑 notificationHelper.showNotification(createNotification(), notificationId) setForeground(createForegroundInfo()) withContext(dispatcher) { while (kotlin.coroutines.coroutineContext.isActive) { // 每次循环重新检查权限,防止中途被撤销 val currentLocationPermission = ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED if (!currentLocationPermission) { break } val location = getLocation() notificationHelper.showNotification(createNotification(), notificationId) delay(MINUTES_30) } } Result.success() } catch (e: Exception) { if (e is CancellationException) { Result.failure() } else { // 捕获SecurityException兜底处理 if (e is SecurityException) { WorkManager.getInstance(applicationContext).cancelWorkById(id) notificationHelper.hideNotification(notificationId) } Timber.e(e) Result.failure() } } }
2. 监听权限变更广播,主动取消Worker
注册权限变更的广播接收器,当位置权限被撤销时,立即取消对应的WorkManager任务:
// 在Application或主Activity中动态注册广播 val permissionReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == Intent.ACTION_PERMISSION_CHANGED) { val hasLocationPermission = ContextCompat.checkSelfPermission(context!!, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED if (!hasLocationPermission) { WorkManager.getInstance(context).cancelAllWorkByTag(WORK_NAME) notificationHelper.hideNotification(notificationId) } } } } // 注册广播(注意在合适时机注销,避免内存泄漏) val filter = IntentFilter(Intent.ACTION_PERMISSION_CHANGED) registerReceiver(permissionReceiver, filter)
3. 优化APP启动时的取消逻辑
在Application的onCreate()中尽早执行取消任务,配合Worker内部的检查覆盖系统恢复场景:
class MyApp : Application() { override fun onCreate() { super.onCreate() // 尽早取消位置Worker WorkManager.getInstance(this).cancelAllWorkByTag(WORK_NAME) notificationHelper.hideNotification(notificationId) // 可选:清理已完成的旧任务,减少冗余 WorkManager.getInstance(this).pruneWork() } }
关键注意点
- 不要依赖启动前的权限检查:Worker可能被系统自动恢复(比如APP进程被杀死后重启),此时之前的权限状态已失效,必须在Worker每次执行时重新检查。
- 捕获SecurityException兜底:即使做了前置检查,也可能存在竞态条件(比如检查后权限被立即撤销),所以必须在
setForeground()的调用路径中捕获异常。 - 使用动态广播:Android 8.0+对静态广播限制严格,动态注册权限变更广播更可靠。
内容的提问来源于stack exchange,提问作者Dannie
相关产品推荐
相关产品推荐

