Android 9 (Pie), Context.startForegroundService() не вызывал Service.startForeground(): ServiceRecord

Прежде всего, Я посмотрел на это;

Android 9 (Pie), Context.startForegroundService() не вызывал Service.startForeground(): ServiceRecord

У меня есть потоковое приложение, которым пользуются почти миллион человек. Я использую службу переднего плана для игрока. Я еще не реализовал MediaSession. У меня 99,95% сеансов без сбоев. Таким образом, это приложение работает на всех версиях, но я начал получать отчеты о сбоях (ANR) с Android 9. Этот сбой происходит только на Samsung телефонах, особенно на s9, s9+, s10, s10+, note9 моделях.

Я попробовал эти,

  • Вызов метода startForeground() в onCreate()
  • Звонок Service.startForeground() до Context.stopService()
  • Другие ответы stackoverflow на похожие вопросы

Я читал некоторые комментарии от разработчиков Google, они сказали, что это просто Intended Behavior. Интересно, это произошло в системе Samsung или в ОС Android? У кого-нибудь есть мнение по этому поводу? Как я могу это исправить?

Существуют ли какие-либо пути, по которым startForeground нельзя вызывать? Или пути, по которым основной поток застопорился/спит/выполняет слишком много работы и не может обработать запуск службы?

Gabe Sechan 28.04.2019 23:20

Это радио/музыкальное приложение, оно просто воспроизводит URL потока. У меня не было сбоев на моем телефоне. Некоторые пользователи говорят, что сбой происходит при прослушивании музыки в фоновом режиме.

Beyazid 28.04.2019 23:26

Я не удивлюсь, если есть шанс, что это произойдет с идеально написанным приложением, поскольку нет гарантии, что ваш код будет вызван вовремя. В то время как отдельные фрагменты кода в этот ответ представляют собой беспорядок, обходной путь, похоже, обещает: запустить службу через привязку, переместить ее на передний план, тогда использовать startForegroundService() и отменить привязку через какое-то время.

CommonsWare 28.04.2019 23:28

Верно. У вас есть короткое окно после запуска службы переднего плана через startForegroundService для вызова службы startForeground I. По какой-то причине это происходит недостаточно быстро на этих устройствах.

Gabe Sechan 28.04.2019 23:29

Если это локализовано только для устройств Samsung, это может означать, что у них есть ошибка в их адаптированной версии (версиях) AOSP для Android 9, а не в вашем коде. Любые обходные пути будут чистой догадкой, пока это не будет точно воспроизведено.

Mark 29.04.2019 01:29

Спасибо за ваши ответы @CommonsWare, GabeSechan, Марк Кин. Эти подходы на самом деле не работали для меня. Я ждал большего процента использования, чтобы написать сюда. Я исправил эту проблему, и я больше не получаю этот сбой. Я поделюсь своей реализацией в качестве ответа

Beyazid 09.05.2019 21:48

Привет, Беязид, если у вас есть какой-нибудь совет для еще одного отчаянного разработчика, мы будем очень рады. Устройства Samsung только на моей стороне, а также. Перепробовал много методов, ни один из них не работает, сумасшедший ... спасибо за возможный совет.

Menion Asamm 27.05.2019 20:37

Привет @MenionAsamm, я согласен, это действительно неприятная проблема. Я поделился своей реализацией.

Beyazid 28.05.2019 12:27

Я также наблюдал это на Huawei P20 Lite (с Android 9).

Alix 23.01.2020 16:50

Привет, ребята, спасибо за ваши вопросы и мнения. Но я сменил компанию и уже год не работаю над этим или подобными проектами. Я поделился решением, которое сработало для меня, но я точно не помню подход. Возможно, это не лучший подход, но он был проработан для проекта. Лучший.

Beyazid 23.05.2020 21:02
25
10
21 007
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

После того, как у меня возникла такая же проблема с теми же телефонами, я внес несколько изменений, и сбои исчезли. Я не уверен, что сделал трюк, но я предполагаю, что вызов startForeground как в onCreate, так и в onStartCommand. Я не уверен, зачем это нужно, если служба уже запущена и в onCreate все вызывалось правильно.

Другие изменения: - Изменение serviceId на какое-то меньшее число (1-10) - Вызов startFororegroundService реже через одноэлементный синхронный класс (ранее это было реализовано при сбоях, чтобы предотвратить остановку службы до ее запуска с обратным вызовом из onStartCommand, но теперь он также фильтрует вызовы, если служба уже запущена). - с помощью START_REDELIVER_INTENT (ни на что не должно влиять)

Проблема возникает на упомянутых телефонах только у некоторых пользователей, поэтому я подозреваю, что она связана с каким-то новым обновлением от Samsung и в конечном итоге будет исправлена.

вам нужно startForeground onStartCommand для случая вызова Context.startForegroundService(), когда Сервис уже создан.

Malachiasz 24.08.2021 07:42
Ответ принят как подходящий

Я ждал отчета о сбое, чтобы поделиться решением. У меня не было сбоев или ANR почти 20 дней. Я хочу поделиться своим решением. Это может помочь тем, кто столкнулся с этой проблемой.

В методе onCreate()

  • Прежде всего, мое приложение является медиа-приложением. Я еще не реализовал медиасессию. Я создаю канал уведомлений в верхней части onCreate(). Официальный документ
  • Я вызываю метод Service.startForeground() после метода Context.startForegroundService(). По моему prepareAndStartForeground() методу.

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

По этой причине я вручную добавил ту же функцию в свой класс обслуживания вместо вызова 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
}

onRebind, onBind, onUnbind методы, подобные этим

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
}

Я надеюсь, что это решение работает должным образом для вас.

Спасибо, что поделились своей реализацией. Кажется, у вас есть соединение на основе привязки. Возможно, это может быть причиной основного различия, почему в остальном одинаковое решение работает для вас. Спасибо, что поделился!

Menion Asamm 28.05.2019 23:10

Привет, любая конкретная причина для "я не знаю почему, но ContextCompat.startForegroundService() не работает должным образом".

ingsaurabh 31.05.2019 07:18

Привет, не могли бы вы уточнить, как вы создаете эту услугу? потому что метод службы onCreate вызывается после вызова: ContextCompat.startForegroundService(контекст, намерение), и здесь вы вызываете: startForegroundService для вашей службы onCreate.

CookieMonster 03.06.2019 10:11

@ingsaurabh На самом деле, я не знаю, потому что я не видел никакой разницы в журналах или каких-либо сбоев. Я могу только сказать, что ContextCompat.startForegroundService() не работает для моего кода :)

Beyazid 13.06.2019 19:41

@CookieMonster startForegroundService — это метод, вызывающий context.startForegroundService. Вы можете увидеть тот же вызов внутри ContextCompat.startForegroundService(контекст, намерение)

Beyazid 13.06.2019 19:46

@Beyazid, спасибо за ответ, но я не уверен, что понимаю. Является ли метод onCreate в вашем примере onCreate MusicService? потому что если это так, похоже, вы дважды вызываете startForegroundService. один раз сформируйте другое место в приложении (чтобы «запустить» службу onCreate) и второй раз в службе onCreate.

CookieMonster 16.06.2019 10:30

Привет, @Beyazid, проблема решена и больше никогда не отображается в отчете о сбое? Спасибо

thecr0w 12.11.2019 10:27

@ Beyazid Я тоже в замешательстве. пожалуйста, прокомментируйте ниже, где находится метод «onCreate ()». вызов службы в onCreate Method of Service для меня не имеет смысла

aguagu 02.12.2019 08:29

@Beyazid, не могли бы вы ответить на комментарий CookieMonster

Giru Bhai 20.03.2020 13:09
ContextCompat.startForegroundService работает исправно
IgorGanapolsky 10.04.2020 21:54

Ответ не ясен. Ваш метод onCreate из какого класса, вы вызываете startForegroundService из onCreate той же службы?

