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

18

所以,我一直在尝试使用新的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)

请注意异常名称:CannotPostForegroundServiceNotificationException。所以显然系统有效地防止我发布前台服务通知。这是在使用有效通知(至少到API 32为止,而我没有看到在构建通知本身方面在API 32和API 33之间有任何变化,除了setOngoing(true),这是我已经在做的)之后发生的。

因此,我检查了是否可以发布通知,使用NotificationManager.areNotificationsEnabled()。如果用户拒绝权限,则返回false,正如预期的那样。现在代码看起来像这样:

if (mNotificationManager.areNotificationsEnabled())
    startForeground(123, mNotificationBuilder.build())

而且,正如预期的那样,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)

需要注意的是,如果用户接受了通知权限,通知将正常显示,因此这似乎是一个权限问题,并且没有观察到崩溃。

编辑:创建的通知渠道应该发布静默通知。 因此,这就是创建通知渠道的方式(也包括尝试发布通知时使用的方式):

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)
    }
}

据我的理解,发生了两件事情:

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

这两个条件有效地移除了前台服务功能。 我认为这看起来像是一个疏忽。

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

"应用程序无需请求POST_NOTIFICATIONS权限即可启动前台服务。 但是,与之前的Android版本一样,应用程序在启动前台服务时必须包含通知。"

所以,我的问题是:

如果用户拒绝了权限,我该如何在后台执行长时间任务而不需要前台服务?

感谢阅读问题,并感激任何帮助、答案或讨论。


2
嘿!有趣的问题。您能否也发布一段创建通知渠道的代码? 根据文档,即使权限被拒绝,它也应该可以工作-它只影响通知的可见性。如果用户拒绝通知权限,则仍然可以在前台服务(FGS)任务管理器中看到与这些前台服务相关的通知。 https://developer.android.com/about/versions/13/behavior-changes-13#privacy - PineapplePie
@PineapplePie 根据修改后的问题描述,通知发布前会创建频道。我可以说,在应用程序启动和发布通知之间创建频道并没有遇到任何问题。此外,应用程序直接崩溃,因此在 FGS 管理器中没有任何指示符。 - Furkan Yurdakul
2个回答

12

好的,我找到了问题所在。

显然,这是由于通知渠道创建的时间造成的。从我的观察中可以看出,创建通道后立即发布通知是没有问题的,我们也没有观察到崩溃或漏洞。但在这种情况下,通道也是在发布通知之前创建的,即在服务启动之后、startForeground 被调用之前。

如果用户事先拒绝了权限,则尝试静默创建通知渠道会失败,但不会崩溃。然后,在使用 startForeground 尝试发布通知之后,它将因在问题中发布的异常而失败,并引起无法解决的问题。

由于我们不希望用户在第一次应用程序启动时拒绝通知权限,我将创建通知渠道任务移动到 Application.onCreate() 中,问题得到了解决。前台服务可以正常工作,并显示在 FGS(前台服务管理器)中。

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

尽管据我所知,这并未在文档中提到。


那么在 Application.onCreate() 中创建所有所需的通知渠道对你有帮助吗?但是如果用户不授予权限会发生什么?前台服务仍然可以正常运行,对吧? - android developer
@androiddeveloper 当应用无法工作时,我唯一能想到的是用户在安装应用后,在打开应用之前从设置应用中明确禁用了通知权限。如果有人真的这样做,那将是一个巨大的巧合。默认情况下不会授予权限,但除非用户明确禁用权限,否则可以在默认状态下创建通知通道。而应用程序类似乎是最好的选择。 - Furkan Yurdakul
我观察到的行为是在API 33模拟器上。我不知道真实设备的情况,因为它们的行为可能有所不同。但是,如果有误解的话,我会相应地编辑答案:您应该在不检查“areNotificationsEnabled”的情况下调用startForeground。只要您有一个通道,即使用户从应用程序手动拒绝了权限,您也应该能够调用startForeground。我还相信这是未来需要修复或记录的问题。 - Furkan Yurdakul
我不知道。我会准备一个新版本,以便帮助我进一步调查它。 - android developer
无论如何,我仍然看到这个崩溃。 - android developer
显示剩余2条评论

1
以下是我对此事的个人见解,希望能对某人有所帮助。我在使用startForeground时也遇到了这个异常。结果发现,在创建通知渠道时,我在检查NotificationManager.areNotificationsEnabled()时出错(因此当返回值为false时没有创建通知渠道)。这绝对是我的错误,花了我一些时间才发现。

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