Context.startForegroundService () no llamó a Service.startForeground ()

258

estoy usando Service Class en el sistema operativo Android O.

Planeo usar el Serviceen el fondo.

La documentación de Android establece que

Si su aplicación está dirigida a un nivel de API 26 o superior, el sistema impone restricciones sobre el uso o la creación de servicios en segundo plano a menos que la aplicación esté en primer plano. Si una aplicación necesita crear un servicio en primer plano, la aplicación debe llamar startForegroundService().

Si lo usa startForegroundService(), Servicearroja el siguiente error.

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

¿Qué tiene de malo esto?

Buen chico
fuente
IOW, proporcione un ejemplo reproducible mínimo . Eso incluiría todo el seguimiento de la pila de Java y el código que desencadena el bloqueo.
CommonsWare
3
El error todavía está aquí en API 26 y 27 (27.0.3). Las versiones de Android afectadas son 8.0 y 8.1. Puede reducir el número de bloqueos agregando startForeground () tanto a onCreate () como a onStartCommand (), pero los bloqueos seguirán ocurriendo para algunos usuarios. La única forma de arreglarlo atm es targetSdkVersion 25 en su build.gradle.
Alex
1
Estoy usando esta solución ahora. funciona como encanto theandroiddeveloper.com/blog/…
Prasad Pawar
1
Tengo el mismo problema. Arreglo este problema. Compartí mi implementación en este tema stackoverflow.com/questions/55894636/…
Beyazid

Respuestas:

99

De los documentos de Google sobre los cambios de comportamiento de Android 8.0 :

El sistema permite que las aplicaciones llamen a Context.startForegroundService () incluso mientras la aplicación está en segundo plano. Sin embargo, la aplicación debe llamar al método startForeground () de ese servicio dentro de los cinco segundos posteriores a la creación del servicio.

Solución: Llamada startForeground()en onCreate()elService que se utilizaContext.startForegroundService()

Consulte también: Límites de ejecución en segundo plano para Android 8.0 (Oreo)

zhuzhumouse
fuente
19
Hice esto en el onStartCommandmétodo, pero todavía recibo este error. Llamé startForegroundService(intent)a mi MainActivity. Tal vez el servicio se inicia demasiado lento. Creo que el límite de cinco segundos no debería existir antes de que puedan prometer que el servicio se iniciará de inmediato.
Kimi Chiu el
29
El período de 5 segundos definitivamente no es suficiente, esta excepción ocurre muy a menudo en las sesiones de depuración. Sospecho que también DEBERÍA suceder en el modo de lanzamiento de vez en cuando. Y siendo EXCEPCIÓN FATAL, ¡simplemente bloquea la aplicación! Traté de atraparlo con Thread.setDefaultUncaughtExceptionHandler (), pero incluso al llegar allí e ignorar Android congela la aplicación. Eso se debe a que esta excepción se activa desde handleMessage (), y el bucle principal termina efectivamente ... buscando una solución.
southerton
Llamé al método Context.startForegroundService () en el receptor de difusión. Entonces, ¿cómo manejar en esta situación? porque onCreate () no está disponible en el receptor de transmisión
Anand Savjani
66
@southerton Creo que deberías llamarlo en onStartCommand()lugar de hacerlo onCreate, porque si cierras el servicio y lo vuelves a iniciar, podría funcionar onStartCommand()sin llamar onCreate...
desarrollador de Android
1
Podemos verificar la respuesta del equipo de Google aquí issuetracker.google.com/issues/76112072#comment56
Prags
82

Llamé ContextCompat.startForegroundService(this, intent)para comenzar el servicio y luego

