Context.startForegroundService () тогда не вызывал Service.startForeground ()

258

Я использую ServiceClass на ОС Android O.

Я планирую использовать Serviceв фоновом режиме.

Документация Android гласит, что

Если ваше приложение предназначено для уровня API 26 или выше, система накладывает ограничения на использование или создание фоновых служб, если само приложение не находится на переднем плане. Если приложение должно создать службу переднего плана, приложение должно вызвать startForegroundService().

Если вы используете startForegroundService(), Serviceвыдает следующую ошибку.

Context.startForegroundService() did not then call
Service.startForeground() 

Что с этим не так?

Хороший парень
источник
Пожалуйста, предоставьте минимальный воспроизводимый пример . Это будет включать в себя всю трассировку стека Java и код, который вызывает сбой.
CommonsWare
3
Ошибка все еще здесь в API 26 и 27 (27.0.3). Подвергнутые уязвимости версии для Android 8.0 и 8.1 Вы можете уменьшить количество сбоев, добавив startForeground () как в onCreate (), так и в onStartCommand (), но сбои все еще будут происходить для некоторых пользователей. Единственный способ исправить это atm - targetSdkVersion 25 в вашем build.gradle.
Алекс
1
К вашему сведению Issueetracker.google.com/issues/76112072
v1k
1
Я использую этот обходной путь сейчас. работает как очарование theandroiddeveloper.com/blog/…
Прасад Павар
1
У меня та же проблема. Я исправляю эту проблему. Я поделился своей реализацией в этой теме stackoverflow.com/questions/55894636/…
Beyazid

Ответы:

99

Из документации Google на Android 8.0 меняется поведение :

Система позволяет приложениям вызывать Context.startForegroundService (), даже когда приложение находится в фоновом режиме. Однако приложение должно вызвать метод startForeground () этой службы в течение пяти секунд после ее создания.

Решение: Вызов startForeground()в onCreate()для Serviceкоторой вы используетеContext.startForegroundService()

Смотрите также: Пределы выполнения фона для Android 8.0 (Oreo)

zhuzhumouse
источник
19
Я сделал это в onStartCommandметоде, но я все еще получаю эту ошибку. Я позвонил startForegroundService(intent)в мой MainActivity. Возможно, служба запущена слишком медленно. Я думаю, что ограничение в пять секунд не должно существовать, прежде чем они смогут пообещать, что служба запущена немедленно.
Кими Чиу
29
Периода в 5 секунд явно недостаточно, это исключение случается очень часто в сеансах отладки. Я подозреваю, что это также происходило бы время от времени в режиме выпуска. И будучи ФАТАЛЬНЫМ ИСКЛЮЧЕНИЕМ, это просто вылетает из приложения! Я пытался поймать его с помощью Thread.setDefaultUncaughtExceptionHandler (), но даже при получении его и игнорировании Android приложение зависает. Это происходит потому, что это исключение вызывается из handleMessage (), и основной цикл фактически заканчивается ... в поисках обходного пути.
Саутертон
Я вызвал метод Context.startForegroundService () в широковещательном приемнике. так как справиться в этой ситуации? потому что onCreate () не доступен в широковещательном приемнике
Anand Savjani
6
@southerton Я думаю, вы должны onStartCommand()вместо этого вызывать его onCreate, потому что если вы закроете сервис и запустите его снова, он может onStartCommand()обойтись без вызова onCreate...
Android-разработчик
1
Мы можем проверить ответ от команды Google здесь Issueetracker.google.com/issues/76112072#comment56
Прагс
82

Я позвонил, ContextCompat.startForegroundService(this, intent)чтобы начать службу тогда

В сервисе onCreate

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

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("").build();

            startForeground(1, notification);
        }
}
humazed
источник
48
Я тоже. Но я все еще иногда получаю эту ошибку. Возможно, Android не может гарантировать, что он позвонит через onCreate5 секунд. Таким образом, они должны изменить дизайн, прежде чем они заставят нас следовать правилам.
Кими Чиу
5
Я вызывал startForeground в onStartCommand () и иногда получал эту ошибку. Я переместил его в onCreate и с тех пор не видел (скрестив пальцы).
Тайлер
4
В Oreo создается локальное уведомление о том, что «APP_NAME запущено. Нажмите, чтобы закрыть или просмотреть информацию». Как перестать показывать это уведомление?
Artist404
6
Нет, команда Android утверждала, что это намеренное поведение. Поэтому я просто переработал свое приложение. Это смешно. Всегда нужно переделывать приложения из-за такого «намеченного поведения». Это уже третий раз.
Кими Чиу
12
Основной причиной этой проблемы является то, что служба была остановлена ​​до того, как она была переведена на передний план. Но утверждение не прекратилось после уничтожения службы. Вы можете попытаться воспроизвести это, добавив StopServiceпосле звонка startForegroundService.
Кими Чиу
55

Эта проблема возникает из-за того, что платформа Android не может гарантировать, что ваш сервис будет запущен в течение 5 секунд, но, с другой стороны, среда имеет строгие ограничения на уведомление переднего плана, которое должно быть запущено в течение 5 секунд, без проверки, попытался ли каркас запустить сервис ,

Это определенно является проблемой среды, но не все разработчики, сталкивающиеся с этой проблемой, делают все возможное:

  1. startForegroundуведомление должно быть в обоих onCreateи onStartCommand, потому что , если ваша служба уже создана и как - то ваша деятельность пытается запустить его снова, onCreateне будем называть.

  2. Идентификатор уведомления не должен быть 0, иначе произойдет тот же сбой, даже если это не та же самая причина.

  3. stopSelfне должен быть вызван раньше startForeground.

Со всем вышеперечисленным 3 эта проблема может быть немного уменьшена, но все же не является исправлением, реальное исправление или, скажем так, обходной путь - понизить целевую версию SDK до 25.

И обратите внимание, что, скорее всего, Android P все еще будет нести эту проблему, потому что Google отказывается даже понимать, что происходит, и не верит, что это их вина, прочитайте №36 и №56 для получения дополнительной информации.

ZhouX
источник
9
Почему и onCreate, и onStartCommand? Можете ли вы просто положить его в OnStartCommand?
rcell
stopSelf нельзя вызывать перед startForeground => или сразу после, потому что, кажется, он отменяет startForeground, когда вы это делаете.
Фрэнк
1
Я выполнил несколько тестов, и даже если onCreate не вызывается, если служба уже создана, вам не нужно снова вызывать startForeground. Следовательно, вы можете вызывать его только для метода onCreate, а не для onStartCommand. Вероятно, они считают, что единственный экземпляр службы находится на переднем плане после первого вызова до его завершения.
Lxu
Я использую 0 в качестве идентификатора уведомления для первоначального вызова startForeground. Изменение его на 1 устраняет проблему для меня (надеюсь)
mr5
Так в чем же решение?
Игорь Ганапольский
35

Я знаю, слишком много ответов уже опубликовано, однако правда в том, что startForegroundService нельзя исправить на уровне приложения, и вы должны прекратить его использовать. Рекомендация Google использовать API Service # startForeground () в течение 5 секунд после вызова Context # startForegroundService () - это не то, что приложение всегда может сделать.

Android запускает множество процессов одновременно, и нет никакой гарантии, что Looper вызовет вашу целевую службу, которая должна вызывать startForeground () в течение 5 секунд. Если ваша целевая служба не получила вызов в течение 5 секунд, вам не повезло, и ваши пользователи столкнутся с ситуацией ANR. В трассировке стека вы увидите что-то вроде этого:

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{1946947 u0 ...MessageService}

main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x763e01d8 self=0x7d77814c00
  | sysTid=11171 nice=-10 cgrp=default sched=0/0 handle=0x7dfe411560
  | state=S schedstat=( 1337466614 103021380 2047 ) utm=106 stm=27 core=0 HZ=100
  | stack=0x7fd522f000-0x7fd5231000 stackSize=8MB
  | held mutexes=
  #00  pc 00000000000712e0  /system/lib64/libc.so (__epoll_pwait+8)
  #01  pc 00000000000141c0  /system/lib64/libutils.so (android::Looper::pollInner(int)+144)
  #02  pc 000000000001408c  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+60)
  #03  pc 000000000012c0d4  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
  at android.os.MessageQueue.nativePollOnce (MessageQueue.java)
  at android.os.MessageQueue.next (MessageQueue.java:326)
  at android.os.Looper.loop (Looper.java:181)
  at android.app.ActivityThread.main (ActivityThread.java:6981)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1445)

Как я понимаю, Лупер проанализировал здесь очередь, нашел «обидчика» и просто убил ее. Теперь система счастлива и здорова, а разработчики и пользователи - нет, но, поскольку Google ограничивает свои обязанности системой, почему они должны заботиться о последних двух? Видимо, нет. Могут ли они сделать это лучше? Конечно, например, они могли бы обслуживать диалог «Приложение занято», предлагая пользователю принять решение о том, ждать или убить приложение, но зачем это беспокоить, это не их обязанность. Главное, что система сейчас здорова.

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

В этом потоке было хорошее предложение использовать «bind» вместо «start», а затем, когда служба готова, обрабатывать onServiceConnected, но, опять же, это означает, что вообще не следует использовать вызовы startForegroundService.

Я думаю, что правильным и честным действием со стороны Google было бы сказать всем, что startForegourndServcie имеет недостаток и не должен использоваться.

Остается вопрос: что использовать вместо этого? К счастью для нас, есть JobScheduler и JobService, которые являются лучшей альтернативой для приоритетных сервисов. Это лучший вариант, потому что:

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

Это означает, что вам больше не нужно заботиться об обработке wakelocks, и поэтому он ничем не отличается от приоритетных сервисов. С точки зрения реализации, JobScheduler - это не ваша служба, а системная, предположительно, она будет правильно обрабатывать очередь, и Google никогда не прекратит свой собственный дочерний элемент :)

Samsung перешла от startForegroundService к JobScheduler и JobService в своем дополнительном протоколе Samsung (SAP). Это очень полезно, когда такие устройства, как умные часы, должны общаться с такими хостами, как телефоны, где работа должна взаимодействовать с пользователем через главный поток приложения. Поскольку задания публикуются планировщиком в главном потоке, это становится возможным. Тем не менее, вы должны помнить, что задание выполняется в главном потоке, и перенести весь тяжелый материал в другие потоки и асинхронные задачи.

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

Единственная ловушка перехода на JobScheduler / JobService заключается в том, что вам нужно будет реорганизовать старый код, и это не весело. Последние два дня я потратил именно на это, чтобы использовать новую реализацию SAP от Samsung. Я посмотрю свои отчеты о сбоях и сообщу, увидит ли снова сбой. Теоретически этого не должно происходить, но всегда есть детали, о которых мы могли бы не знать.

ОБНОВЛЕНИЕ Больше не сообщает о сбоях в Play Store. Это означает, что JobScheduler / JobService не имеют такой проблемы, и переключение на эту модель является правильным подходом, чтобы избавиться от проблемы startForegroundService раз и навсегда. Надеюсь, Google / Android его прочитает и в конечном итоге прокомментирует / посоветует / предоставит официальное руководство для всех.

ОБНОВЛЕНИЕ 2

Для тех, кто использует SAP и спрашивает, как SAP V2 использует JobService, объяснение ниже.

В вашем пользовательском коде вам нужно инициализировать SAP (это Kotlin):

SAAgentV2.requestAgent(App.app?.applicationContext, 
   MessageJobs::class.java!!.getName(), mAgentCallback)

Теперь вам нужно декомпилировать код Samsung, чтобы увидеть, что происходит внутри. В SAAgentV2 взгляните на реализацию requestAgent и следующую строку:

SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);

where d defined as below

private SAAdapter d;

Теперь перейдите в класс SAAdapter и найдите функцию onServiceConnectionRequested, которая планирует задание, используя следующий вызов:

SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12); 

SAJobService - это всего лишь реализация Android JobService, и именно она выполняет планирование работы:

private static void a(Context var0, String var1, String var2, long var3, String var5, SAPeerAgent var6) {
    ComponentName var7 = new ComponentName(var0, SAJobService.class);
    Builder var10;
    (var10 = new Builder(a++, var7)).setOverrideDeadline(3000L);
    PersistableBundle var8;
    (var8 = new PersistableBundle()).putString("action", var1);
    var8.putString("agentImplclass", var2);
    var8.putLong("transactionId", var3);
    var8.putString("agentId", var5);
    if (var6 == null) {
        var8.putStringArray("peerAgent", (String[])null);
    } else {
        List var9;
        String[] var11 = new String[(var9 = var6.d()).size()];
        var11 = (String[])var9.toArray(var11);
        var8.putStringArray("peerAgent", var11);
    }

    var10.setExtras(var8);
    ((JobScheduler)var0.getSystemService("jobscheduler")).schedule(var10.build());
}

Как видите, последняя строка здесь использует Android'd JobScheduler, чтобы получить эту системную службу и запланировать работу.

В вызове requestAgent мы передали mAgentCallback, который является функцией обратного вызова, которая получит контроль, когда происходит важное событие. Вот как обратный вызов определяется в моем приложении:

private val mAgentCallback = object : SAAgentV2.RequestAgentCallback {
    override fun onAgentAvailable(agent: SAAgentV2) {
        mMessageService = agent as? MessageJobs
        App.d(Accounts.TAG, "Agent " + agent)
    }

    override fun onError(errorCode: Int, message: String) {
        App.d(Accounts.TAG, "Agent initialization error: $errorCode. ErrorMsg: $message")
    }
}

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

class MessageJobs (context:Context) : SAAgentV2(SERVICETAG, context, MessageSocket::class.java) {


    public fun release () {

    }


    override fun onServiceConnectionResponse(p0: SAPeerAgent?, p1: SASocket?, p2: Int) {
        super.onServiceConnectionResponse(p0, p1, p2)
        App.d(TAG, "conn resp " + p1?.javaClass?.name + p2)


    }

    override fun onAuthenticationResponse(p0: SAPeerAgent?, p1: SAAuthenticationToken?, p2: Int) {
        super.onAuthenticationResponse(p0, p1, p2)
        App.d(TAG, "Auth " + p1.toString())

    }


    override protected fun onServiceConnectionRequested(agent: SAPeerAgent) {


        }
    }

    override fun onFindPeerAgentsResponse(peerAgents: Array<SAPeerAgent>?, result: Int) {
    }

    override fun onError(peerAgent: SAPeerAgent?, errorMessage: String?, errorCode: Int) {
        super.onError(peerAgent, errorMessage, errorCode)
    }

    override fun onPeerAgentsUpdated(peerAgents: Array<SAPeerAgent>?, result: Int) {

    }

}

Как видите, MessageJobs также требует класс MessageSocket, который вам необходимо реализовать, и который обрабатывает все сообщения, приходящие с вашего устройства.

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

Олег Гриб
источник
Хороший ответ, но есть серьезная проблема: он JobIntentServiceзапускается сразу как IntentServiceOreo ниже, но планирует работу на Oreo и выше, поэтому JobIntentServiceне запускается сразу. Больше информации
CopsOnRoad
1
@ CopsOnRoad Это очень полезно, и оно начинается сразу же в моем случае. Как я уже писал, он используется для взаимодействия в реальном времени между телефоном и умными часами. Ни в коем случае пользователь не будет ждать 15 минут, чтобы отправить данные между этими двумя. Работает очень хорошо и никогда не падает.
Олег Гриб
1
Звучит круто, если бы вы могли поделиться некоторыми примерами кода, это принесло бы вам много пользы, я новичок в Android и обнаружил, что Job...для запуска требуется время, и это предварительная версия AlarmManager.
CopsOnRoad
2
Вероятно, я сделаю это, когда позволит время: мне нужно
Олег Гриб
1
@batmaci - JobService используется SAP для внутренних целей. Смотрите Обновление 2 в OP для деталей.
Олег Гриб
32

Ваше приложение будет зависать, если вы позвоните, Context.startForegroundService(...)а затем вызовите Context.stopService(...)до вызова Service.startForeground(...).

У меня есть четкое воспроизведение здесь ForegroundServiceAPI26

Я открыл ошибку по этому адресу: Google tracker tracker

Несколько ошибок по этому вопросу были открыты и закрыты не исправят.

Надеюсь, мой с четкими действиями repro сделает разрез.

Информация предоставлена ​​командой Google

Отслеживание проблем Google Комментарий 36

Это не ошибка фреймворка; это намеренно. Если приложение запускает экземпляр службы с помощью startForegroundService(), оно должно перевести этот экземпляр службы в состояние переднего плана и показать уведомление. Если экземпляр службы останавливается до того, как startForeground()вызывается на нем, это обещание не выполняется: это ошибка в приложении.

