Context.startForegroundService () тоді не викликав Service.startForeground ()


258

Я використовую ServiceClass в ОС Android Android.

Я планую використовувати Serviceфонове зображення.

У документації на Android зазначено, що

Якщо ваш додаток націлений на рівень API 26 або вище, система накладає обмеження на використання або створення фонових служб, якщо сам додаток не виходить на перший план. Якщо додатку потрібно створити послугу переднього плану, програма повинна зателефонувати startForegroundService().

Якщо ви використовуєте startForegroundService(), Serviceвикидає наступну помилку.

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

Що з цим погано?


IOW, будь ласка, надайте мінімально відтворюваний приклад . Це буде включати весь слід стека Java та код, який викликає збій.
CommonsWare

3
Помилка все ще знаходиться в API 26 і 27 (27.0.3). Уражені версії Android на 8.0 та 8.1 Ви можете зменшити кількість збоїв, додавши startForeground () і в onCreate (), і в onStartCommand (), але збої все ще трапляються для деяких користувачів. Єдиний спосіб виправити її atm - це targetSdkVersion 25 у вашій build.gradle.
Олексій


1
Я зараз використовую цю роботу. працює як шарм theandroiddeveloper.com/blog/…
Prasad

1
У мене така ж проблема. Я вирішую це питання. Я поділився реалізації в цій темі stackoverflow.com/questions/55894636 / ...
Beyazid

Відповіді:


99

З документів Google на Android 8.0 зміни поведінки :

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

Рішення: Виклик startForeground()в onCreate()для Serviceякої ви використовуєтеContext.startForegroundService()

Дивіться також: Фонові обмеження виконання для Android 8.0 (Oreo)


19
Я робив це onStartCommandметодом, але я все одно отримую цю помилку. Я подзвонив startForegroundService(intent)у свою MainActivity. Можливо, служба запускається занадто повільно. Я думаю, що обмеження на п'ять секунд не повинно існувати, перш ніж вони можуть пообіцяти, що послуга розпочнеться негайно.
Кімі Чіу

29
5 секунд періоду, безумовно, недостатньо, цей виняток трапляється дуже часто в сеансах налагодження. Я підозрюю, що також час від часу траплятиметься в режимі випуску. І будучи FATAL EXCEPTION, це просто розбиває додаток! Я спробував наздогнати це за допомогою Thread.setDefaultUncaughtExceptionHandler (), але навіть після потрапляння його та ігнорування андроїда заморожує додаток. Ось тому, що цей виняток спрацьовує у handleMessage (), і основний цикл ефективно закінчується ... шукаючи обхід.
Саутертон

Я закликав метод Context.startForegroundService () у широкомовному приймачі. так як в цій ситуації впоратися? тому що onCreate () недоступний у приймачі мовлення
Anand Savjani

6
@southerton Я думаю, вам слід зателефонувати onStartCommand()замість нього onCreate, тому що якщо ви закриєте службу і запустили її знову, це може перейти onStartCommand()без дзвінків onCreate...
android developer

1
Ми можемо перевірити відповідь команди google тут issueetracker.google.com/isissue/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);
        }
}

47
Я також. Але я все одно час від часу отримую цю помилку. Можливо, Android не може гарантувати, що він зателефонує onCreateчерез 5 секунд. Тож вони повинні переробити його перед тим, як змусити нас слідувати правилам.
Кімі Чіо

5
Я дзвонив startForeground в onStartCommand (), і мені періодично виникала ця помилка. Я перемістив його в onCreate і не бачив його з тих пір (схрещуючи пальці).
Тайлер

4
Це створює локальне сповіщення в Oreo: "APP_NAME працює. Торкніться, щоб закрити або переглянути інформацію". Як перестати показувати це повідомлення?
Художник404

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 для отримання додаткової інформації


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 за 5 секунд зателефонує до вашої цільової служби, яка повинна викликати startForeground (). Якщо ваша цільова служба не отримала дзвінок протягом 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)

