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

Android 9 (Pie), Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord

首先, 我看了这些;

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

我试过这些,

我读了 Google 开发人员的一些评论,他们说它只是 Intended Behavior。不知是三星的系统还是AndroidOS出现的。有人对此有意见吗?我该如何解决这个问题?

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

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

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

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

onCreate()方法中

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

    Note: I don't know why but ContextCompat.startForegroundService() doesn't work properly.

出于这个原因,我手动将相同的功能添加到我的服务 class 而不是调用 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
}

希望此解决方案适合您。

Android Service 组件要正常工作有点棘手,尤其是在 Android 之后的版本中 OS 添加了额外的限制。正如其他答案中提到的,在启动 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
}

总是 return START_STICKY 在你的 Service 中。其他任何东西都可能是错误的,特别是如果你正在做任何类型的音频播放器。事实上,如果你正在做一个音频播放器,你不应该实现你自己的服务,而是使用 MediaBrowserServiceCompat(来自 AndroidX)。

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

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

经过与这次崩溃的斗争,我终于完全修复了这个异常并找到了解决方案。

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

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- 如果您的服务有操作(需要待处理的 Intents),请使用 Broadcast receiver 而不是当前的处理您的 pending 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- 始终在 停止您的服务 之前确保您的服务已创建并启动(我创建了一个具有我的服务状态的全局 class)

  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

希望对您有所帮助:))

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

我浪费了很多时间来弄清楚它崩溃的原因,却不明白为什么。问题是 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;
    }