Что касается публикации № 31 , публикация Службы, которую другие приложения могут запускать напрямую, в принципе небезопасна. Вы можете немного смягчить это, рассматривая все начальные действия этой службы как требующие startForeground(), хотя, очевидно, это может не соответствовать вашим ожиданиям.

Google tracker track Комментарий 56

Есть несколько различных сценариев, которые приводят к одному и тому же результату.

Непосредственная семантическая проблема, это просто ошибка, с которой можно что-то начать, startForegroundService()но пренебрежение фактическим переводом на передний план черезstartForeground() , - это просто семантическая проблема. Это преднамеренно рассматривается как ошибка приложения. Остановка службы перед ее переносом на передний план - ошибка приложения. В этом суть ОП, и именно поэтому этот вопрос был помечен как «работающий по назначению».

Однако есть и вопросы о ложном обнаружении этой проблемы. Вот это время рассматриваются как реальная проблема, хотя это отслеживается отдельно от этого конкретного вопроса ошибки трекера. Мы не глухи к жалобе.

swooby
источник
2
Лучшее обновление, которое я видел, это issetracker.google.com/issues/76112072#comment56 . Я переписал свой код для использования Context.bindService, что полностью исключает проблемный вызов Context.startForegroundService. Вы можете увидеть мой пример кода в github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/...
swooby
да, как я уже писал, bind должен работать, но я уже переключился на JobServive и не собираюсь ничего менять, пока этот тоже не сломается »:)
Олег Гриб
20

Я исследовал это в течение нескольких дней и получил решение. Теперь в Android O вы можете установить ограничение фона, как показано ниже

Служба, которая вызывает класс обслуживания

Intent serviceIntent = new Intent(SettingActivity.this,DetectedService.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    SettingActivity.this.startForegroundService(serviceIntent);
} else {
    startService(serviceIntent);
}

и класс обслуживания должен быть как

public class DetectedService extends Service { 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        int NOTIFICATION_ID = (int) (System.currentTimeMillis()%10000);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForeground(NOTIFICATION_ID, new Notification.Builder(this).build());
        }


        // Do whatever you want to do here
    }
}
Ахмад Арслан
источник
3
Вы пробовали это в каком-то приложении, которое имеет по крайней мере несколько тысяч пользователей? Я пытался, у некоторых пользователей все еще есть проблемы сбоя.
Алекс
Нет-нет, я использую точный код и не вижу сбоя клиентов.
Ахмад Арслан
Сколько у тебя пользователей? Я видел ~ 10-30+ сбоев ежедневно (с ошибкой) в приложении, в котором было 10 тысяч пользователей в день. Конечно, это происходит только для пользователей с android 8.0 и android 8.1, в случае если targetSdkVersion равен 27. Все проблемы исчезают до 0 сбоев сразу после того, как я установил targetSdkVersion равным 25.
Alex
только 2200 пользователей: - / но не получилось ни одного сбоя
Ахмад Арслан
1
@ Джива, не совсем. Atm, я использую targetSdkVersion 25 с compileSdkVersion 27. Это выглядит наилучшим образом после многих экспериментов .... Надеюсь, они закончат developer.android.com/topic/libraries/architecture/… до августа 2018 года, потому что android-developers.googleblog .com / 2017/12 /…
Алекс
16

У меня есть виджет, который делает относительно частые обновления, когда устройство не работает, и я видел тысячи аварий за несколько дней.

Триггер проблемы

Я даже заметил проблему даже на моем Pixel 3 XL, когда я не думал, что устройство вообще сильно загружено. И все пути кода были покрыты startForeground(). Но потом я понял, что во многих случаях мой сервис выполняет работу очень быстро.Я полагаю, что триггер для моего приложения заключался в том, что служба заканчивала работу, прежде чем система успела показать уведомление.

Обходной путь / решение

Я смог избавиться от всех сбоев. Что я сделал, чтобы удалить вызов stopSelf(). (Я думал о том, чтобы отложить остановку, пока я не был уверен, что уведомление было показано, но я не хочу, чтобы пользователь видел уведомление, если оно не нужно.) Когда служба простаивала в течение минуты или система уничтожает его как обычно без каких-либо исключений.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    stopForeground(true);
} else {
    stopSelf();
}
Рой Солберг
источник
11

Просто один на один, потому что я потратил слишком много часов на это. Я продолжал получать это исключение, хотя я и звонил startForeground(..)первым делом onCreate(..). В конце концов я обнаружил, что проблема была вызвана использованием NOTIFICATION_ID = 0. Использование любого другого значения, кажется, исправит это.

tobalr
источник
В моем случае я использовал ID 1, спасибо, проблема была решена
Амин Пинджари
4
Я использую ID 101, но иногда я получаю эту ошибку.
CopsOnRoad
9

У меня есть решение этой проблемы. Я проверил это исправление в своем собственном приложении (300 КБ + DAU), которое может уменьшить как минимум 95% таких аварий, но все еще не может на 100% избежать этой проблемы.

Эта проблема возникает, даже если вы уверены, что вызываете startForeground () сразу после запуска службы, как это задокументировано Google. Это может быть связано с тем, что процесс создания и инициализации службы во многих сценариях уже стоит более 5 секунд, а затем независимо от того, когда и где вы вызываете метод startForeground (), этот сбой неизбежен.

