致命 Android 12:异常:由于 mAllowStartForeground false 而不允许 startForegroundService()

use*_*924 84 android android-service android-12

我注意到 Pixel 5 和 Pixel 4a(均在 Android 12 上)有一个例外(Firebase Crashlytics),没有其他设备,仅发生两次,每个设备一次。

这是什么意思?Android 11 和 12 对于前台服务的使用规则相同,但 Android 11 没有问题。这是 Pixel 的错误吗?

来自 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)
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

Him*_*kur 46

除少数特殊情况外,面向 Android 12(API 级别 31)或更高版本的应用无法在后台运行时启动前台服务。如果应用程序在后台运行时尝试启动前台服务,并且前台服务不满足其中一种异常情况,系统将抛出 ForegroundServiceStartNotAllowedException。

后台启动限制的豁免

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

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

欲了解更多信息,请查看链接1 链接2

  • 很好的解释,但是解决方案是什么? (55认同)
  • 我在清单中声明了 `foregroundServiceType="mediaPlayback"` ,并且该服务仅在单击应用程序中的通知或播放按钮后启动,因此我希望它能够工作。尽管如此,我每个月仍然会发生几百次崩溃,这是 Android 中最神秘的区域之一 (13认同)
  • 绝对不。这是谷歌让我们彻底晾干的领域之一 (5认同)
  • 我发现,如果您的应用程序尝试启动 FG 服务,但由于接听电话等用户操作(基本上是任何会影响您的应用程序的操作)而被中断并进入后台,您将遇到此错误。因为它正在服务内部执行`startForeground(notificationId, notification);`,所以整个进程被杀死。 (4认同)
  • 看来这可能是 AOS12 中开始的错误,并且在 13 中仍然存在。[致命异常错误](https://issuetracker.google.com/issues/229000935) (3认同)

nir*_*997 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 );
}
Run Code Online (Sandbox Code Playgroud)

但是,由于Android 12 - Foreground service launch restrictions,我们将无法调用 Service 来执行后台任务。要了解有关此限制的更多信息,请参阅Android 12 行为更改

因此,从现在开始,(即)从 targetSdk 31 / Android 12+ 开始,Service只能在应用程序位于前台时调用。当应用程序关闭或应用程序转到后台时,调用ServiceusingstartForegroundService将导致ForegroundServiceStartNotAllowedException. 因此,要在Android 12及更高版本中执行后台任务,我们需要使用Worker而不是Service. 要了解更多信息Worker,请参阅工作请求

因此,对于面向 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 );
}
Run Code Online (Sandbox Code Playgroud)

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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

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 ();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

implementation 'androidx.work:work-runtime:2.7.1'
implementation 'com.google.guava:guava:27.0.1-android'
Run Code Online (Sandbox Code Playgroud)

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

希望这个解决方案可以帮助那些将应用程序定位为 SDK 31 / Android 12+ 的人。

  • 嘿@Lars!对于 Target SDK 31 及更高版本,应用程序只能使用前台服务执行少数任务,例如媒体播放、媒体投影、电话通话等。因此,对于其他任务,我们需要使用任何后台进程,例如“Worker”。您可以参考此页面以查找有关相同内容的更多信息:) https://developer.android.com/guide/components/foreground-services (2认同)
  • 我在 Android 12 google Pixel 上没有遇到此异常的任何问题,但三星那就是不同的故事了..为什么? (2认同)

Tim*_*iev 7

最多Android 12使用android:foregroundServiceType="yourType"服务属性即可AndroidManifest从后台启动您的服务(我假设如果您没有找到合适的服务类型,您需要跳过此步骤。不幸的是,我还没有在文档中找不到有关此内容的信息)。在 Android 12 及更高版本中 - 如果您的任务允许您使用WorkManager,那么您应该将您的服务替换为Worker,例如下载/上传文件或定期同步。如果您的任务比较广泛,需要服务不断在后台运行,例如播放音频,那么这里您仍然需要使用前台服务,但使用AlarmManager. 如果您的服务应该立即启动,那么您需要使用精确的警报来启动您的服务。为此,您必须确保用户已授予SCHEDULE_EXACT_ALARM权限。它由系统自动授予,但可以随时由用户或系统撤销。

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

  1. 添加android:foregroundServiceType="mediaPlayback"到您的AndroidManifest.xml文件:
<service
    android:name=".data.playback.PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="false"
    />
Run Code Online (Sandbox Code Playgroud)
  1. 在您想要开始服务的地方:
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)
                )
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 要要求您的用户授予启动确切警报的权限,您应该向用户解释为什么需要授予此权限并将用户重定向到系统设置部分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" }
            }
        }
    )
}
Run Code Online (Sandbox Code Playgroud)
  1. SCHEDULE_EXACT_ALARM您的应用程序获得权限后,系统会向其发送广播ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED。您可以实现广播接收器来处理此更改。

链接: