适配API31时,如何避免WorkManager触发ForegroundServiceStartNotAllowedException?
我们原本基于官方指南,用WorkManager的前台服务执行长时任务,适配API 31后,偶尔会触发android.app.ForegroundServiceStartNotAllowedException,崩溃日志显示SystemForegroundService无法调用startForeground(),核心原因是API 31新增限制:应用进入后台后,不能直接启动前台服务。结合代码来看,问题出在Application的onPause回调中启动WorkManager任务,再加上任务设置了初始延迟,当延迟结束时应用可能已经完全进入后台,此时WorkManager尝试启动前台服务就会触发系统拦截,模拟器里的通知也是系统针对这个拦截行为的提示。
以下是具体解决方案:
1. 避免在后台触发需前台服务的任务
不要在onPause这类可能让应用进入后台的生命周期回调里直接调度带前台服务的WorkManager任务。如果必须在应用退到后台时启动任务,先判断应用当前是否处于前台:
工具方法:判断应用前台状态
public static boolean isAppInForeground(Context context) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses(); if (appProcesses == null) { return false; } final String packageName = context.getPackageName(); for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { if (appProcess.processName.equals(packageName) && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { return true; } } return false; }
修改任务启动逻辑
public static void startCloudWorker(Context context) { // API31及以上,应用在后台时不启动需前台服务的任务 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !isAppInForeground(context)) { return; } OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(CloudWorker.class) .setInitialDelay(... TimeUnit.MILLISECONDS) // 其他配置 .build(); WorkManager workManager = WorkManager.getInstance(context); workManager.enqueue(oneTimeWorkRequest); }
2. 使用WorkManager加急任务(Expedited Work)替代前台服务(推荐)
API 31及以上,WorkManager提供了加急任务,无需手动启动前台服务,系统会自动处理前台展示逻辑,且完全符合系统后台启动限制:
修改任务构建代码
OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(CloudWorker.class) .setInitialDelay(... TimeUnit.MILLISECONDS) // 设置加急任务,配额不足时转为普通任务 .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) // 其他配置 .build();
如果需要自定义前台通知,仍可在Worker中调用setForegroundAsync,但加急任务本身已经满足系统后台启动的规则,不会触发异常。
3. 捕获异常并降级处理
如果无法避免在后台启动任务,可在doWork中捕获异常,改为后台执行任务(若任务允许)或重试:
@NonNull @Override public Result doWork() { try { final ForegroundInfo foregroundInfo = createForegroundInfo(...); // 同步等待前台服务启动,方便捕获异常 setForegroundAsync(foregroundInfo).get(); } catch (ExecutionException e) { if (e.getCause() instanceof ForegroundServiceStartNotAllowedException) { // 后台无法启动前台服务,改为后台执行任务 return runBackgroundJob(); // 或者返回重试,等待应用回到前台后再执行 // return Result.retry(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); return Result.failure(); } // 执行长时任务 // Some busy job here... return Result.success(); }
4. 优化任务的初始延迟
去掉不必要的初始延迟,或确保延迟结束时应用大概率处于前台。如果必须保留延迟,可结合WorkManager的约束条件(如setRequiredNetworkType),或自定义应用前台状态约束,只有当应用回到前台时才执行任务。
模拟器中出现的通知是Android 12+系统的默认提示,当应用后台启动前台服务被拦截时,系统会通知用户,解决上述问题后该通知会自动消失。
内容的提问来源于stack exchange,提问作者Cheok Yan Cheng