En servicio 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);
        }
}
humillado
fuente
48
Yo también. Pero todavía recibo este error ocasionalmente. Tal vez el Android no puede garantizar que llamará onCreateen 5 segundos. Entonces deberían rediseñarlo antes de obligarnos a seguir las reglas.
Kimi Chiu
55
Estaba llamando a startForeground en onStartCommand (), y ocasionalmente recibía este error. Lo moví a onCreate y no lo he visto desde entonces (cruzando los dedos).
Tyler
44
Esto crea una notificación local en Oreo que dice "APP_NAME se está ejecutando. Toque para cerrar o ver información". ¿Cómo dejar de mostrar esa notificación?
Artista404
66
No, el equipo de Android afirmó que este es un comportamiento previsto. Así que acabo de rediseñar mi aplicación. Esto es ridículo. Siempre es necesario rediseñar las aplicaciones debido a estos "comportamientos previstos". Es la tercera vez.
Kimi Chiu el
12
La causa principal de este problema es que el servicio se detuvo antes de ser promovido a primer plano. Pero la afirmación no se detuvo después de que el servicio fuera destruido. Puede intentar reproducir esto agregando StopServicedespués de llamar startForegroundService.
Kimi Chiu el
55

El motivo por el que ocurre este problema es porque el marco de Android no puede garantizar que su servicio se inicie en 5 segundos, pero por otro lado, el marco tiene un límite estricto en la notificación en primer plano, debe activarse en 5 segundos, sin verificar si el marco había intentado iniciar el servicio .

Este es definitivamente un problema de marco, pero no todos los desarrolladores que enfrentan este problema están haciendo su mejor esfuerzo:

  1. startForegrounduna notificación debe estar en ambos onCreatey onStartCommand, porque si su servicio ya está creado y de alguna manera su actividad está tratando de iniciarlo nuevamente, onCreateno se llamará.

  2. el ID de notificación no debe ser 0; de lo contrario, se producirá el mismo bloqueo, aunque no sea la misma razón.

  3. stopSelfNo debe ser llamado antes startForeground.

Con todo lo anterior 3, este problema se puede reducir un poco, pero aún no es una solución, la solución real o, digamos, una solución alternativa es degradar su versión de SDK de destino a 25.

Y tenga en cuenta que lo más probable es que Android P siga teniendo este problema porque Google se niega incluso a comprender lo que está sucediendo y no cree que sea su culpa, lea los números 36 y 56 para obtener más información.

ZhouX
fuente
99
¿Por qué tanto onCreate como onStartCommand? ¿Puedes ponerlo en onStartCommand?
rcell
No se debe llamar a stopSelf antes de startForeground => o directamente después, porque parece cancelar "startForeground" cuando lo hace.
Frank
1
Ejecuté algunas pruebas, e incluso si no se llama a onCreate si el servicio ya está creado, no necesita llamar a startForeground nuevamente. Por lo tanto, puede llamarlo solo en el método onCreate y no en el onStartCommand. Probablemente consideran que la instancia única de servicio está en primer plano después de la primera llamada hasta su finalización.
Lxu
Estoy usando 0 como ID de notificación de la llamada inicial startForeground. Cambiarlo a 1 me soluciona el problema (con suerte)
mr5
¿Entonces, cuál es la solución?
IgorGanapolsky
35

Lo sé, ya se han publicado demasiadas respuestas, sin embargo, la verdad es que startForegroundService no se puede solucionar a nivel de aplicación y debes dejar de usarlo. Esa recomendación de Google de utilizar la API Service # startForeground () dentro de los 5 segundos posteriores a la llamada al Contexto # startForegroundService () no es algo que una aplicación siempre pueda hacer.

Android ejecuta muchos procesos simultáneamente y no hay ninguna garantía de que Looper llame a su servicio de destino que supuestamente llamará a startForeground () en 5 segundos. Si su servicio objetivo no recibió la llamada en 5 segundos, no tiene suerte y sus usuarios experimentarán una situación de ANR. En su seguimiento de pila verá algo como esto:

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)

Según tengo entendido, Looper ha analizado la cola aquí, encontró un "abusador" y simplemente lo mató. El sistema es feliz y saludable ahora, mientras que los desarrolladores y usuarios no lo son, pero dado que Google limita sus responsabilidades con el sistema, ¿por qué deberían preocuparse por los dos últimos? Aparentemente no lo hacen. ¿Podrían hacerlo mejor? Por supuesto, por ejemplo, podrían haber servido el cuadro de diálogo "La aplicación está ocupada", pidiéndole al usuario que tome una decisión sobre esperar o matar la aplicación, pero por qué molestarse, no es su responsabilidad. Lo principal es que el sistema está sano ahora.