Мое решение состоит в том, чтобы гарантировать, что startForeground () будет выполняться в течение 5 секунд после метода startForegroundService (), независимо от того, сколько времени необходимо создать и инициализировать ваш сервис. Вот подробное решение.

  1. Не используйте startForegroundService в первую очередь, используйте bindService () с флагом auto_create. Он будет ждать инициализации сервиса. Вот код, мой пример сервиса MusicService:

    final Context applicationContext = context.getApplicationContext();
    Intent intent = new Intent(context, MusicService.class);
    applicationContext.bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            if (binder instanceof MusicBinder) {
                MusicBinder musicBinder = (MusicBinder) binder;
                MusicService service = musicBinder.getService();
                if (service != null) {
                    // start a command such as music play or pause.
                    service.startCommand(command);
                    // force the service to run in foreground here.
                    // the service is already initialized when bind and auto_create.
                    service.forceForeground();
                }
            }
            applicationContext.unbindService(this);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }, Context.BIND_AUTO_CREATE);
  2. Тогда вот реализация MusicBinder:

    /**
     * Use weak reference to avoid binder service leak.
     */
     public class MusicBinder extends Binder {
    
         private WeakReference<MusicService> weakService;
    
         /**
          * Inject service instance to weak reference.
          */
         public void onBind(MusicService service) {
             this.weakService = new WeakReference<>(service);
         }
    
         public MusicService getService() {
             return weakService == null ? null : weakService.get();
         }
     }
  3. Самая важная часть, реализация MusicService, метод forceForeground () обеспечит вызов метода startForeground () сразу после startForegroundService ():

    public class MusicService extends MediaBrowserServiceCompat {
    ...
        private final MusicBinder musicBind = new MusicBinder();
    ...
        @Override
        public IBinder onBind(Intent intent) {
            musicBind.onBind(this);
            return musicBind;
        }
    ...
        public void forceForeground() {
            // API lower than 26 do not need this work around.
            if (Build.VERSION.SDK_INT >= 26) {
                Intent intent = new Intent(this, MusicService.class);
                // service has already been initialized.
                // startForeground method should be called within 5 seconds.
                ContextCompat.startForegroundService(this, intent);
                Notification notification = mNotificationHandler.createNotification(this);
                // call startForeground just after startForegroundService.
                startForeground(Constants.NOTIFICATION_ID, notification);
            }
        }
    }
  4. Если вы хотите запустить фрагмент кода шага 1 в ожидающем намерении, например, если вы хотите запустить службу переднего плана в виджете (нажатие кнопки виджета), не открывая свое приложение, вы можете заключить фрагмент кода в приемник вещания. и запустить широковещательное событие вместо команды запуска службы.

Вот и все. Надеюсь, поможет. Удачи.

Hexise
источник
8

Эта ошибка также возникает в Android 8+, когда Service.startForeground (int id, Notificationtification) вызывается, когда id установлен в 0.

id int: идентификатор для этого уведомления согласно NotificationManager.notify (int, Notification); не должно быть 0 .

almisoft
источник
3
Не используйте также более высокие номера для идентификатора уведомления. Попробуйте использовать однозначные идентификаторы. stackoverflow.com/a/12228555/2527204
Марлон
7

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

Вопрос в том, чтобы позвонить Service.startForeground(id, notification)из самой службы, верно? К сожалению, Android Framework не гарантирует звонка в Service.startForeground(id, notification)течение Service.onCreate()5 секунд, но в любом случае выдает исключение, поэтому я придумала этот способ.

  1. Привязать службу к контексту с помощью связующего из службы перед вызовом Context.startForegroundService()
  2. Если привязка прошла успешно, позвоните Context.startForegroundService() из сервисного соединения и немедленно позвоните Service.startForeground() в сервисное соединение.
  3. ВАЖНО: Вызов Context.bindService()метода внутри попробовать прилов , потому что в некоторых случаях вызов может бросить исключение, в этом случае вы должны полагаться на призывая Context.startForegroundService()прямо и надеюсь , что это не удастся. Примером может быть контекст получателя широковещания, однако получение контекста приложения в этом случае не вызывает исключения, а использование контекста напрямую.

Это даже работает, когда я жду на точке останова после привязки сервиса и до запуска вызова startForeground. Ожидание в течение 3-4 секунд не вызывает исключение, в то время как через 5 секунд оно выдает исключение. (Если устройство не может выполнить две строки кода за 5 секунд, то пришло время выбросить это в корзину.)

Итак, начнем с создания сервисного соединения.

// Create the service connection.
ServiceConnection connection = new ServiceConnection()
{
    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
        // The binder of the service that returns the instance that is created.
        MyService.LocalBinder binder = (MyService.LocalBinder) service;

        // The getter method to acquire the service.
        MyService myService = binder.getService();

        // getServiceIntent(context) returns the relative service intent 
        context.startForegroundService(getServiceIntent(context));

        // This is the key: Without waiting Android Framework to call this method
        // inside Service.onCreate(), immediately call here to post the notification.
        myService.startForeground(myNotificationId, MyService.getNotification());

        // Release the connection to prevent leaks.
        context.unbindService(this);
    }

    @Override
    public void onBindingDied(ComponentName name)
    {
        Log.w(TAG, "Binding has dead.");
    }

    @Override
    public void onNullBinding(ComponentName name)
    {
        Log.w(TAG, "Bind was null.");
    }

    @Override
    public void onServiceDisconnected(ComponentName name)
    {
        Log.w(TAG, "Service is disconnected..");
    }
};

Внутри вашего сервиса создайте связыватель, который возвращает экземпляр вашего сервиса.

public class MyService extends Service
{
    public class LocalBinder extends Binder
    {
        public MyService getService()
        {
            return MyService.this;
        }
    }

    // Create the instance on the service.
    private final LocalBinder binder = new LocalBinder();

    // Return this instance from onBind method.
    // You may also return new LocalBinder() which is
    // basically the same thing.
    @Nullable
    @Override
    public IBinder onBind(Intent intent)
    {
        return binder;
    }
}

