绑定服务崩溃与“Context.startForegroundService() 没有然后调用 Service.startForeground()”错误

Posted

技术标签:

【中文标题】绑定服务崩溃与“Context.startForegroundService() 没有然后调用 Service.startForeground()”错误【英文标题】:Bound service crash with "Context.startForegroundService() did not then call Service.startForeground()" error 【发布时间】:2018-05-16 11:53:59 【问题描述】:

我目前正在开发音频播放应用程序,并且正在使用已启动的绑定服务在后台播放音乐。我使用以下代码启动并绑定到服务。

val intent = Intent(context, MusicService::class.java)
context.startService(intent)
context.bindService(intent, serviceConnection, 0)

播放时提升到前台,音乐暂停时降级。

// onAudioPlay
service.startForeground(NOTIFY_ID, notification)

// onAudioPause
service.stopForeground(false)

到目前为止,服务工作正常。但是,当用户在暂停状态下滑动(删除)通知时,服务会在几秒钟后崩溃并给出此错误。

E/androidRuntime: FATAL EXCEPTION: main
    Process: com.example.myplayer, PID: 4380
    android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
        android.app.ActivityThread$H.handleMessage(ActivityThread.java:1775)
        android.os.Handler.dispatchMessage(Handler.java:105)
        android.os.Looper.loop(Looper.java:164)
        android.app.ActivityThread.main(ActivityThread.java:6541)
        java.lang.reflect.Method.invoke(Native Method)
      com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
        com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

这只发生在奥利奥上,我已经阅读了奥利奥的背景限制。但是以下几点让我感到困惑。

此服务为绑定服务(不受限制) 我从不使用 Context.startForegroundService() 来启动服务。(也不想使用它) 服务从前台降级时不会崩溃,它是在删除通知时发生的。

为什么服务会崩溃?我究竟做错了什么?如果有人告诉我这里发生了什么,我将非常感激。

【问题讨论】:

这很有趣。我建议您将解决方案发布为答案并将其标记为已接受,而不是编辑问题。未来的读者也会更清楚。 @YuchenZhong 感谢您的建议。我添加了一个答案。 【参考方案1】:

扩展UdeshUk's answer - 对MediaButtonReceiver 的任何使用(构建待处理意图或仅接收媒体按钮意图)都可能导致使用startForegroundService 调用您的服务,因为MediaButtonReceiver.onReceive 以这种方式启动您的服务在奥利奥上。我在任何地方都没有看到这方面的记录。

【讨论】:

我在调用 MediaButtonReceiver.handleIntent 时遇到了同样的问题,并且文档中有一条注释“一旦服务启动,它必须开始在前台运行”。在阅读您的答案之前,它非常神秘。 如果您在清单中注册 MediaButtonReceiver,Android 可以尝试通过调用 startForegroundService 重新启动您的媒体会话,如果在这种情况下您没有尽快显示通知,则会导致 ANR。您可以按照 [docs] (developer.android.com/guide/topics/media-apps/…) 中的说明选择退出此行为 您知道使用setMediaButtonReceiver(null) 是否会导致使用外部媒体控件出现任何问题吗?我更新了我的代码以使用它,它仍然可以识别来自我的蓝牙耳机的按钮命令,但将其设置为 null 似乎很奇怪。 @RJAylward 但实际上仅适用于 Android 5.0(API 级别 21)及更高版本?【参考方案2】:

我发现了问题。在我的通知中我已经设置了

setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(service, PlaybackStateCompat.ACTION_STOP))

因此,当用户删除通知时,它会调用 startForegroundService(),因为我之前在暂停时调用了 stopForeground()。

为了克服,我使用了一个自定义的待处理意图并在 onStartCommand() 中处理它,而不是 MediaButtonReveiver 的 PendingIntent

【讨论】:

你能解释一下那行代码是如何隐式调用 startForegroundService() 的吗?我有类似的问题,我从不调用它,它可能会提供线索。谢谢。 @SteveM 上面的行创建了一个PendingIntent,它以MediaButtonReceiver 为目标,然后它可以用startForegroundService 调用你的服务(这也取决于你如何在清单中配置你的意图过滤器) .因此,避免那些startForegroundService-calls 的最佳解决方案是避免MediaButtonReveiver,请参阅***.com/a/50629196/1394330 @UdeshUK 当你说你创建了一个自定义待定意图时,我很困惑。不只是在您的MediaBrowserService 工作中向onStartCommand() 添加通知吗? @KrisB 抱歉,我没有使用MediaBrowserService。我有一个自定义音乐服务来处理播放和媒体文件。【参考方案3】:

您必须在 Service 类的 onCreate() 方法中调用此方法:->

private void startServiceOreoCondition()
        if (Build.VERSION.SDK_INT >= 26) 


            String CHANNEL_ID = "my_service";
            String CHANNEL_NAME = "My Background Service";

            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    CHANNEL_NAME,NotificationManager.IMPORTANCE_NONE);
            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setCategory(Notification.CATEGORY_SERVICE).setSmallIcon(R.drawable.ic_tgc_icon).setPriority(PRIORITY_MIN).build();

            startForeground(101, notification);
        
    

【讨论】:

【参考方案4】:

晚会,但如前所述,它是MediaButtonReceiver;它有这个代码:

    @Override
    public void onReceive(Context context, Intent intent) 
        ...
        startForegroundService(context, intent);
        ...
    

我在绑定服务中使用接收器,所以我创建了一个与MediaButtonReceiver 具有完全相同代码的类,除了这一行。我将其替换为:

        context.startService(intent);

【讨论】:

谷歌真的很高兴让我们意识到这一点。如果 Google 修改了 MediaButtonReceiver 中的代码会怎样?每次有任何更改时,您是否都将代码复制到您的项目中? 我猜是的,我猜他们这样做是出于某种强制;您只能访问前台的媒体按钮。虽然这是短视的,但整个服务 api 无论如何都是一种痛苦。 我刚刚将setMediaButtonReceiver 设置为null,这似乎像这里提到的那样工作:developer.android.com/guide/topics/media-apps/… 我担心它会影响从蓝牙耳机处理媒体控制,但它不会。 你还能收听媒体按钮的事件吗? @KrisB 但实际上仅适用于 Android 5.0(API 级别 21)及更高版本?【参考方案5】:

来自文档8.0 Behavior Changes

系统甚至允许应用调用 Context.startForegroundService() 当应用程序在后台时。但是,应用程序必须调用它 服务的 startForeground() 方法在 5 秒内 服务已创建。

因此,您必须在 onCreate() 中为您的服务调用 startForeground。

【讨论】:

是的。但这发生在我的活动开放时。所以我的应用程序不在后台。而且我也不用 startForegroundService() 启动服务。【参考方案6】:

我有同样的问题,但我使用通知服务修复了它

public void createNotificationChannel()
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 

        NotificationChannel serviceChanel =new NotificationChannel(
                CHANNEL_ID,
                "Forground service Chane"
                , NotificationManager.IMPORTANCE_DEFAULT
        );
        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.createNotificationChannel(serviceChanel);
    

在课程开始时创建 Chanel id

public static final String CHANNEL_ID = "ForegroundServiceChannel";

和服务 onStartCommand indside

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 


        createNotificationChannel();
        Intent notificationIntent = new Intent(this , MainActivity.class);
        PendingIntent pendingIntent =PendingIntent.getActivity(this,0,notificationIntent,0);
        Notification notification = new NotificationCompat.Builder(this,CHANNEL_ID)
                .setContentTitle("ForgroundService")
                .setContentText("Any message")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentIntent(pendingIntent)
                .build();
        startForeground(1,notification);
    

输入代码

源https://androidwave.com/foreground-service-android-example/

【讨论】:

【参考方案7】:

MediaButtonReceiver 调用 startForegroundService,这对我来说是罪魁祸首。当我没有可播放的媒体(播放列表为空)时出现了我的问题。我通过在onStartCommand 中显示通知并立即取消它解决了这个问题:

    if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) 
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID);

        builder.setSmallIcon(R.drawable.notification)
                .setContentTitle("")
                .setContentText("")
                .setSmallIcon(R.drawable.notification);
        startForeground(99, builder.build());
        stopForeground(true);
        
        MyMediaButtonReceiver.handleIntent(mediaSessionCompat, intent);
    
        return START_NOT_STICKY;
      

【讨论】:

以上是关于绑定服务崩溃与“Context.startForegroundService() 没有然后调用 Service.startForeground()”错误的主要内容,如果未能解决你的问题,请参考以下文章

在 swift 3 上的按钮单击应用程序是与故事板绑定的崩溃

输入UI时RabbitMQ崩溃

多项活动服务

RxSwift rx_text 双向绑定崩溃

在代码隐藏崩溃中绑定到 UIElement

通过一对一关系绑定时文档关闭时崩溃