Según mis observaciones, esto ocurre relativamente raramente, en mi caso aproximadamente 1 bloqueo en un mes para usuarios de 1K. Reproducirlo es imposible, e incluso si se reproduce, no hay nada que pueda hacer para arreglarlo permanentemente.

Hubo una buena sugerencia en este hilo para usar "bind" en lugar de "start" y luego, cuando el servicio esté listo, procese onServiceConnected, pero de nuevo, significa no usar las llamadas startForegroundService.

Creo que la acción correcta y honesta del lado de Google sería decirle a todos que startForegourndServcie tiene una deficiencia y no debe usarse.

La pregunta sigue siendo: ¿qué usar en su lugar? Afortunadamente para nosotros, hay JobScheduler y JobService ahora, que son una mejor alternativa para los servicios en primer plano. Es una mejor opción, por eso:

Mientras se ejecuta un trabajo, el sistema mantiene un wakelock en nombre de su aplicación. Por este motivo, no es necesario que realice ninguna acción para garantizar que el dispositivo permanezca despierto durante el trabajo.

Significa que ya no necesita preocuparse por el manejo de wakelocks y es por eso que no es diferente de los servicios en primer plano. Desde el punto de vista de la implementación, JobScheduler no es su servicio, es un sistema, presumiblemente manejará la cola correctamente y Google nunca terminará su propio hijo :)

Samsung ha cambiado de startForegroundService a JobScheduler y JobService en su Samsung Accessory Protocol (SAP). Es muy útil cuando dispositivos como los relojes inteligentes necesitan hablar con hosts como teléfonos, donde el trabajo necesita interactuar con un usuario a través del hilo principal de una aplicación. Dado que el planificador publica los trabajos en el subproceso principal, es posible. Sin embargo, debe recordar que el trabajo se está ejecutando en el hilo principal y descargar todas las cosas pesadas a otros hilos y tareas asíncronas.

Este servicio ejecuta cada trabajo entrante en un controlador que se ejecuta en el hilo principal de su aplicación. Esto significa que debe descargar su lógica de ejecución a otro hilo / controlador / AsyncTask de su elección

El único inconveniente de cambiar a JobScheduler / JobService es que necesitará refactorizar el código antiguo, y no es divertido. Pasé los últimos dos días haciendo exactamente eso para usar la nueva implementación de SAP de Samsung. Veré mis informes de fallas y te haré saber si vuelves a ver las fallas. Teóricamente no debería suceder, pero siempre hay detalles de los que quizás no estemos al tanto.

ACTUALIZACIÓN No más bloqueos reportados por Play Store. Significa que JobScheduler / JobService no tiene ese problema y cambiar a este modelo es el enfoque correcto para deshacerse del problema startForegroundService de una vez y para siempre. Espero que Google / Android lo lea y eventualmente comente / aconseje / brinde una guía oficial para todos.

ACTUALIZACIÓN 2

Para aquellos que usan SAP y preguntan cómo SAP V2 utiliza JobService, la explicación a continuación.

En su código personalizado, deberá inicializar SAP (es Kotlin):

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

Ahora necesita descompilar el código de Samsung para ver lo que sucede dentro. En SAAgentV2, observe la implementación de requestAgent y la siguiente línea:

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

where d defined as below

private SAAdapter d;

Vaya a la clase SAAdapter ahora y encuentre la función onServiceConnectionRequested que programa un trabajo mediante la siguiente llamada:

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

SAJobService es solo una implementación de Android'd JobService y esta es la que hace una programación de trabajo:

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

Como puede ver, la última línea aquí usa JobScheduler de Android'd para obtener este servicio del sistema y programar un trabajo.

En la llamada requestAgent hemos pasado mAgentCallback, que es una función de devolución de llamada que recibirá el control cuando ocurra un evento importante. Así es como se define la devolución de llamada en mi aplicación:

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 aquí es una clase que he implementado para procesar todas las solicitudes provenientes de un reloj inteligente Samsung. No es el código completo, solo un esqueleto:

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) {

    }

}

Como puede ver, MessageJobs también requiere la clase MessageSocket que necesitaría implementar y que procesa todos los mensajes que provienen de su dispositivo.

