致命的Android 12错误:异常:由于mAllowStartForeground为false,无法使用startForegroundService()。

81

我注意到一个例外情况(Firebase Crashlytics),在Pixel 5和Pixel 4a(均运行Android 12)上出现,其他设备没有出现过,每个设备只有一次。

这是什么意思?Android 11和12对于前台服务的工作有相同的规定,但是在Android 11中没有问题。这是Pixel的一个bug吗?

来自Firebase Crashlytics的信息:

Fatal Exception: android.app.ForegroundServiceStartNotAllowedException
startForegroundService() not allowed due to mAllowStartForeground false: service com.*.*/.service.RecorderService

android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel (ForegroundServiceStartNotAllowedException.java:54)
androidx.core.content.ContextCompat.startForegroundService (ContextCompat.java:6)
MyAppPackageHidden.service.RecorderService$Companion.startService (RecorderService.java:2)
MyAppPackageHidden.ui.rec.RecActivity$getConnectionRecorderService$1.onServiceConnected (RecActivity.java:4)
android.app.LoadedApk$ServiceDispatcher.doConnected (LoadedApk.java:2077)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1003)

Fatal Exception: android.app.ForegroundServiceStartNotAllowedException: startForegroundService() not allowed due to mAllowStartForeground false: service MyAppPackageHidden/.service.RecorderService
       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 androidx.core.content.ContextCompat$Api26Impl.startForegroundService(ContextCompat.java)
       at androidx.core.content.ContextCompat.startForegroundService(ContextCompat.java:6)
       at MyAppPackageHidden.service.RecorderService$Companion.startService(RecorderService.java:2)
       at MyAppPackageHidden.ui.rec.RecActivity$getConnectionRecorderService$1.onServiceConnected(RecActivity.java:4)
       at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:2077)
       at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:2110)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loopOnce(Looper.java:201)
       at android.os.Looper.loop(Looper.java:288)
       at android.app.ActivityThread.main(ActivityThread.java:7838)
       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)

Caused by android.os.RemoteException: Remote stack trace:
    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)

enter image description here


也许在调用 startForegroundService() 时,RecActivity 此时在后台运行。 - CommonsWare
@CommonsWare 我想是这样的。Activity有一个bindService方法,它需要一个ServiceConnection对象作为回调,并在onServiceConnected方法中将服务设置为前台。我想我需要使用lifecycleScope.launchWhenStarted { /* start foreground */ } - user924
1
请参阅此相关问题和可能的解决方案,有关 ForegroundServiceStartNotAllowedException 的信息,请点击这里 - Jadent
@user924 你是如何解决这个问题的?我也遇到了audio_service的同样问题。你能分享一下解决方案吗? - Alex Aung
9个回答

44

针对 Android 12 (API level 31) 或更高版本的应用,除了少数特殊情况外,无法在后台运行期间启动前台服务。如果应用在后台运行时尝试启动前台服务,而前台服务不符合特殊情况之一,则系统会抛出 ForegroundServiceStartNotAllowedException 异常。

免受后台启动限制的例外情况

在以下情况下,您的应用即使在后台运行也可以启动前台服务:

  • 应用从用户可见状态(例如活动)转换过来。
  • 应用可以从后台启动活动,但不包括应用在现有任务的后堆栈中具有活动的情况。
  • 使用 Firebase Cloud Messaging 接收到高优先级消息。
  • 用户在与您的应用相关的 UI 元素上执行操作。例如,他们可能与气泡、通知、小部件或活动互动。
  • 应用通过准确警报调用以完成用户请求的操作。
  • 应用是设备的当前输入法。
  • 应用接收了与地理围栏或活动识别转换相关的事件。
  • 设备重新启动并在广播接收器中接收 ACTION_BOOT_COMPLETED、ACTION_LOCKED_BOOT_COMPLETED 或 ACTION_MY_PACKAGE_REPLACED 意图操作。

如果需要更多信息,请查看链接1链接2


52
好的解释,但是解决方案是什么? - Shailendra Madda
13
在我的清单文件中声明了 foregroundServiceType="mediaPlayback",并且服务只在用户点击应用程序通知或播放按钮后才会启动,所以我希望它能正常工作。然而,每个月仍然会有几百次崩溃,这是 Android 中最神秘的领域之一。 - avalancha
5
绝对不行。这是谷歌让我们彻底摆烂的领域之一。 - avalancha
4
我发现,如果您的应用程序试图启动FG服务,但由于用户操作(例如接听电话等任何会将应用程序置于后台的操作)而被中断和后台化,那么您将遇到这个错误。因为它在服务内执行了startForeground(notificationId, notification);,整个进程被杀死了。 - Dan Davis
3
似乎这可能是在AOS12中开始的一个错误,并且仍然存在于13中。致命异常错误 - Dan Davis
显示剩余5条评论