Як я розумію, Looper проаналізував тут чергу, знайшов "кривдника" і просто вбив її. Зараз система щаслива і здорова, тоді як розробники та користувачі це не так, але оскільки Google обмежує свої обов'язки перед системою, чому вони повинні дбати про останні два? Мабуть, вони цього не роблять. Чи могли вони зробити це краще? Звичайно, наприклад, вони могли обслуговувати діалогове вікно "Програма зайнята", попросивши користувача прийняти рішення про очікування або вбивство програми, але чому це турбувати, це не їхня відповідальність. Головне, що система зараз здорова.

З моїх спостережень, це трапляється порівняно рідко, у моєму випадку приблизно 1 аварія на місяць для користувачів 1К. Відтворити це неможливо, і навіть якщо воно відтворене, ви нічого не можете зробити, щоб остаточно його виправити.

У цій темі було добре запропонувати використовувати "bind" замість "start", і тоді, коли служба готова, обробіть onServiceConnected, але знову ж таки, це означає взагалі не використовувати виклики startForegroundService.

Я думаю, що правильним та чесним дій з боку Google було б сказати всім, що startForegourndServcie має дефіцит і його не слід використовувати.

Все ще залишається питання: що використовувати замість цього? На щастя для нас, зараз є JobScheduler та JobService, які є кращою альтернативою для послуг переднього плану. Це кращий варіант, тому що:

Поки робота працює, система від імені вашого додатка проводить будильник. З цієї причини вам не потрібно вживати жодних дій, щоб гарантувати, що пристрій залишається неспаним протягом тривалості завдання.

Це означає, що вам більше не потрібно дбати про обробку годинників, і тому це не відрізняється від служб переднього плану. З точки зору впровадження, JobScheduler - це не ваша послуга, це система, імовірно, вона буде працювати з чергою правильно, і Google ніколи не закриє власну дитину :)

Samsung переключився з startForegroundService на JobScheduler та JobService у своєму протоколі аксесуарів Samsung (SAP). Це дуже корисно, коли пристрої, такі як смарт-годинники, повинні спілкуватися з такими хостами, як телефони, де завдання потрібно взаємодіяти з користувачем через основну нитку програми. Оскільки завдання розміщуються планувальником в основний потік, це стає можливим. Ви повинні пам’ятати, що ця робота виконується в основному потоці та завантажуйте всі важкі речі на інші потоки та завдання асинхронізації.

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

Єдина помилка переходу на JobScheduler / JobService полягає в тому, що вам потрібно буде перефактурувати старий код, і це не весело. Останні два дні я проводив саме так, щоб використовувати нову реалізацію Samsung SAP. Я перегляну свої звіти про аварійне завершення роботи, і повідомляю вас, чи побачите збої ще раз. Теоретично цього не повинно відбуватися, але завжди є деталі, про які ми можемо не знати.

ОНОВЛЕННЯ Немає більше повідомлень про збої в Play Store. Це означає, що у JobScheduler / JobService не виникає такої проблеми, і перехід на цю модель - це правильний підхід для позбавлення від проблеми startForegroundService раз і назавжди. Сподіваюся, Google / Android читає це, і згодом коментує / радить / надасть офіційне керівництво для всіх.

ОНОВЛЕННЯ 2

Для тих, хто використовує SAP і запитує, як SAP V2 використовує пояснення JobService нижче.

У своєму власному коді вам потрібно ініціалізувати SAP (це Котлін):

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'd 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працює негайно, як IntentServiceнижче Oreo, але планує роботу на Oreo і вище, тому JobIntentServiceне починається відразу. Більше інформації
CopsOnRoad

1
@CopsOnRoad Це дуже корисно, і він починається негайно в моєму випадку, як я писав, він використовується для взаємодії в режимі реального часу між телефоном та смарт-годинником. Ні в якому разі користувач не зачекав би 15 хвилин, щоб надіслати дані між цими двома. Працює дуже добре і ніколи не виходить з ладу.
Олег Гриб