En pocas palabras, no es tan simple y requiere un poco de excavación en las partes internas y la codificación, pero funciona, y lo más importante: no se bloquea.

Oleg Gryb
fuente
Buena respuesta, pero hay un problema importante, se JobIntentServiceejecuta inmediatamente como un IntentServiceOreo por debajo, pero programa un trabajo en Oreo y por encima, por lo JobIntentServiceque no comienza de inmediato. Más información
CopsOnRoad
1
@CopsOnRoad Es muy útil y comienza de inmediato en mi caso. Como escribí, se usa para interacciones en tiempo real entre el teléfono y un reloj inteligente. De ninguna manera, un usuario esperaría 15 minutos para enviar datos entre estos dos. Funciona muy bien y nunca se bloquea.
Oleg Gryb
1
Suena genial, haría que tu respuesta valiera mucho si pudieras compartir algún código de muestra, soy nuevo en Android, y descubrí que Job...lleva tiempo comenzar y es una versión avanzada AlarmManager.
CopsOnRoad
2
Probablemente lo haré cuando el tiempo lo permita: tendré que
desacoplarlo del
1
@batmaci: SAP utiliza internamente JobService. Vea la Actualización 2 en OP para más detalles.
Oleg Gryb
32

Su aplicación se bloqueará si llama Context.startForegroundService(...)y luego llama Context.stopService(...)antes de que Service.startForeground(...)se llame.

Tengo una clara reproducción aquí ForegroundServiceAPI26

He abierto un error en esto en: rastreador de problemas de Google

Se han abierto y cerrado varios errores en esto.

Espero que la mía con pasos claros de reproducción haga el corte.

Información proporcionada por el equipo de google

Rastreador de problemas de Google Comentario 36

Esto no es un error de marco; Es intencional. Si la aplicación inicia una instancia de servicio con startForegroundService(), debe pasar esa instancia de servicio al estado de primer plano y mostrar la notificación. Si la instancia de servicio se detiene antes de startForeground()que se invoque, esa promesa no se cumple: esto es un error en la aplicación.

Re # 31 , publicar un Servicio que otras aplicaciones pueden iniciar directamente es fundamentalmente inseguro. Puede mitigarlo un poco tratando todas las acciones de inicio de ese servicio como requeridasstartForeground() , aunque obviamente eso puede no ser lo que tenía en mente.

Rastreador de problemas de Google Comentario 56

Hay un par de escenarios diferentes que conducen al mismo resultado aquí.

El problema semántico absoluto, es que es simplemente un error comenzar algo startForegroundService()pero descuidar la transición al primer plano a través destartForeground() , es solo eso: un problema semántico. Eso se trata como un error de la aplicación, intencionalmente. Detener el servicio antes de pasarlo a primer plano es un error de la aplicación. Ese fue el quid de la OP, y es por eso que este problema se ha marcado como "funcionando según lo previsto".

Sin embargo, también hay preguntas sobre la detección espuria de este problema. Eso se está tratando como un problema genuino, aunque se está rastreando por separado de este problema particular del rastreador de errores. No estamos sordos a la queja.

swooby
fuente
2
La mejor actualización que he visto es issuetracker.google.com/issues/76112072#comment56 . Reescribí mi código para usar Context.bindService, lo que evita por completo la llamada problemática a Context.startForegroundService. Puede ver mi código de muestra en github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/…
swooby
sí, como escribí, bind debería funcionar, pero ya me cambié a JobServive y no voy a cambiar nada a menos que este también se rompa ':)
Oleg Gryb
20

He investigado sobre esto durante un par de días y obtuve la solución. Ahora en Android O puede establecer la limitación de fondo como se muestra a continuación

El servicio que llama a una clase de servicio.

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

