Android 9 (Pie),Context.startForegroundService() 没有调用 Service.startForeground():ServiceRecord

Posted

技术标签:

【中文标题】Android 9 (Pie),Context.startForegroundService() 没有调用 Service.startForeground():ServiceRecord【英文标题】:Android 9 (Pie), Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord 【发布时间】:2019-09-17 13:30:51 【问题描述】:

首先, 我看了这些;

Context.startForegroundService() did not then call Service.startForeground() Context.startForegroundService() did not then call Service.startForeground android 9 (Pie) Only: Context.startForegroundService() did not then call Service.startForeground() - Works fine on Oreo RemoteServiceException Context.startForegroundService() did not then call Service.startForeground() https://issuetracker.google.com/issues/76112072

我有一个将近一百万人使用的流媒体应用程序。我正在为播放器使用前台服务。我还没有实现MediaSession。我有 99.95% 的无崩溃会话。 所以这个应用程序适用于所有版本,但我开始收到 android 9 的崩溃报告 (ANR)。这种崩溃只发生在Samsung 手机,尤其是s9, s9+, s10, s10+, note9 型号。

我试过这些,

onCreate() 中调用startForeground() 方法 在Context.stopService() 之前调用Service.startForeground() 类似问题的其他 *** 答案

我从 Google 的开发人员那里读到了一些 cmets,他们说只是 Intended Behavior。我想知道它是由三星的系统还是 Android 操作系统发生的。有人对此有意见吗?我该如何解决这个问题?

【问题讨论】:

是否有不能调用 startForeground 的路径?还是主线程停滞/休眠/做太多工作无法处理服务启动的路径? 这是一个广播/音乐应用程序,它只是播放流 URL。我的手机没有崩溃。有用户表示在后台听音乐时发生崩溃 如果编写完美的应用程序有可能发生这种情况,我不会感到惊讶,因为无法保证您的代码会及时被调用。虽然this answer 中的特定代码部分是一团糟,但解决方法似乎有希望:通过绑定让服务运行,将其移动到前台,然后使用startForegroundService(),然后取消绑定稍后。 对。通过 startForegroundService 启动前台服务以调用 startForeground I 服务后,您有一个短窗口。出于某种原因,这在这些设备上发生得不够快 如果这仅本地化到三星设备,则表明他们的自定义版本的 Android 9 AOSP 存在错误 - 而不是您的代码。在可以忠实再现之前,任何解决方法都只是猜测。 【参考方案1】:

在与这个崩溃进行了太多的斗争之后,我彻底修复了这个异常并找到了解决方案。

确保在您的服务中完成了这些工作,我将它们列出如下: (有些东西是重复的,正如另一个答案中提到的,我只是再写一遍)。

1- 呼叫

startForeground()

onCreateonStartCommand 中。(可以多次调用 startForeground())

  @Override
public void onCreate() 
    super.onCreate();
    startCommand();


@Override
public int onStartCommand(Intent intent, int flags, int startId) 
    if (intent == null) 
        return START_NOT_STICKY;
    
    final int command = intent.getIntExtra(MAIN_SERVICE_COMMAND_KEY, -1);

    if (command == MAIN_SERVICE_START_COMMAND) 
        startCommand();
        return START_STICKY;
    
    return START_NOT_STICKY;


 private void startCommand() 
    createNotificationAndStartForeground();
    runningStatus.set(STARTED);

2- 停止您的服务使用

context.stopService()

,不需要调用stopForeground()stopSelf()

  try 
        context.stopService(
                new Intent(
                        context,
                        NavigationService.class
                )
        );
     catch (Exception ex) 
        Crashlytics.logException(ex);
        LogManager.e("Service manager can't stop service ", ex);
    

3- 启动您的服务使用

ContextCompat.startForegroundService()

它将处理不同的 API 版本。

   ContextCompat.startForegroundService(
            context,
            NavigationService.getStartIntent(context)
    );

4- 如果您的服务有操作(需要待处理的 Intent),则使用 广播接收器 处理您的 待处理的意图,而不是您当前的服务(它将调用您的服务Create() 并且可能很危险,或者使用 PendingIntent.FLAG_NO_CREATE) ,最好使用特定的广播接收器来处理您的服务通知操作,我的意思是使用 PendingIntent.getBroadcast()强>。

    private PendingIntent getStopActionPendingIntent() 
    final Intent stopNotificationIntent = getBroadCastIntent();

    stopNotificationIntent.setAction(BROADCAST_STOP_SERVICE);

    return getPendingIntent(stopNotificationIntent);