Затем попробуйте связать сервис из этого контекста. Если это удастся, он вызовет ServiceConnection.onServiceConnected()метод из соединения службы, которое вы используете. Затем обработайте логику в коде, который показан выше. Пример кода будет выглядеть так:

// Try to bind the service
try
{
     context.bindService(getServiceIntent(context), connection,
                    Context.BIND_AUTO_CREATE);
}
catch (RuntimeException ignored)
{
     // This is probably a broadcast receiver context even though we are calling getApplicationContext().
     // Just call startForegroundService instead since we cannot bind a service to a
     // broadcast receiver context. The service also have to call startForeground in
     // this case.
     context.startForegroundService(getServiceIntent(context));
}

Кажется, он работает над приложениями, которые я разрабатываю, поэтому он должен работать, когда вы попробуете.

Фуркан Юрдакул
источник
5

Проблема с Android O API 26

Если вы сразу же остановите службу (так что ваша служба на самом деле не работает (формулировка / понимание) и вы находитесь в интервале ANR, вам все равно необходимо вызвать startForeground перед stopSelf).

https://plus.google.com/116630648530850689477/posts/L2rn4T6SAJ5

Пробовал этот подход, но он по-прежнему создает ошибку: -

if (Util.SDK_INT > 26) {
    mContext.startForegroundService(playIntent);
} else {
    mContext.startService(playIntent);
}

Я использую это, пока ошибка не будет устранена

mContext.startService(playIntent);
Энди Касс
источник
3
Должно быть Util.SDK_INT> = 26, а не просто больше
Андрис
4

Я столкнулся с той же проблемой, и, потратив время на поиск солутонов, вы можете попробовать код ниже. Если вы используете, Serviceпоместите этот код в OnCreate, иначе используйте Intent Serviceэтот код в OnHandleIntent.

if (Build.VERSION.SDK_INT >= 26) {
        String CHANNEL_ID = "my_app";
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                "MyApp", NotificationManager.IMPORTANCE_DEFAULT);
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("")
                .setContentText("").build();
        startForeground(1, notification);
    }
Самбхаджи Карад
источник
4

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

MyForegroundService.java

public class MyForegroundService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
    }
}

MainActivity.java

Intent serviceIntent = new Intent(this, MyForegroundService.class);
startForegroundService(serviceIntent);
...
stopService(serviceIntent);

Исключение выдается в следующем блоке кода:

ActiveServices.java

private final void bringDownServiceLocked(ServiceRecord r) {
    ...
    if (r.fgRequired) {
        Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                  + r);
        r.fgRequired = false;
        r.fgWaiting = false;
        mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
        mAm.mHandler.removeMessages(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        if (r.app != null) {
            Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
            msg.obj = r.app;
            msg.getData().putCharSequence(
                ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
            mAm.mHandler.sendMessage(msg);
         }
    }
    ...
}

Этот метод выполняется перед onCreate()из MyForegroundServiceпотому , что Android графиков создания службы на главном обработчика потока , но bringDownServiceLockedназывается на BinderThread, которым является состояние гонки. Это значит, что MyForegroundServiceне было возможности позвонить, startForegroundчто приведет к сбою.

Чтобы исправить это , мы должны убедиться , что bringDownServiceLockedне вызывается перед onCreate()из MyForegroundService.

public class MyForegroundService extends Service {

    private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";

    private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            context.removeStickyBroadcast(intent);
            stopForeground(true);
            stopSelf();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
        registerReceiver(
            stopReceiver, new IntentFilter(ACTION_STOP));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(stopReceiver);
    }

    public static void stop(Context context) {
        context.sendStickyBroadcast(new Intent(ACTION_STOP));
    }
}

С помощью липкой широковещательные мы уверены , что трансляция не заблудиться и stopReceiverполучает остановку намерения , как только оно было зарегистрировано в onCreate()из MyForegroundService. К этому времени мы уже позвонилиstartForeground(...) . Мы также должны удалить эту липкую трансляцию, чтобы в следующий раз не было уведомлено stopReceiver.

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

makovkastar
источник
Вы должны вызывать его вместо context.stopService (serviceIntent), когда хотите остановить службу.
Маковкастар
4

Так много ответов, но никто не работал в моем случае.

Я начал службу как это.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

И в моем сервисе в onStartCommand

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker Running")
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    } else {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker is Running...")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    }

И не забывайте устанавливать NOTIFICATION_ID ненулевым

private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
private static final int NOTIFICATION_ID = 555;

ТАК все было отлично, но все еще терпел крах на 8.1, причина была как ниже.

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            stopForeground(true);
        } else {
            stopForeground(true);
        }

Я вызвал остановку переднего плана с удалением уведомлений, но как только служба удаленных уведомлений стала фоновой, фоновая служба не может работать в Android O из фона. началось после толчка получил.

Так волшебное слово

   stopSelf();

До сих пор по любой причине, что ваш сервис не работает, следуйте всем вышеперечисленным шагам и наслаждайтесь

Сэнди
источник
if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.O) {stopForeground (true); } else {stopForeground (true); } что это, если еще для? в обоих случаях вы делали одно и то же, stopForeground (true);
user3135923
Я полагаю, вы хотели поставить stopSelf (); внутри блока else вместо stopForeground (true);
Джастин Стэнли,
1
Да, @JustinStanley use stopSelf ();
песчаный
Не должны startForegroundбыть вызваны onCreate?
Аба
Я использую NotificationManager, а не класс Notification. Как я могу это реализовать?
Prajwal W
3

https://developer.android.com/reference/android/content/Context.html#startForegroundService(android.content.Intent)