y la clase de servicio debería ser como

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
    }
}
Ahmad Arslan
fuente
3
¿Lo probaste en alguna aplicación que tiene al menos varios miles de usuarios? Lo intenté, algunos usuarios todavía tienen problemas de bloqueo.
Alex
No, no, estoy usando el código exacto y no veo que los clientes se bloqueen.
Ahmad Arslan
¿Cuántos usuarios tienes? Vi ~ 10-30 + bloqueos diarios (con el error) en la aplicación que tenía 10k usuarios por día. Por supuesto, sucede solo para usuarios con Android 8.0 y Android 8.1, en caso de que targetSdkVersion sea 27. Todos los problemas desaparecen a 0 bloqueos justo después de establecer targetSdkVersion en 25.
Alex
solo 2200 usuarios: - / pero no tuvo ningún bloqueo
Ahmad Arslan
1
@Jeeva, no realmente. Atm, uso targetSdkVersion 25 con compileSdkVersion 27. Parece que es la mejor manera después de muchos experimentos ... Espero que terminen developer.android.com/topic/libraries/architecture/… antes de agosto de 2018 porque android-developers.googleblog .com / 2017/12 / ...
Alex
16

Tengo un widget que realiza actualizaciones relativamente frecuentes cuando el dispositivo está despierto y vi miles de bloqueos en solo unos días.

El desencadenante del problema

Incluso noté el problema incluso en mi Pixel 3 XL cuando no hubiera pensado que el dispositivo tuviera mucha carga. Y todos y cada uno de los caminos de código estaban cubiertos startForeground(). Pero luego me di cuenta de que en muchos casos mi servicio hace el trabajo realmente rápido.Creo que el desencadenante de mi aplicación fue que el servicio estaba terminando antes de que el sistema realmente mostrara una notificación.

La solución / solución

Pude deshacerme de todos los accidentes. Lo que hice fue quitar la llamada a stopSelf(). (Estaba pensando en retrasar la parada hasta estar bastante seguro de que se mostró la notificación, pero no quiero que el usuario vea la notificación si no es necesario). Cuando el servicio ha estado inactivo durante un minuto o el sistema lo destruye normalmente sin lanzar ninguna excepción.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    stopForeground(true);
} else {
    stopSelf();
}
Roy Solberg
fuente
11

Solo un aviso porque perdí demasiadas horas en esto. Seguí recibiendo esta excepción a pesar de que estaba llamando startForeground(..)como lo primero onCreate(..). Al final descubrí que el problema fue causado por el uso NOTIFICATION_ID = 0. Usar cualquier otro valor parece arreglar esto.

tobalr
fuente
En mi caso estaba usando ID 1, gracias, el problema se resolvió
Amin Pinjari
44
Estoy usando ID 101, todavía recibo este error a veces.
CopsOnRoad
9

Tengo una solución para este problema. He verificado esta solución en mi propia aplicación (300K + DAU), que puede reducir al menos el 95% de este tipo de bloqueo, pero aún no puede evitar este problema al 100%.

Este problema ocurre incluso cuando se asegura de llamar a startForeground () justo después de que se inició el servicio como Google documentó. Puede deberse a que el proceso de creación e inicialización del servicio ya cuesta más de 5 segundos en muchos escenarios, y no importa cuándo y dónde llame al método startForeground (), este bloqueo es inevitable.

Mi solución es asegurar que startForeground () se ejecute dentro de los 5 segundos posteriores al método startForegroundService (), sin importar cuánto tiempo deba crearse e inicializarse su servicio. Aquí está la solución detallada.

  1. No use startForegroundService en primer lugar, use bindService () con el indicador auto_create. Esperará la inicialización del servicio. Aquí está el código, mi servicio de muestra es 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. Entonces aquí está la implementación de 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. La parte más importante, la implementación de MusicService, el método forceForeground () asegurará que se llame al método startForeground () justo después de 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. Si desea ejecutar el fragmento de código del paso 1 en un intento pendiente, como si desea iniciar un servicio en primer plano en un widget (un clic en el botón del widget) sin abrir su aplicación, puede envolver el fragmento de código en un receptor de difusión , y dispara un evento de difusión en lugar del comando de inicio de servicio.

Eso es todo. Espero eso ayude. Buena suerte.

Hexise
fuente
7

Como todos los que visitan aquí están sufriendo lo mismo, quiero compartir mi solución que nadie más ha probado antes (de todos modos, en esta pregunta). Les puedo asegurar que está funcionando, incluso en un punto de interrupción detenido. que confirma este método.