26

以前我们使用 Service 来运行后台任务,例如数据备份、设置提醒通知等。之前调用服务的代码如下:

Intent serviceIntent = new Intent ( context, BackupService.class );
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService ( serviceIntent );
} else {
    context.startService ( serviceIntent );
}

但是,由于Android 12-前台服务启动限制,我们将无法调用服务执行后台任务。要了解更多信息,请参考Android 12 行为变更

因此,从现在开始,即从targetSdk 31 / Android 12+版本开始,只有当应用程序在前台运行时才能调用Service。当应用程序关闭或进入后台时,使用startForegroundService调用Service将导致ForegroundServiceStartNotAllowedException。因此,在Android 12及以上版本中执行后台任务,我们需要使用Worker而不是Service。要了解有关Worker的更多信息,请参阅Work Requests

因此,针对目标SDK 31 / Android 12+的应用程序,调用后台任务的代码如下:

Intent serviceIntent = new Intent ( context, BackupService.class );
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    OneTimeWorkRequest request = new OneTimeWorkRequest.Builder ( BackupWorker.class ).addTag ( "BACKUP_WORKER_TAG" ).build ();
    WorkManager.getInstance ( context ).enqueue ( request );
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService ( serviceIntent );
} else {
    context.startService ( serviceIntent );
}

BackupService(现有)的示例代码。

public class BackupService extends Service {

    private static final String TAG = "BackupService";

    @Nullable
    @Override
    public IBinder onBind ( Intent intent ) {
        return null;
    }

    @Override
    public int onStartCommand ( Intent intent, int flags, int startId ) {
        Log.d ( TAG, "onStartCommand" );
        startForeground ( BACKUP_SERVICE_NOTIFICATION_ID, createServiceNotification () );
        //call methods to perform background task
        return super.onStartCommand ( intent, flags, startId );
    }
}

BackupWorker的示例代码(新添加)。

public class BackupWorker extends Worker {

    private static final String TAG = "BackupWorker";

    public BackupWorker ( @NonNull Context context, @NonNull WorkerParameters workerParams ) {
        super ( context, workerParams );
    }

    @NonNull
    @Override
    public Result doWork () {
        //call methods to perform background task
        return Result.success ();
    }
}

请确保在模块级别的gradle文件中添加以下依赖项

implementation 'androidx.work:work-runtime:2.7.1'
implementation 'com.google.guava:guava:27.0.1-android'

我已测试上述代码在Android 5、Android 8、Android 11和Android 12上的工作情况。在我的情况下按预期工作。

希望这个解决方案能够帮助那些针对SDK31/Android 12+开发应用程序的人。


1
那么,如果对于应用程序来说,始终在前台运行应用程序非常重要,而不仅仅是为了完成一些任务呢? - Lars
2
嘿@Lars!对于Target SDK 31及以上版本,应用程序只能使用前台服务来执行少量任务,例如媒体播放、媒体投影、电话呼叫和其他一些任务。因此,对于其他任务,我们需要使用任何后台进程,如“Worker”。您可以参考此页面以获取有关此内容的更多信息 :) https://developer.android.com/guide/components/foreground-services - niranj1997
1
谢谢,我就怕会这样。我想要求用户禁用电池优化可能是我的解决办法。感谢您的快速回复! - Lars
1
我在 Android 12 Google Pixel 上没有遇到这个异常的问题,但是在三星手机上就不同了。为什么? - Kebab Krabby
不需要在较低版本的Android中仍然使用BackupService。您也可以在那里使用Worker。 - Malachiasz
我应该如何处理doWork方法?我的意思是,我怎样才能从那里启动我的服务? - Aanal Shah

6

在Android 12及以下版本中,只需在AndroidManifest中使用android:foregroundServiceType="yourType"服务属性即可从后台启动您的服务(如果找不到合适的服务类型,则需要跳过此步骤。不幸的是,文档中没有相关信息)。在Android 12及以上版本中 - 如果您的任务允许使用WorkManager,则应该将服务替换为Worker,例如用于下载/上传文件或定期同步。如果您的任务更加复杂且需要在后台持续运行服务,例如播放音频,则仍然需要使用前台服务,但可以使用AlarmManager来运行它。如果您的服务应立即启动,则需要使用精确闹钟来启动您的服务。为此,您必须确保用户已授予SCHEDULE_EXACT_ALARM权限。系统会自动授予该权限,但用户或系统随时可以撤销。

假设我们使用PlaybackService来播放音频。那么我的解决方案如下:

  1. 在你的AndroidManifest.xml文件中添加android:foregroundServiceType="mediaPlayback"