Аналогично startService (Intent), но с неявным обещанием, что служба вызовет startForeground (int, android.app.Notification), как только она начнет работать. Сервису предоставляется время, сравнимое с интервалом ANR, чтобы сделать это, иначе система автоматически остановит сервис и объявит ANR приложения.

В отличие от обычного startService (Intent), этот метод можно использовать в любое время, независимо от того, находится ли приложение, в котором размещается служба, в переднем плане.

убедитесь, что вы вызываете Service.startForeground(int, android.app.Notification)onCreate (), чтобы убедиться, что он будет вызываться ... если у вас есть какие-либо условия, которые могут помешать вам сделать это, то вам лучше использовать обычный Context.startService(Intent)и вызватьService.startForeground(int, android.app.Notification) себя.

Похоже, что Context.startForegroundService()добавляет сторожевой таймер, чтобы вы позвонили Service.startForeground(int, android.app.Notification)до того, как он был уничтожен ...

Альесио Карвалью
источник
3

Пожалуйста, не вызывайте никаких StartForgroundServices внутри метода onCreate () , вы должны вызывать сервисы StartForground в onStartCommand () после создания рабочего потока, в противном случае вы всегда будете получать ANR, поэтому не пишите сложное имя входа в основной поток onStartCommand () ;

public class Services extends Service {

    private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker Running")
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button","home button");
        } else {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker is Running...")
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button_value","home_button_value");

        }
        return super.onStartCommand(intent, flags, startId);

    }
}

РЕДАКТИРОВАТЬ: Осторожно! Функция startForeground не может принимать 0 в качестве первого аргумента, она вызовет исключение! этот пример содержит неверный вызов функции, измените 0 на свой собственный const, который не может быть 0 или больше, чем Max (Int32)

Шалу Гупта
источник
2

Даже после вызова startForegroundin Service, на некоторых устройствах происходит сбой, если мы вызываем stopServiceнепосредственно перед вызовом onCreate. Итак, я исправил эту проблему, запустив службу с дополнительным флагом:

Intent intent = new Intent(context, YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);

и добавил проверку в onStartCommand, чтобы увидеть, запущен ли он для остановки:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    //call startForeground first
    if (intent != null) {
        boolean stopService = intent.getBooleanExtra("request_stop", false);
        if (stopService) {
            stopSelf();
        }
    }

    //Continue with the background task
    return START_STICKY;
}

PS Если служба не запущена, она сначала запустит службу, что является издержками.

Гаурав Сингла
источник
2

Вы должны добавить разрешение, как показано ниже, для устройства Android 9 при использовании цели SDK 28 или более поздней версии, иначе всегда будет происходить исключение:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Shrdi
источник
1

Я просто проверяю PendingIntentноль или нет перед вызовом context.startForegroundService(service_intent)функции.

это работает для меня

PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_NO_CREATE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && pendingIntent==null){
            context.startForegroundService(service_intent);
        }
        else
        {
            context.startService(service_intent);
        }
}
SaimumIslam27
источник
Это на самом деле просто запускает службу для startService, а не startForegroundService, если ноль. Оператор if должен был бы быть вложенным, в противном случае приложение зависнет при попытке использовать службы в более поздней версии в какой-то момент.
a54studio
1

просто вызовите метод startForeground сразу после создания Service или IntentService. как это:

import android.app.Notification;
public class AuthenticationService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}
Хоссейн Карами
источник
1
ИСКЛЮЧИТЕЛЬНОЕ ИСКЛЮЧЕНИЕ: main android.app.RemoteServiceException: Плохое уведомление для startForeground: java.lang.RuntimeException: недопустимый канал для сервисного уведомления: Уведомление (channel = null pri = 0 contentView = null vibrate = null sound = null defaults = 0x0 flags = 0x40 color = 0x00000000 vis = PRIVATE) на android.app.ActivityThread $ H.handleMessage (ActivityThread.java:1821) на android.os.Handler.dispatchMessage (Handler.java:106) на android.os.Looper.loop (Looper. Java: 164) на android.app.ActivityThread.main (ActivityThread.java:6626)
Джерри
1

Хорошо, кое-что, что я заметил по этому поводу, может помочь и другим. Это строго от тестирования, чтобы увидеть, могу ли я выяснить, как исправить вхождения, которые я вижу. Для простоты, скажем, у меня есть метод, который вызывает это от докладчика.

context.startForegroundService(new Intent(context, TaskQueueExecutorService.class));

try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    e.printStackTrace();
}       

Это вылетит с той же ошибкой. Сервис НЕ запустится, пока метод не будет завершен, поэтому нет onCreate()в сервисе.

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

Это заставило меня задуматься: если бы я вызывал службу из потока в фоновом режиме, не мог ли он быть полностью связан с запуском и запустился немедленно, поэтому я начал экспериментировать. Несмотря на то, что это НЕ запускает это немедленно, это не терпит крах.

new Handler(Looper.getMainLooper()).post(new Runnable() {
        public void run() {
               context.startForegroundService(new Intent(context, 
           TaskQueueExecutorService.class));
               try {
                   Thread.sleep(10000);
               } catch (InterruptedException e) {
                  e.printStackTrace();
              }       
        }
});

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

a54studio
источник
вы запускаете свой сервис из фона или в деятельности?
Матин Чаудхри
Задний план. Проблема в том, что Android не может гарантировать, что служба будет запущена в отведенное время. Можно подумать, что они просто сгенерируют ошибку, чтобы игнорировать ее, если хотите, но, увы, не Android. Это должно быть смертельно. Это помогло в нашем приложении, но не помогло полностью.
a54studio
1