El problema es llamar Service.startForeground(id, notification)desde el servicio en sí, ¿verdad? Lamentablemente, Android Framework no garantiza llamar Service.startForeground(id, notification)dentro Service.onCreate()de 5 segundos, pero arroja la excepción de todos modos, así que se me ocurrió de esta manera.

  1. Enlace el servicio a un contexto con un cuaderno del servicio antes de llamar Context.startForegroundService()
  2. Si el enlace es exitoso, llame Context.startForegroundService() desde la conexión de servicio e inmediatamente llame Service.startForeground() dentro de la conexión de servicio.
  3. NOTA IMPORTANTE: llame al Context.bindService()método dentro de un try-catch porque en algunas ocasiones la llamada puede arrojar una excepción, en cuyo caso debe confiar en llamar Context.startForegroundService()directamente y esperar que no falle. Un ejemplo puede ser un contexto de receptor de difusión, sin embargo, obtener el contexto de la aplicación no arroja una excepción en ese caso, pero usar el contexto directamente lo hace.

Esto incluso funciona cuando estoy esperando un punto de interrupción después de vincular el servicio y antes de activar la llamada "startForeground". Esperar entre 3-4 segundos no activa la excepción mientras que después de 5 segundos arroja la excepción. (Si el dispositivo no puede ejecutar dos líneas de código en 5 segundos, entonces es hora de tirarlo a la basura).

Entonces, comience creando una conexión de servicio.

// 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..");
    }
};

Dentro de su servicio, cree una carpeta que devuelva la instancia de su servicio.

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

Luego, intente vincular el servicio desde ese contexto. Si tiene éxito, llamará al ServiceConnection.onServiceConnected()método desde la conexión de servicio que está utilizando. Luego, maneje la lógica en el código que se muestra arriba. Un código de ejemplo se vería así:

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

Parece estar funcionando en las aplicaciones que desarrollo, por lo que debería funcionar cuando lo intentas también.

Furkan Yurdakul
fuente
5

Problema con Android O API 26

Si detiene el servicio de inmediato (por lo que su servicio realmente no se ejecuta realmente (redacción / comprensión) y está muy por debajo del intervalo ANR, aún debe llamar a startForeground antes de detenerse a sí mismo

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

Intenté este enfoque pero aún crea un error:

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

Estoy usando esto hasta que se resuelva el error

mContext.startService(playIntent);
Andy Cass
fuente
3
Debería ser Util.SDK_INT> = 26, no solo más grande
Andris
4

Estoy enfrentando el mismo problema y después de pasar tiempo encontrando una solución, puede probar el siguiente código. Si está usando Service, coloque este código en onCreate, de lo contrario, use Intent Serviceeste código en 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);
    }
Sambhaji Karad
fuente
4

He estado investigando este problema y esto es lo que he descubierto hasta ahora. Este bloqueo podría ocurrir si tenemos un código similar a este:

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

La excepción se produce en el siguiente bloque del código:

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

Este método se ejecuta antes onCreate()de MyForegroundServiceporque los horarios de Android de la creación del servicio en el controlador principal hilo, pero bringDownServiceLockedse llama en una BinderThread, la cual es una condición de carrera. Significa que MyForegroundServiceno tuve la oportunidad de llamar, startForegroundlo que causará el bloqueo.

Para solucionar esto, debemos asegurarnos de que bringDownServiceLockedno se llame antes onCreate()de 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));
    }
}

Mediante el uso de emisiones persistentes nos aseguramos de que la emisión no se pierde y stopReceiverrecibe la parada intención tan pronto como se haya registrado en la onCreate()de MyForegroundService. En este momento ya hemos llamadostartForeground(...) . También tenemos que eliminar esa transmisión fija para evitar que StopReceiver sea notificado la próxima vez.

Tenga en cuenta que el método sendStickyBroadcastestá en desuso y lo uso solo como una solución temporal para solucionar este problema.

makovkastar
fuente
Debería llamarlo en lugar de context.stopService (serviceIntent) cuando desee detener el servicio.
makovkastar
4

Tantas respuestas pero ninguna funcionó en mi caso.

He comenzado un servicio como este.

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

Y en mi servicio en 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);
    }

Y no olvide establecer NOTIFICATION_ID diferente de cero

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