1
Звучить здорово, зробить вашу відповідь значно вартішою, якщо ви зможете поділитися зразком коду, я новачок в Android, і я виявив, що Job...потрібно час, щоб почати, і це попередня версія AlarmManager.
CopsOnRoad

2
Напевно, я стану, коли дозволить час: мені потрібно буде
від’єднати

1
@batmaci - JobService використовується SAP внутрішньо. Детальні відомості див. У розділі Оновлення 2 в ОП.
Олег Гриб

32

Ваш додаток завершиться, якщо ви зателефонуєте, Context.startForegroundService(...)а потім зателефонуйте Context.stopService(...)раніше Service.startForeground(...).

Я маю чітке докори тут ForegroundServiceAPI26

Я відкрив помилку щодо цього в: трекер випуску Google

Кілька помилок на цьому було відкрито та закрито, не виправлено.

Сподіваюся, міна з чіткими етапами докорів зробить скорочення.

Інформація, надана командою Google

Контроль випуску Google Коментар 36

Це не рамкова помилка; це навмисно. Якщо додаток запускає екземпляр служби startForegroundService(), він повинен перейти цей екземпляр служби у стан переднього плану та показати сповіщення. Якщо екземпляр служби зупинено до того, як startForeground()буде викликано його, ця обіцянка невиконана: це помилка в додатку.

Повторне число 31 , публікація послуги, яку інші програми можуть запускати безпосередньо, є принципово небезпечною. Ви можете трохи пом’якшити це, ставлячись до всіх необхідних дій цієї послуги як до необхідних startForeground(), хоча, очевидно, це може бути не тим, що ви мали на увазі.

Відстежувач проблем Google Коментар 56

Тут є кілька різних сценаріїв, які призводять до однакових результатів.

Відверта семантична проблема, що це просто помилка, з якої щось почати, startForegroundService()але нехтування фактичним переходом на передній план через startForeground(), є саме це: семантична проблема. Це навмисно трактується як помилка програми. Припинення послуги перед переходом на передній план - це помилка програми. У цьому полягало суть ОП, і саме тому це питання було позначене "працюючи за призначенням".

Однак є також питання щодо хибного виявлення цієї проблеми. Ось це час розглядаються як реальна проблема, хоча це відстежується окремо від цього конкретного питання помилки трекера. Ми не глухі до скарги.


2
Найкраще оновлення, яке я бачив, - issueetracker.google.com/isissue/76112072#comment56 . Я переписав свій код для використання Context.bindService, що повністю уникає проблемного виклику Context.startForegroundService. Ви можете переглянути мій зразок коду на сайті github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/…
swooby

так, як я писав, прив'язка повинна працювати, але я вже перейшов на JobServive і не збираюся нічого змінювати, якщо цей теж не зламається ':)
Олег Гриб

19

Я досліджував це пару днів і отримав рішення. Тепер в 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 + збоїв щодня (з помилкою) у додатку, який мав 10k користувачів на день. Звичайно, це трапляється лише для користувачів з android 8.0 та android 8.1, якщо targetSdkVersion дорівнює 27. Усі проблеми зникають до 0 збоїв відразу після того, як я встановив targetSdkVersion на 25.
Олексій

лише 2200 користувач: - / але не отримав жодної аварії
Ахмад Арслан

1
@Jeeva, не дуже. 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. Використання будь-якого іншого значення, здається, це виправить.


У моєму випадку я використовував ідентифікатор 1, дякую, проблему було вирішено
Амін Пінджарі

4
Я використовую ID 101, але іноді я отримую цю помилку.
CopsOnRoad

9

