禁用通知权限后启动前台服务导致崩溃(Android 13)

Fur*_*kul 20 android android-notifications android-permissions android-13

因此,我一直在尝试新的 Android 模拟器(即 Android 13 或 Android Tiramisu,API 33),并且在应用程序上,我需要一个前台服务。

该应用程序的目标 SDK 目前为 33。

由于通知要求,我已将其添加android.permission.POST_NOTIFICATIONS到清单中。而且,因为它也是运行时权限,所以我在打开应用程序后请求权限。

如果用户拒绝该权限,但在使用 启动前台服务后尝试执行涉及前台服务的任务startForegroundService,则在从我的服务调用时startForeground,我会崩溃:

android.app.RemoteServiceException$CannotPostForegroundServiceNotificationException: Bad notification for startForeground
        at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:1983)
        at android.app.ActivityThread.-$$Nest$mthrowRemoteServiceException(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2242)
        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:7898)
        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:936)
Run Code Online (Sandbox Code Playgroud)

请注意异常名称:CannotPostForegroundServiceNotificationException。显然,系统有效地阻止了我发布前台服务通知。这种情况发生在startForeground使用有效通知进行调用之后(无论如何,直到 API 32,并且我没有看到 API 32 和 API 33 之间用于构造通知本身的更改,此外setOngoing(true)这是我已经在做的事情)。

因此,我检查了是否可以使用NotificationManager.areNotificationsEnabled(). 如果用户按预期拒绝该权限,则返回 false。代码现在如下所示:

if (mNotificationManager.areNotificationsEnabled())
    startForeground(123, mNotificationBuilder.build())
Run Code Online (Sandbox Code Playgroud)

并且,正如预期的那样,startForeground没有被调用。但是,需要执行的任务可能很长(可能大约2分钟)并且必须在后台执行,这不能在作业中或通过 执行WorkManager,并且如果不调用startForeground,应用程序会在大约20秒后抛出异常具有以下内容:

android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{ecbbdfb u0 com.example.android/.service.FgService}
        at android.app.ActivityThread.generateForegroundServiceDidNotStartInTimeException(ActivityThread.java:2006)
        at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:1977)
        at android.app.ActivityThread.-$$Nest$mthrowRemoteServiceException(Unknown Source:0)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2242)
        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:7898)
        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:936)
     Caused by: android.app.StackTrace: Last startServiceCommon() call for this service was made here
        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1915)
        at android.app.ContextImpl.startForegroundService(ContextImpl.java:1870)
        at android.content.ContextWrapper.startForegroundService(ContextWrapper.java:822)
        at com.example.android.MainActivity.startTaskWithFgService(MainActivity.kt:30)
        at com.example.kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:749)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
Run Code Online (Sandbox Code Playgroud)

我应该注意,如果用户接受通知权限,通知通常会变得可见,因此这看起来像是权限问题,并且没有观察到崩溃。

编辑:创建的通知通道应发布静默通知。因此,这就是通知通道的创建方式(并且在尝试发布通知时也包含在内):

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
{
    withNotificationManager {
        if (notificationChannels?.map { it.id }?.contains(SILENT_NOTIF_CHANNEL_ID) == true)
            return@withNotificationManager

        val channel = NotificationChannel(
            SILENT_NOTIF_CHANNEL_ID,
            getString(R.string.silent_notif_channel_name),
            NotificationManager.IMPORTANCE_MIN).apply {
                enableLights(false)
                setShowBadge(false)
                setSound(null, null)
                description = getString(R.string.silent_notif_channel_desc)
                vibrationPattern = null
                lockscreenVisibility = Notification.VISIBILITY_SECRET
            }

        createNotificationChannel(channel)
    }
}
Run Code Online (Sandbox Code Playgroud)

据我了解,发生了两件事:

  • 我无法使用通知启动前台服务,因为权限被拒绝,
  • 我也无法在没有通知的情况下启动前台服务,因为通知是必要的。

这两个条件有效地消除了前台服务功能。这对我来说似乎是一个疏忽。

引用 Android 13 行为更改通知权限(链接):

"应用不需要请求 POST_NOTIFICATIONS 权限即可启动前台服务。但是,应用在启动前台服务时必须包含通知,就像在以前版本的 Android 上一样。

所以,我的问题是:

如果用户拒绝权限,我应该怎么做才能在没有前台服务的情况下在后台执行长任务?

感谢您阅读这个问题,我感谢任何帮助、回答或讨论。

Fur*_*kul 13

好的,我已经找到问题了。

显然,这是由于通知通道创建时造成的。到目前为止,根据我的观察,在发布通知之前创建一个频道很好,我们没有观察到崩溃/错误。在这种情况下,通道也是在通知发布之前创建的,也就是在服务启动之后和startForeground调用之前创建的。

如果用户事先拒绝该权限,则尝试以静默方式创建通知通道会失败,而不会崩溃。然后,在尝试使用 发布通知后startForeground,它失败并出现问题中发布的异常,并导致无法解决的问题。

由于我们不希望用户在第一次应用程序启动时拒绝通知权限,因此我已将创建通知通道任务移至此Application.onCreate(),问题已解决。前台服务工作了,它出现在FGS(前台服务管理器)中。

解决方案的关键点是:在Application.onCreate()处创建通知通道,然后在前台服务上调用startForeground而不检查NotificationManager.areNotificationsEnabled()。这应该允许系统在 FGS(前台服务管理器)中显示服务。

尽管据我所知,文档中没有指出这一点。