Entonces, todo fue perfecto, pero aún se bloqueó en 8.1, por lo que la causa fue la siguiente.

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

He llamado a detener el primer plano con la notificación de eliminación, pero una vez que el servicio de notificación eliminada se convierte en segundo plano y el servicio en segundo plano no se puede ejecutar en Android O desde segundo plano. comenzó después del empuje recibido.

Así que la palabra mágica es

   stopSelf();

Hasta ahora, por cualquier motivo que su servicio se esté bloqueando, siga todos los pasos anteriores y disfrute.

arenoso
fuente
if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.O) {stopForeground (verdadero); } else {stopForeground (true); } ¿para qué sirve esto? ambos casos estabas haciendo lo mismo, stopForeground (verdadero);
user3135923
Creo que querías poner stopSelf (); dentro del bloque else en lugar de stopForeground (true);
Justin Stanley
1
Sí, @JustinStanley usa stopSelf ();
arenoso
¿No debería startForegroundser llamado onCreate?
Aba
Estoy usando NotificationManager y no la clase de notificación. ¿Cómo puedo implementar esto?
Prajwal W
3

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

Similar a startService (Intent), pero con una promesa implícita de que el Servicio llamará a startForeground (int, android.app.Notification) una vez que comience a ejecutarse. El servicio recibe una cantidad de tiempo comparable al intervalo ANR para hacer esto; de lo contrario, el sistema detendrá automáticamente el servicio y declarará la aplicación ANR.

A diferencia del startService (Intención) ordinario, este método se puede usar en cualquier momento, independientemente de si la aplicación que aloja el servicio se encuentra en primer plano.

asegúrese de llamar al Service.startForeground(int, android.app.Notification)onCreate () para asegurarse de que se llamará ... si tiene alguna condición que le impida hacerlo, será mejor que use el normal Context.startService(Intent)y llame alService.startForeground(int, android.app.Notification) usted mismo.

Parece que Context.startForegroundService()agrega un perro guardián para asegurarte de que llamaste Service.startForeground(int, android.app.Notification)antes de que fuera destruido ...

Alécio Carvalho
fuente
3

No llame a ningún StartForgroundServices dentro del método onCreate () , debe llamar a los servicios StartForground en onStartCommand () después de hacer el subproceso de trabajo, de lo contrario obtendrá ANR siempre, así que no escriba inicio de sesión complejo en el subproceso principal de 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);

    }
}

EDITAR: ¡Precaución! La función startForeground no puede tomar 0 como primer argumento, ¡generará una excepción! este ejemplo contiene una llamada de función incorrecta, cambie 0 a su propia constante que no podría ser 0 o mayor que Max (Int32)

Shalu Gupta
fuente
2

Incluso después de llamar al startForegrounden Service, Se estrella en algunos dispositivos si llamamos stopServicejusto antes de onCreateque se llama. Entonces, solucioné este problema al iniciar el servicio con un indicador adicional:

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

y agregó un control en onStartCommand para ver si se comenzó a detener:

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

PD: si el servicio no se estuviera ejecutando, lo iniciaría primero, lo que es una sobrecarga.

Gaurav Singla
fuente
2

Debe agregar un permiso como se indica a continuación para el dispositivo Android 9 cuando use el sdk 28 de destino o posterior o la excepción siempre sucederá:

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

Solo verifico el PendingIntentnulo o no antes de llamar al context.startForegroundService(service_intent) función.

esto funciona para mi

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
fuente
En realidad, esto simplemente activa el servicio a startService en lugar de startForegroundService si es nulo. La instrucción if tendría que estar anidada, de lo contrario, la aplicación se bloquearía al intentar utilizar los servicios en una versión posterior en algún momento.
a54studio
1

simplemente llame al método startForeground inmediatamente después de crear el servicio o IntentService. Me gusta esto:

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

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}
Hossein Karami
fuente
1
EXCEPCIÓN FATAL: principal android.app.RemoteServiceException: notificación incorrecta para startForeground: java.lang.RuntimeException: canal no válido para notificación de servicio: Notificación (channel = null pri = 0 contentView = null vibrate = null sound = null defaults = 0x0 flags = 0x40 color = 0x00000000 vis = PRIVADO) en android.app.ActivityThread $ H.handleMessage (ActivityThread.java:1821) en android.os.Handler.dispatchMessage (Handler.java:106) en android.os.Looper.loop (Looper. java: 164) en android.app.ActivityThread.main (ActivityThread.java:6626)
Gerry
1