private PendingIntent getPendingIntent(final Intent intent) 
    return PendingIntent.getBroadcast(
            this,
            0,
            intent,
            0
    );


new NotificationCompat.Builder(this, CHANNEL_ID)
            .addAction(
                    new NotificationCompat.Action(
                            R.drawable.notification,
                            getString(R.string.switch_off),
                            getStopActionPendingIntent()
                    )
            )

5- 始终在停止您的服务之前确保您的服务已创建并启动(我创建了一个具有我的服务状态的全局类)

  if (navigationServiceStatus == STARTED) 
            serviceManager.stopNavigationService();
        

6- 将您的 notificationId 设置为长数字,例如 121412。

7- 使用 NotificationCompat.Builder 将处理不同的 API 版本,您只需要为构建版本创建通知通道 >= Build.VERSION_CODES.O.(这不是一个解决方案,只是让您的代码更具可读性)

8- 添加

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> 

权限您的清单。 (这个在 android 文档中有提到) Android Foreground Service

希望对你有帮助:))

【讨论】:

为什么我们必须在 onCreate 和 onStartCommand 中都这样做? @Sepehr 什么是 MAIN_SERVICE_COMMAND_KEY ? awwe,很高兴在很长一段时间后在这里见到你 Sepehr...感谢它在 onStartCommand 中添加 startForegroundService 对我来说就像一个魅力。【参考方案2】:

我正在等待我的崩溃报告来分享解决方案。将近 20 天我没有遇到任何崩溃或 ANR。我想分享我的解决方案。它可以帮助遇到这个问题的人。

onCreate()方法中

首先,我的应用是一个媒体应用。我还没有实现媒体会话。我正在onCreate() 的顶部创建一个通知渠道。 Official doc 我在Context.startForegroundService() 方法之后调用Service.startForeground() 方法。在我的prepareAndStartForeground() 方法中。

注意:我不知道为什么,但是 ContextCompat.startForegroundService() 不能正常工作。

出于这个原因,我已经手动将相同的函数添加到我的服务类中,而不是调用ContextCompat.startForegroundService()

private fun startForegroundService(intent: Intent) 
    if (Build.VERSION.SDK_INT >= 26) 
        context.startForegroundService(intent)
     else 
        // Pre-O behavior.
        context.startService(intent)
    


prepareAndStartForeground()方法

private fun prepareAndStartForeground() 
    try 
        val intent = Intent(ctx, MusicService::class.java)
        startForegroundService(intent)

        val n = mNotificationBuilder.build()
        // do sth
        startForeground(Define.NOTIFICATION_ID, n)
     catch (e: Exception) 
        Log.e(TAG, "startForegroundNotification: " + e.message)
    


这是我的onCreate()

override fun onCreate() 
    super.onCreate()
    createNotificationChannel()
    prepareAndStartForeground()


我的onStartCommand()

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int 
    if (intent == null) 
        return START_STICKY_COMPATIBILITY
     else 
        //....
        //...
    
    return START_STICKY


onRebindonBindonUnbind 之类的方法

internal var binder: IBinder? = null

override fun onRebind(intent: Intent) 
    stopForeground(true) // <- remove notification


override fun onBind(intent: Intent): IBinder? 
    stopForeground(true) // <- remove notification
    return binder


override fun onUnbind(intent: Intent): Boolean 
    prepareAndStartForeground() // <- show notification again
    return true


onDestroy() 调用时我们需要清除一些东西

   override fun onDestroy() 
    super.onDestroy()
    releaseService()
   

private fun releaseService() 
    stopMedia()
    stopTimer()
    // sth like these
    player = null
    mContext = null
    afChangeListener = null
    mAudioBecomingNoisy = null
    handler = null
    mNotificationBuilder = null
    mNotificationManager = null
    mInstance = null

我希望此解决方案适合您。

【讨论】:

