Android 12(SDK 31)触发ForegroundServiceStartNotAllowedException如何解决?
Android 12(API 31)升级后小组件启动前台服务崩溃问题
问题背景
将应用的targetSdkVersion和compileSdkVersion升级到SDK 31后,在用于后台更新小组件的服务中会出现如下崩溃:
java.lang.RuntimeException: at android.app.ActivityThread.handleReceiver (ActivityThread.java:4321) at android.app.ActivityThread.access$1600 (ActivityThread.java:247) at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2068) at android.os.Handler.dispatchMessage (Handler.java:106) at android.os.Looper.loopOnce (Looper.java:201) at android.os.Looper.loop (Looper.java:288) at android.app.ActivityThread.main (ActivityThread.java:7842) at java.lang.reflect.Method.invoke (Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1003) Caused by: android.app.ForegroundServiceStartNotAllowedException: at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel (ForegroundServiceStartNotAllowedException.java:54) at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel (ForegroundServiceStartNotAllowedException.java:50) at android.os.Parcel.readParcelable (Parcel.java:3333) at android.os.Parcel.createExceptionOrNull (Parcel.java:2420) at android.os.Parcel.createException (Parcel.java:2409) at android.os.Parcel.readException (Parcel.java:2392) at android.os.Parcel.readException (Parcel.java:2334) at android.app.IActivityManager$Stub$Proxy.startService (IActivityManager.java:5971) at android.app.ContextImpl.startServiceCommon (ContextImpl.java:1847) at android.app.ContextImpl.startForegroundService (ContextImpl.java:1823) at android.content.ContextWrapper.startForegroundService (ContextWrapper.java:779) at android.content.ContextWrapper.startForegroundService (ContextWrapper.java:779) at com.mypackage.appname.ui.widget.widget_package.WidgetClassName.onUpdate (WidgetClassName.java:48) at android.appwidget.AppWidgetProvider.onReceive (AppWidgetProvider.java:66) at com.mypackage.appname.ui.widget.widget_package.WidgetClassName.onReceive (WidgetClassName.java) at android.app.ActivityThread.handleReceiver (ActivityThread.java:4312) at android.app.ActivityThread.access$1600 (ActivityThread.java:247) at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2068) at android.os.Handler.dispatchMessage (Handler.java:106) at android.os.Looper.loopOnce (Looper.java:201) at android.os.Looper.loop (Looper.java:288) at android.app.ActivityThread.main (ActivityThread.java:7842) at java.lang.reflect.Method.invoke (Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1003) Caused by: android.os.RemoteException: at com.android.server.am.ActiveServices.startServiceLocked (ActiveServices.java:691) at com.android.server.am.ActiveServices.startServiceLocked (ActiveServices.java:616) at com.android.server.am.ActivityManagerService.startService (ActivityManagerService.java:11839) at android.app.IActivityManager$Stub.onTransact (IActivityManager.java:2519) at com.android.server.am.ActivityManagerService.onTransact (ActivityManagerService.java:2498)
如果你使用Firebase Crashlytics这类崩溃采集工具,收到的堆栈信息将类似如下:
Caused by android.app.ForegroundServiceStartNotAllowedException: startForegroundService() not allowed due to mAllowStartForeground false: service com.mypackage.appname/.ui.widget.widget_package.MyForegroundServiceName at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel(ForegroundServiceStartNotAllowedException.java:54) at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel(ForegroundServiceStartNotAllowedException.java:50) at android.os.Parcel.readParcelable(Parcel.java:3333) at android.os.Parcel.createExceptionOrNull(Parcel.java:2420) at android.os.Parcel.createException(Parcel.java:2409) at android.os.Parcel.readException(Parcel.java:2392) at android.os.Parcel.readException(Parcel.java:2334) at android.app.IActivityManager$Stub$Proxy.startService(IActivityManager.java:5971) at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1847) at android.app.ContextImpl.startForegroundService(ContextImpl.java:1823) at android.content.ContextWrapper.startForegroundService(ContextWrapper.java:779) at android.content.ContextWrapper.startForegroundService(ContextWrapper.java:779) at com.mypackage.appname.ui.widget.widget_package.WidgetClassName.onUpdate(WidgetClassName.java:48) at android.appwidget.AppWidgetProvider.onReceive(AppWidgetProvider.java:66) at com.mypackage.appname.ui.widget.widget_package.WidgetClassName.onReceive(WidgetClassName.java) at android.app.ActivityThread.handleReceiver(ActivityThread.java:4312) at android.app.ActivityThread.access$1600(ActivityThread.java:247) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2068) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7842) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
根因说明
Android 12(API 31)新增了前台服务启动限制,系统仅允许应用在处于特定前台场景、或被加入临时白名单的情况下启动前台服务。而AppWidgetProvider.onUpdate属于普通广播回调,不会将应用加入允许启动前台服务的临时白名单,当应用处于后台时触发该回调、并调用startForegroundService()就会抛出ForegroundServiceStartNotAllowedException。
复现方法
- 把应用的
targetSdkVersion和compileSdkVersion升级到31及以上 - 在
AppWidgetProvider.onUpdate()回调中直接调用startForegroundService()启动前台服务 - 将应用退到后台,触发小组件更新(可通过调整小组件刷新周期、或系统自动触发更新)
- 即可复现该崩溃
修复方案
方案1:使用WorkManager替代前台服务(推荐)
小组件更新通常是耗时较短的后台任务,使用Jetpack WorkManager调度任务可以完全规避前台服务启动限制,适配所有Android版本。
示例代码如下:
// 定义执行小组件更新的Worker class WidgetUpdateWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { // 把原来放在前台服务里的小组件更新逻辑移到这里执行 val appWidgetManager = AppWidgetManager.getInstance(context) val appWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(context, WidgetClassName::class.java)) appWidgetIds.forEach { widgetId -> // 执行单组件更新逻辑 updateSingleWidget(widgetId) } return Result.success() } private fun updateSingleWidget(widgetId: Int) { // 你的自定义更新逻辑 } } // 在Widget的onUpdate回调中触发任务 override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { val updateRequest = OneTimeWorkRequestBuilder<WidgetUpdateWorker>() .build() WorkManager.getInstance(context).enqueue(updateRequest) }
方案2:场景适配,仅前台状态启动前台服务
如果你的业务必须使用前台服务,可以在启动前先判断应用当前的前后台状态,仅前台状态下启动前台服务,后台状态下降级为WorkManager调度。
private boolean isAppOnForeground(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.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName.equals(packageName)) { return true; } } return false; } // 调用处逻辑 if (isAppOnForeground(context)) { // 应用在前台,正常启动前台服务 context.startForegroundService(new Intent(context, MyForegroundService.class)); } else { // 后台状态使用WorkManager执行任务 OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(WidgetUpdateWorker.class).build(); WorkManager.getInstance(context).enqueue(workRequest); }
方案3:申请特殊前台服务权限(不推荐)
如果业务必须在后台启动前台服务,可以在AndroidManifest.xml中声明特殊权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" /> <service android:name=".ui.widget.widget_package.MyForegroundServiceName" android:foregroundServiceType="specialUse" android:exported="false"> </service>
该方案需要应用启动时引导用户手动开启"允许特殊前台服务"权限,且Google Play上架时需要提供合理的使用说明,不符合要求的应用会被驳回。
内容的提问来源于stack exchange,提问作者Vedprakash Wagh
相关产品推荐
相关产品推荐