Ok, algo que noté en esto que podría ayudar a algunos otros también. Esto es estrictamente de las pruebas para ver si puedo descubrir cómo solucionar los sucesos que estoy viendo. Por simplicidad, digamos que tengo un método que llama a esto desde el presentador.

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

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

Esto se bloqueará con el mismo error. El Servicio NO comenzará hasta que se complete el método, por lo tanto, no onCreate()en el servicio.

Por lo tanto, incluso si actualiza la interfaz de usuario del hilo principal, SI tiene algo que pueda retrasar ese método después, no comenzará a tiempo y le dará el temido Error de primer plano. En mi caso, estábamos cargando algunas cosas en una cola y cada una llamó startForegroundService, pero había algo de lógica involucrada con cada una en el fondo. Entonces, si la lógica tardó demasiado en terminar ese método, ya que se llamaron consecutivamente, el tiempo de bloqueo. El viejostartService simplemente lo ignoró y siguió su camino y como lo llamábamos cada vez, la siguiente ronda terminaría.

Esto me dejó preguntándome, si llamé al servicio desde un hilo en segundo plano, ¿no podría estar completamente vinculado al inicio y ejecutarse de inmediato, así que comencé a experimentar? Aunque esto NO lo inicia de inmediato, no se bloquea.

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

No voy a pretender saber por qué no se bloquea, aunque sospecho que esto lo obliga a esperar hasta que el hilo principal pueda manejarlo de manera oportuna. Sé que no es ideal vincularlo al hilo principal, pero dado que mi uso lo llama en segundo plano, no estoy realmente preocupado si espera hasta que pueda completarse en lugar de bloquearse.

a54studio
fuente
¿Está comenzando su servicio desde el fondo o en actividad?
Mateen Chaudhry
Antecedentes. El problema es que Android no puede garantizar que el servicio se iniciará en el tiempo asignado. Uno pensaría que simplemente arrojarían un error para ignorarlo si lo desea, pero, por desgracia, no Android. Tiene que ser fatal. Esto ayudó en nuestra aplicación, pero no lo solucionó por completo.
a54studio
1

Estoy agregando un código en la respuesta @humazed. Así que no hay notificación inicial. Puede ser una solución, pero funciona para mí.

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

Estoy agregando transparentColor en pequeño icono y color en la notificación. Funcionará.

KHEM RAJ MEENA
fuente
0

He solucionado el problema al iniciar el servicio en startService(intent)lugar de Context.startForeground()llamar startForegound()inmediatamente después super.OnCreate(). Además, si inicia el servicio en el arranque, puede iniciar la Actividad que inicia el servicio en la transmisión de arranque. Aunque no es una solución permanente, funciona.

melianor
fuente
en algunos dispositivos Xiaomi, las actividades no pueden iniciarse desde el fondo
Mateen Chaudhry
0

Actualización de datos en onStartCommand(...)

onBind (...)

onBind(...)es un mejor evento de ciclo de vida para iniciar startForegroundvs. onCreate(...)porque onBind(...)pasa en un Intentque puede contener datos importantes Bundlenecesarios para inicializar Service. Sin embargo, no es necesario ya queonStartCommand(...) se llama cuando Servicese crea por primera vez o se llama después.

onStartCommand (...)

startForegrounden onStartCommand(...)es importante para actualizar elService vez que ya ha sido creado.

Cuando ContextCompat.startForegroundService(...)se llama después de que Servicese ha creado un onBind(...)y onCreate(...)no se llama. Por lo tanto, los datos actualizados se pueden pasar a onStartCommand(...)través de Intent Bundlepara actualizar los datos en elService .

Muestra

Estoy usando este patrón para implementar el PlayerNotificationManageren el Coinverse aplicación de noticias criptomoneda.

Activity / 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)
}
Adam Hurwitz
fuente
-1

Servicio

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

    }

}

Ensayador

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

Resultado

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
JeanK
fuente