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

运行中前台服务因权限撤销触发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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 13:43:16