2023年1月6日更新
我在我的音乐播放器应用中遇到了这个问题
Context.startForegroundService()
没有调用 Service.startForeground()
经过大量的研究,我找到了一些解决方案。也许这会帮助其他人
解决方案1:
如果您的应用程序的目标API级别为26或更高,则当应用程序自身不在前台时,系统会对运行后台服务施加限制。如果应用程序需要创建前台服务,则应用程序应调用 startForegroundService()
。即使在应用程序处于后台时,系统也允许应用程序调用 Context.startForegroundService()
。但是,应用程序必须在服务创建后的五秒内调用该服务的 startForeground()
方法。
注意:为使用 Context.startForegroundService()
的服务调用 onCreate()
中的 startForeground()
。
为什么会出现这个问题呢?因为 Android 框架无法保证您的服务在5秒内启动,但另一方面,框架对前台通知有严格的5秒内必须发送通知的限制。因此,在您的服务中,startForeground()
必须在 onCreate()
和 onStartCommand()
中都有通知,因为如果您的服务已经创建,并且您的活动正在尝试再次启动它,则不会调用 onCreate()
。通知 ID 不能为0,否则即使原因不同也会发生相同的崩溃。必须在 startForeground
方法之前调用 stopSelf
。
后台服务限制:
应用程序服务有五秒钟的时间调用 startForeground()
,如果应用程序未在时间限制内调用 startForegroudn()
,系统将停止服务并声明应用程序发生 ANR。
可能情况:
- 要么您的前台服务在调用
startForeground()
方法之前被销毁/结束。
- 要么如果前台服务已经实例化并再次调用它,则不会调用
onCreate()
方法,而是将调用 onStartCommand()
。然后将逻辑移到 onStartCommand()
中以调用 startForeground()
方法。
startForeground()
中的通知 ID 不得为0,否则也会导致崩溃。
解决方案2:
在onCreate()
和onStartCommand()
中都调用startForeground()
(可以多次调用startForeground()
)
使用context.stopService()
停止您的服务,不需要调用 stopForeground()
或 stopSelf()
使用ContextCompat.startForegroundService()
启动您的服务。它将处理不同的API。
如果您的服务有操作(需要挂起意图),请使用广播接收器处理您的挂起意图,而不是您当前的服务(它会在onCreate()
中调用您的服务,可能会很危险,或者使用PendingIntent.FLAG_NO_CREATE
),最好为处理您的服务通知操作创建一个特定的广播接收器,我的意思是使用PendingIntent.getBroadcast()
创建所有待处理的意图。
一些崩溃报告与Oreo中的BOOT_COMPLETED
处理相关的问题有关
基本上,一旦您执行了startForegroundService()
,就必须执行startForeground()
。如果您不能在服务中执行startForeground()
,那么最好在广播接收器中进行此类检查等 - 这样,只有在确保服务将执行startForeground()
时才会启动服务
添加像这样的Handler()
:
Handler().postDelayed(()>ContextCompat.startForegroundService(activity, new Intent(activity, ChatService.class)), 500);
解决方案3:
代替前台服务应该使用什么?
现在有JobScheduler
和JobService
,它们是前台服务的更好选择。这是一个更好的选择,因为:
当作业正在运行时,系统代表您的应用程序持有唤醒锁定。因此,您不需要采取任何措施来保证设备在作业期间保持唤醒状态。
这意味着您不再需要关心处理唤醒锁了,这就是为什么它与前台服务没有区别。从实现的角度来看,JobScheduler
不是您的服务,而是系统的服务,它可能会正确地处理队列,并且Google永远不会终止自己的子服务。
三星已经在其 Samsung Accessory Protocol (SAP) 中从
startForegroundService
切换到了
JobScheduler
和
JobService
。这对于智能手表等设备需要与手机这样的主机通讯,其中作业确实需要通过应用程序的主线程与用户进行交互非常有帮助。由于作业是由调度程序发布到主线程上的,因此变得可能。但请记住,作业正在主线程上运行,请将所有繁重的工作都卸载到其他线程和异步任务中。
解决方案4:
问题是要在服务本身中调用
Service.startForeground(id, notification)
,对吗?不幸的是,Android Framework无法保证在5秒内从
Service.onCreate()
调用
Service.startForeground(id, notification)
,但无论如何都会抛出异常,所以我想出了这个方法。在调用
Context.startForegroundService()
之前,使用服务将绑定器绑定到上下文。
如果绑定成功,则从服务连接调用
Context.startForegroundService()
并立即在服务连接内部调用
Service.startForeground()
。
重要提示:在try-catch中调用
Context.bindService()
方法,因为在某些情况下调用可能会引发异常,在这种情况下,您需要依靠直接调用
Context.startForegroundService()
并希望它不会失败。一个示例可以是广播接收器上下文,但在该情况下获取应用程序上下文不会抛出异常,但直接使用上下文会抛出异常。
解决方案5:
如果在调用
Context.startForegroundService(...)
之后,在调用
Service.startForeground(...)
之前调用
Context.stopService(...)
,则应用程序将崩溃。
谷歌团队提供的信息
这不是框架错误;这是故意的。如果应用程序使用
startForegroundService()
启动服务实例,则必须将该服务实例转换为前台状态并显示通知。如果在调用
startForeground()
之前停止服务实例,则该承诺未能实现:这是应用程序中的错误。
发布其他应用程序可以直接启动的服务基本上是不安全的。您可以通过将该服务的所有启动操作视为需要
startForeground()
来减轻一些风险,但显然可能不是您想要的。
解决方案6:
我成功地摆脱了所有崩溃。我所做的就是删除对
stopSelf()
的调用。(我正在考虑推迟停止,直到我相当确定已显示通知,但我不希望用户在不必要的情况下看到通知。)当服务闲置了一分钟或系统正常销毁它而不抛出任何异常时。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true);
} else {
stopSelf();
}
解决方案 7:
与前台服务使用相关的常见崩溃是:
Context.startForegroundService()没有调用Service.startForeground()
要消除此问题,请使用以下策略:
不要将服务作为前台服务启动,而应将其作为后台服务启动,绑定到它,然后当您在活动/ UI组件中获得服务实例时,可以直接调用服务内的方法,该方法调用, 并添加通知。
这可能听起来像一个小技巧,但请想象一下像 Spotify 这样的音乐应用如何启动他们的服务。这是一种类似的方法。结果是杰出的。使用此方法,问题计数从一个显着的不可透露的数字减少到0。
startForegroundService()
,稍后取消绑定。 - CommonsWare