<service
    android:name=".data.playback.PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="false"
    />
  • 在您想要开始服务的地方:
  • private suspend fun startPlaybackService(state: PlaybackState) {
        withContext(Dispatchers.Main) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                val operation = PendingIntent.getForegroundService(
                    context,
                    REQUEST_CODE,
                    Intent(context, PlaybackService::class.java),
                    FLAG_UPDATE_CURRENT_COMPAT
                )
                val alarmManager = context.getSystemService<AlarmManager>()
                if (state in PlaybackStateSets.ACTIVE) {
                    logi { "Starting playback service with exact alarm" }
                    startPlaybackServiceWithAlarm(alarmManager, operation)
                } else {
                    logi { "Cancelling exact alarm operation" }
                    alarmManager.cancel(operation)
                }
            } else {
                if (state in PlaybackStateSets.ACTIVE) {
                    logi { "Starting playback service" }
                    ContextCompat.startForegroundService(
                        context,
                        Intent(context, PlaybackService::class.java)
                    )
                }
            }
        }
    }
    

    要请求用户授权启动准确的闹钟,您应该向用户解释为什么需要授予权限,并将用户重定向到系统设置部分的 “闹钟和提醒”:Alarms & reminders
    fun navigateToAlarmSettings(context: Context) {
        context.startActivity(
            Intent().apply {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                    action = Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
                } else {
                    loge { "Attempt to create Alarm settings section intent on Android sdk version < 31" }
                }
            }
        )
    }
    

    当应用程序被授予SCHEDULE_EXACT_ALARM权限时,系统会向其发送ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED广播。您可以实现一个广播接收器来处理此更改。
    链接:

    @user924 这不是黑客行为。这个解决方案是由谷歌推荐的。请查看文档 https://developer.android.com/about/versions/12/behavior-changes-12#foreground-service-launch-restrictions为了完成用户请求的及时操作,请在精确的闹钟内启动前台服务。 - Timur Panzhiev
    1
    @sagar-patel 如果在foregroundServiceType列表中找不到合适的类型,您可以跳过此步骤,一切都将正常工作。这一步仅适用于少数服务类型,请在此处检查可能的值https://developer.android.com/guide/topics/manifest/service-element。如果您的计步器仍然崩溃,请给出反馈。 - Timur Panzhiev
    1
    @sagar-patel SCHEDULE_EXACT_ALARM 是在 Android 12 中引入的100%精确的闹钟权限。该权限可以随时由用户或系统撤销。但是,在 Android 13 中还引入了启动精确闹钟的 USE_EXACT_ALARM 权限。虽然 SCHEDULE_EXACT_ALARMUSE_EXACT_ALARM 权限都表示相同的功能,但它们的授权方式不同,并支持不同的用例。请查看文档 https://developer.android.com/training/scheduling/alarms#exact-permission-declare - Timur Panzhiev
    1
    @TimurPanzhiev 是的...你说得对。谢谢你纠正我。我很感激。我已经实现了解决方案并移交给QA。我已经接受了你的答案。我一定会在这里分享结论。 - Sagar Patel
    1
    @sagar-patel 我不知道你的实现情况,但可能需要在启动新操作之前取消挂起的警报操作,请检查更新的答案。 - Timur Panzhiev
    显示剩余5条评论

    3
    我们的媒体播放应用程序也遇到了同样的问题。在我们的情况下,根本原因是每次播放器暂停时我们使用了stopForeground(false),并在恢复播放时使用了startForeground(...)。这在API < 31中是必要的,以便当播放暂停时,通知可以被用户滑动清除。
    从Android 12(API 31)开始,这导致了一个问题,即当应用程序暂停播放(例如由于短暂的音频焦点丢失)时,以下恢复(例如在获得音频焦点时)尝试调用startForeground(...)时会失败,并出现上述崩溃。
    我们的解决方法是只有当应用程序完全失去音频焦点或被用户关闭时才调用stopForeground(false)
    我们对应用程序的唯一更改是,当播放暂停时,我们现在使用类似以下内容的东西:
    if (isNotificationActive) {
        // update notification
        notificationManager.notify(...);
    
        // Starting with Android 12 (API 31), we cannot stop the foreground service on pause, otherwise we won't be able to resume later.
        if (Build.VERSION.SDK_INT < 31) {
            stopForeground(false);
        }
    }
    

    3

    [注意:以下内容与Android 12及更高版本无关]

    在我的案例中,我们使用一个用于SIP通信(VoIP)的服务,我们需要启动一个前台服务来进行时间敏感操作(例如注册)或运行SIP呼叫时。这些任务不能在Worker中运行。

    为了处理这种情况,Android允许您声明服务的“foregroundServiceType”,某些类型可以从后台创建前台服务。另一个值得注意的用例是媒体播放。

    对于Android 12及更高版本

    如评论中所述,foregroundServiceType属性不再受Android 12支持。我当前的解决方案是捕获从startForegroundService()抛出的异常(作为检测到我们正在使用Android 12的方法),然后使用一个AlarmManager启动前台服务,如此答案中所述。

    请注意,AlarmManager提供多个闹钟模式,有些模式不能从“深度睡眠”中唤醒设备。如果需要能够从“深度睡眠”中唤醒设备以启动服务,请尝试setAlarmClock()setAndAllowWhileIdle()。在Android Developers上的Keep Device Awake文章此答案中还讨论了其他几个选项。


    我已经声明了我的代码 android:foregroundServiceType="mediaPlayback",但我仍然遇到了崩溃问题。我有一个与服务相关联的媒体通知。有任何想法吗? - casolorz
    在进一步研究后,我意识到这个答案是错误的 - 从Android 12开始,没有foregroundServiceType可以设置,使您能够启动前台服务,否则应用程序将被排除在外 - 允许前台服务的Android 12启发式方法很复杂,但不受foregroundServiceType的影响。 - Guss
    我的当前实现是尝试使用startForgroundService()启动前台服务,如果由于OP错误而失败,则捕获抛出的异常并使用AlarmManager作为备用方案,如此处所述:https://stackoverflow.com/a/53759060/53538 - Guss
    1
    嗨@Guss,我已经实现了相同的功能,但是如果应用程序处于睡眠模式,闹钟管理器不会触发待定意图。在调试时它可以正常工作。 - androidStud
    @androidStud 之前链接的 AlarmManager 回答展示了 set() 的使用,但这并不是我实际使用的方法 - 我使用 setAlarmClock() 来唤醒设备从深度睡眠中唤醒。我在我的回答中添加了更多细节。 - Guss

    0
    我发现最好的解决方案是通过 Android O+ 上的 AlarmManager 在后台或设备重启后启动服务。
    val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    val i = Intent(context, Service::class.java)
    val pi = PendingIntent.getForegroundService(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
    val calendar: Calendar = Calendar.getInstance()
    calendar.timeInMillis = System.currentTimeMillis()
    calendar.add(Calendar.SECOND, 3)
    mgr.set(AlarmManager.RTC_WAKEUP, calendar.timeInMillis ,pi)
    

    此外,在 NotificationBuilder 中添加了:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        notificationBuilder.foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
    }
    
    notificationBuilder.setPriority(NotificationCompat.PRIORITY_MAX)
    

    有什么区别? - user924
    区别在于,Android似乎允许通过这种方式在设备重新启动后从后台启动服务。 - EAK TEAM
    1
    有趣,谢谢,我会尝试的。但如果它能够运行,它仍然是一个黑客程序,应用可能会被从Google Play中移除 :) - user924
    1
    因此,如果用户希望在设备启动/重新启动时从后台自动启动前台服务等功能,则最好要求用户禁用电池优化。 - user924

    0
    根据上面的评论所说,你在应用程序在后台运行时不允许启动前台进程。
    private boolean isInBackground(){
        ActivityManager.RunningAppProcessInfo myProcess = new ActivityManager.RunningAppProcessInfo();
        ActivityManager.getMyMemoryState(myProcess);
        Boolean isInBackground = myProcess.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
        Log.d("isInBackground", myProcess.processName + " " + myProcess.importance + " " + isInBackground);
        return  isInBackground;
    }
    

    在调用startForegroundService()之前,检查应用是否在后台运行解决了我的问题。

    -1

    我们可以把这个作为基本解决方案吗?

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                    context.startService ( Service_name );
                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    ContextCompat.startForegroundService(
                            context,
                            Service_name
                    );
                } else {
                    context.startService ( Service_name );
                }
    

    -1

    要解决 Android 12 或更高版本的此问题,请更新您的应用程序逻辑:

    如果您发现您的应用程序在后台运行时启动前台服务,请更新您的应用程序逻辑以使用 WorkManager。要查看如何更新您的应用程序的示例,请浏览 GitHub 上的 WorkManagerSample


    3
    要从后台启动前台服务,我们可以禁用应用的电池优化,然后它将在没有任何问题的情况下工作。 - user924
    1
    但是通过禁用电池优化并不是一个理想的解决方案。 - Shailendra Madda
    1
    @Christian 你是在说关于user924的评论,建议在设置中禁用电池优化吗? - Shailendra Madda
    1
    @ShailendraMadda 没错。对于造成的混淆,我向您道歉 :) - Christian
    1
    感谢克里斯蒂安的澄清。 - Shailendra Madda
    显示剩余2条评论

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接