感谢分享您的实施。似乎您有基于绑定的连接。也许这可能会导致主要区别,否则相同的解决方案对您有效。感谢分享! 嗨,“我不知道为什么,但 ContextCompat.startForegroundService() 无法正常工作”的任何具体原因。 您好,您能具体说明一下您是如何创建此服务的吗?因为服务onCreate方法是在调用后调用的:ContextCompat.startForegroundService(context, intent),而这里你调用的是:你的服务onCreate上的startForegroundService。 @ingsaurabh 实际上,我不知道,因为我在日志中没有看到任何差异或发生任何崩溃。我基本上只能说 ContextCompat.startForegroundService() 不适用于我的代码:) @Beyazid 谢谢你的回答,但我不确定我是否理解。您示例中的 onCreate 方法是 MusicService 的 onCreate 吗?因为如果是,我看起来像你两次调用 startForegroundService 。一次在应用程序中形成不同的位置(为了“触发”服务 onCreate),第二次在服务 onCreate 中形成。【参考方案3】:

Android Service 组件要正常工作有点棘手,尤其是在操作系统添加了额外限制的更高版本的 Android 上。如其他答案中所述,启动Service 时,请使用ContextCompat.startForegroundService()。接下来,在Service.onStartCommand() 中,立即致电startForeground()。将要显示的Notification 存储为成员字段并使用它,除非它为空。示例:

private var notification:Notification? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int 
    if (notification == null) 
        notification = createDefaultNotification()
    
    startForeground(NOTIFICATION_ID, notification)

    // Do any additional setup and work herre

    return START_STICKY

始终在您的Service 中返回START_STICKY。其他任何事情都可能是错误的,特别是如果您正在制作任何类型的音频播放器。事实上,如果你正在做一个音频播放器,你不应该实现自己的 Service,而是使用 MediaBrowserServiceCompat(来自 AndroidX)。

我也推荐我在这上面写的博文:https://hellsoft.se/how-to-service-on-android-part-3-1e24113152cd

【讨论】:

始终在您的服务中返回 START_STICKY。还有什么可能是错误的,这是相当冒昧的,需要解释一下吗? @Tim 请阅读我链接到的帖子。在该系列中,我解释了为什么 START_STICKY 是您今天应该使用的。当然也有例外,但对于 99% 的需要 Service 的情况,这是正确的做法。【参考方案4】:

在使用相同的手机遇到同样的问题后,我做了一些更改,并且崩溃消失了。我不确定是什么造成的,但我猜是打电话 onCreate 和 onStartCommand 中的 startForeground。如果服务已经启动并且在 onCreate 中正确调用了所有内容,我不确定为什么需要这样做。

其他变化: - 将 serviceId 更改为某个较低的数字 (1-10) - 通过单例同步类调用 startFororegroundService 的频率较低(这是在崩溃之前实现的,以防止在使用来自 onStartCommand 的回调开始之前停止服务,但现在如果服务已经启动,它也会过滤调用)。 - 使用 START_REDELIVER_INTENT(不应该影响任何东西)

上述问题仅针对部分用户出现在上述手机上,因此我怀疑这与三星的一些新更新有关,最终将得到修复

【讨论】:

如果Service已经创建,调用Context.startForegroundService()需要startForeground onStartCommand。【参考方案5】:

我几乎已经消除了 MediaSessionCompat.Callback 方法中 startForeground() 的问题,例如 onPlay()、onPause()。

【讨论】:

【参考方案6】:

根据此错误消息,当您调用 Context.startForegroundService() 时,您必须使用 Service.startForeground() 方法发出通知。这是我理解的。

【讨论】:

【参考方案7】:

我浪费了很多时间来弄清楚它为什么会崩溃,但不明白为什么。问题是startForeground() 的通知 ID。将其更改为 0 以外的值。这是在 Android 11 上运行的。

@Override
    public int onStartCommand(Intent intent, int flags, int startId) 
        
        //Create notification here
        
        startForeground(5, builder.build()); //Change the ID to something other than 0

        return START_NOT_STICKY;
    

【讨论】:

以上是关于Android 9 (Pie),Context.startForegroundService() 没有调用 Service.startForeground():ServiceRecord的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android 9.0(PIE) 中获取 WIFI SSID?

Android Api(Okhttps)未在android 9(pie)及更高版本中调用[重复]

为啥 Android Studio 不会在 Android Pie (9.0) 上运行应用程序?

Android 9 (Pie),Context.startForegroundService() 没有调用 Service.startForeground():ServiceRecord

Android 9 Pie 崩溃 (com.google.android.gms...ClassNotFoundException)

多进程中的 Android Pie (9.0) WebView