Я добавляю код в @humazed ответ. Так что нет ни одного начального уведомления. Это может быть обходной путь, но он работает для меня.

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

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("")
                    .setColor(ContextCompat.getColor(this, R.color.transparentColor))
                    .setSmallIcon(ContextCompat.getColor(this, R.color.transparentColor)).build();

            startForeground(1, notification);
        }
}

Я добавляю прозрачный цвет маленьким значком и цветом в уведомлении. Это сработает.

ХЕМ РАД МЕЕНА
источник
0

Я исправил проблему с запуском сервиса startService(intent)вместо Context.startForeground()и startForegound()сразу после звонка super.OnCreate(). Кроме того, если вы запускаете службу при загрузке, вы можете запустить Activity, которая запускает службу при загрузке. Хотя это не постоянное решение, оно работает.

melianor
источник
в некоторых устройствах Xiaomi действия не могут быть начаты из фона
Mateen Chaudhry
0

Обновление данных в onStartCommand(...)

onBind (...)

onBind(...)является лучшим событием жизненного цикла , чтобы инициировать startForegroundvs. onCreate(...)потому onBind(...)переходит в Intentкоторый может содержать важные данные в Bundleнеобходимых для инициализации Service. Однако в этом нет необходимости, так как он onStartCommand(...)вызывается при Serviceпервом создании или при последующих вызовах.

onStartCommand (...)

startForegroundв onStartCommand(...)это важно для того , чтобы обновить , Serviceкогда он уже создан.

Когда ContextCompat.startForegroundService(...)вызывается после того, как Serviceбыл создан onBind(...)и onCreate(...)не вызывается . Следовательно, обновленные данные могут быть переданы onStartCommand(...)через Intent Bundleдля обновления данных в Service.

Образец

Я использую этот шаблон , чтобы реализовать PlayerNotificationManagerв Coinverse приложение криптовалюта новостей.

Деятельность / Fragment.kt

context?.bindService(
        Intent(context, AudioService::class.java),
        serviceConnection, Context.BIND_AUTO_CREATE)
ContextCompat.startForegroundService(
        context!!,
        Intent(context, AudioService::class.java).apply {
            action = CONTENT_SELECTED_ACTION
            putExtra(CONTENT_SELECTED_KEY, contentToPlay.content.apply {
                audioUrl = uri.toString()
            })
        })

AudioService.kt

private var uri: Uri = Uri.parse("")

override fun onBind(intent: Intent?) =
        AudioServiceBinder().apply {
            player = ExoPlayerFactory.newSimpleInstance(
                    applicationContext,
                    AudioOnlyRenderersFactory(applicationContext),
                    DefaultTrackSelector())
        }

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    intent?.let {
        when (intent.action) {
            CONTENT_SELECTED_ACTION -> it.getParcelableExtra<Content>(CONTENT_SELECTED_KEY).also { content ->
                val intentUri = Uri.parse(content.audioUrl)
                // Checks whether to update Uri passed in Intent Bundle.
                if (!intentUri.equals(uri)) {
                    uri = intentUri
                    player?.prepare(ProgressiveMediaSource.Factory(
                            DefaultDataSourceFactory(
                                    this,
                                    Util.getUserAgent(this, getString(app_name))))
                            .createMediaSource(uri))
                    player?.playWhenReady = true
                    // Calling 'startForeground' in 'buildNotification(...)'.          
                    buildNotification(intent.getParcelableExtra(CONTENT_SELECTED_KEY))
                }
            }
        }
    }
    return super.onStartCommand(intent, flags, startId)
}

// Calling 'startForeground' in 'onNotificationStarted(...)'.
private fun buildNotification(content: Content): Unit? {
    playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            content.title,
            app_name,
            if (!content.audioUrl.isNullOrEmpty()) 1 else -1,
            object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun createCurrentContentIntent(player: Player?) = ...
                override fun getCurrentContentText(player: Player?) = ...
                override fun getCurrentContentTitle(player: Player?) = ...
                override fun getCurrentLargeIcon(player: Player?,
                                                 callback: PlayerNotificationManager.BitmapCallback?) = ...
            },
            object : PlayerNotificationManager.NotificationListener {
                override fun onNotificationStarted(notificationId: Int, notification: Notification) {
                    startForeground(notificationId, notification)
                }
                override fun onNotificationCancelled(notificationId: Int) {
                    stopForeground(true)
                    stopSelf()
                }
            })
    return playerNotificationManager.setPlayer(player)
}
Адам Гурвиц
источник
-1

обслуживание

class TestService : Service() {

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate")

        val nBuilder = NotificationCompat.Builder(this, "all")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("TestService")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        startForeground(1337, nBuilder.build())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val rtn = super.onStartCommand(intent, flags, startId)

        if (intent?.action == STOP_ACTION) {
            Log.d(TAG, "onStartCommand -> STOP")
            stopForeground(true)
            stopSelf()
        } else {
            Log.d(TAG, "onStartCommand -> START")
        }

        return rtn
    }

    override fun onDestroy() {
        Log.d(TAG, "onDestroy")
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? = null

    companion object {

        private val TAG = "TestService"
        private val STOP_ACTION = "ly.zen.test.TestService.ACTION_STOP"

        fun start(context: Context) {
            ContextCompat.startForegroundService(context, Intent(context, TestService::class.java))
        }

        fun stop(context: Context) {
            val intent = Intent(context, TestService::class.java)
            intent.action = STOP_ACTION
            ContextCompat.startForegroundService(context, intent)
        }

    }

}

тестер

val nChannel = NotificationChannel("all", "All", NotificationManager.IMPORTANCE_NONE)
val nManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nManager.createNotificationChannel(nChannel)

start_test_service.setOnClickListener {
    TestService.start(this@MainActivity)
    TestService.stop(this@MainActivity)
}

результат

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
JeanK
источник