Gaurav Singla 20.05.2020 17:36

Привет, ребята, спасибо за ваши вопросы и мнения. Но я сменил компанию и уже год не работаю над этим или подобными проектами. Эти строки кода у меня работали, но я точно не помню подход. Возможно, это не лучший подход, но он был проработан для проекта. Лучший.

Beyazid 23.05.2020 20:58

Тогда это не должен быть принятый лучший ответ, так как автор пишет "но я точно не помню подход. Наверное, это не лучший подход". Логику в этом примере кода проследить очень сложно.

Mobile Visuals 14.01.2022 08:25

@mobile-visuals Никто не претендует на звание лучшего ответа. Я задал вопрос и тоже ответил на него. Это было решением для меня. Я принял это, поэтому это принятый ответ :)

Beyazid 14.01.2022 09:38

Компонент 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
}

Всегда возвращайте START_STICKY в свой Service. Все остальное, вероятно, неправильно, особенно если вы делаете какой-либо аудиоплеер. На самом деле, если вы делаете аудиоплеер, вам не следует реализовывать свою собственную службу, а вместо этого использовать MediaBrowserServiceCompat (из AndroidX).

Я также рекомендую сообщения в блоге, которые я написал по этому поводу: https://hellsoft.se/how-to-service-on-android-part-3-1e24113152cd

Всегда возвращайте START_STICKY в свой Сервис. Все остальное, вероятно, неправильно это довольно самонадеянно, не могли бы вы объяснить?
Tim 30.11.2020 19:09

@Tim Пожалуйста, прочитайте сообщение, на которое я ссылаюсь. В этой серии я объясню, почему START_STICKY — это то, что вы должны использовать сегодня. Есть конечно исключения, но в 99% случаев, когда вам нужен Service, это правильный способ сделать это.

Erik Hellman 02.12.2020 08:39

Я почти устранил проблему с startForeground() в методах MediaSessionCompat.Callback, таких как onPlay(), onPause().

После слишком долгой борьбы с этим сбоем, наконец, я полностью исправил это исключение и нашел решение.

Убедитесь, что вы сделали это в своем сервисе. Я перечисляю их, как показано ниже: (некоторые из этих вещей повторяются, как упоминалось в другом ответе, я просто пишу их снова).

1- Звонок

startForeground()

как в onCreate, так и в onStartCommand (можно много раз вызывать 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()

, нет необходимости вызывать остановить передний план () или остановить себя ().

  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- Если в вашей службе есть действия (нужны ожидающие намерения), обрабатывайте ваши ожидаемое намерение с помощью Вещательный приемник, а не вашей текущей службы (она вызовет вашу службу в 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- Установите для идентификатор уведомления длинное число, например 121412.

7- Использование NotificationCompat.Builder будет обрабатывать разные версии API, вам просто нужно создать канал уведомлений для версий сборки> = Build.VERSION_CODES.O. (Это не решение, просто сделайте ваш код более читабельным)

8- Добавить

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

разрешение в ваш манифест. (этот упоминается в документах Android) Служба переднего плана Android

Надеюсь, поможет :))

Почему мы должны делать это и в onCreate, и в onStartCommand?

IgorGanapolsky 10.04.2020 21:54

@Sepehr, что такое MAIN_SERVICE_COMMAND_KEY?

WISHY 27.08.2021 10:20

оу, рад видеть тебя здесь, Сепер, после долгого времени... спасибо, для меня это работает как шарм, добавив startForegroundService в onStartCommand.

imansdn 15.09.2021 11:32

В соответствии с этим сообщением об ошибке это говорит о том, что когда вы вызываете Context.startForegroundService(), вы должны выдать уведомление, используя метод Service.startForeground(). Это то, что я понимаю.

Я потратил так много времени, выясняя, почему он падает, и не понимая, почему. Проблема заключалась в идентификаторе уведомления для startForeground(). Измените его на значение, отличное от 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;
    }

Другие вопросы по теме