У мене є проблема щодо цієї проблеми. Я підтвердив це виправлення у власному додатку (300K + 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. Якщо ви хочете запустити фрагмент коду першого кроку в очікуванні наміру, наприклад, якщо ви хочете запустити послугу переднього плану у віджеті (натискання на кнопку віджета), не відкриваючи додаток, ви можете загорнути фрагмент коду у приймач широкомовного перегляду та запустіть трансляцію події замість команди запуску служби.

Це все. Сподіваюся, це допомагає. Удачі.


8

Ця помилка також виникає на Android 8+, коли Service.startForeground (int id, сповіщення про сповіщення) викликається, коли id встановлено у 0.

id int: ідентифікатор цього сповіщення згідно NotificationManager.notify (int, сповіщення); не повинно бути 0 .


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 else your Intent Serviceuse, тоді поставте цей код у 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застарілий, і я використовую його лише як тимчасове рішення для вирішення цієї проблеми.


Вам слід зателефонувати замість контексту. Сторінка сервісу (serviceIntent), коли ви хочете зупинити послугу.
makovkastar

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);
        }

Я закликав зупинити передній план із видаленням notificaton, але як тільки служба видалення сповіщень стане фоном, а сервіс фонового режиму не може працювати в android O з фону розпочато після отримання натискання.

Так магічне слово

   stopSelf();

Поки будь-яка причина, коли ваша послуга перестає працювати, виконайте всі вищезазначені кроки та насолоджуйтесь.


if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.O) {stopForeground (true); } else {stopForeground (вірно); } для чого це ще? в обох випадках ви робили те саме, stopForeground (вірно);
користувач3135923

Я вважаю, ви мали намір поставити stopSelf (); всередині блоку else замість stopForeground (true);
Джастін Стенлі

1
Так, @JustinStanley використовувати stopSelf ();
піщаний

Не startForegroundслід телефонувати onCreate?
Аба

Я використовую NotificationManager, а не клас сповіщень. Як я можу це здійснити?
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 (Намір), цей метод можна використовувати в будь-який час, незалежно від того, перебуває програма, що розміщує послугу, у передньому плані.

переконайтеся, що ви зателефонували 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 або перевищити Макс (Int32)


2

Навіть після виклику startForegroundв системі 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" />

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);
        }
}

Це насправді просто запускає послугу для запуску сервісу, а не startForegroundService, якщо це недійсне. Оператор if повинен бути вкладений, інакше додаток стане збоєм, намагаючись в певний момент використовувати сервіси на більш пізній версії.
a54studio

1

просто зателефонуйте метод startForeground відразу після створення Сервісу або IntentService. подобається це:

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

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}

FATAL EXCEPTION: main android.app.RemoteServiceException: Неправильне повідомлення для startForeground: java.lang.RuntimeException: недійсний канал для сповіщення про службу: Повідомлення (канал = 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()в сервісі.

Тож навіть якщо ви оновлюєте інтерфейс користувача на основному потоці, якщо у вас є щось, що може затримати цей метод після нього, він не запуститься вчасно і дасть вам жахливу помилку переднього плану. У моєму випадку ми завантажували деякі речі в чергу, і кожен дзвонив 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();
              }       
        }
});

Я не буду робити вигляд, що знаю, чому він не виходить з ладу, хоча, підозрюю, це змушує його почекати, поки головна нитка зможе вчасно впоратися з цим. Я знаю, що не ідеально прив’язувати його до основної нитки, але оскільки моє використання викликає це у фоновому режимі, я не переживаю, якщо він чекає, поки він може закінчитися, а не вийти з ладу.


ви починаєте свою послугу з фонового чи активного характеру?
Mateen Chaudhry

Фон Проблема полягає в тому, що 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);
        }
}

Я додаю transparentColor маленьким значком та кольором у сповіщенні. Це спрацює.


0

Я вирішив проблему із запуском послуги startService(intent)замість того, щоб Context.startForeground()дзвонити startForegound()відразу після цього super.OnCreate(). Крім того, якщо ви запускаєте службу під час завантаження, ви можете запустити Діяльність, яка запускає службу в ефірі завантаження. Хоча це не постійне рішення, воно працює.


на деяких пристроях Xiaomi діяльність не може бути розпочата з фону
Mateen Chaudhry

0

Оновлення даних у onStartCommand(...)

onBind (...)

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

onStartCommand (...)

startForegroundв onStartCommand(...)важливо для того, щоб оновити, Serviceколи воно вже створене.

Коли ContextCompat.startForegroundService(...)викликається після того Service, як створено a , 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.