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

Resuelto NiceGuy asked hace 54 años • 0 respuestas

Estoy usando ServiceClass en el sistema operativo Android O.

Planeo usar el Serviceen segundo plano.

La documentación de Android establece que

Si su aplicación tiene como objetivo el nivel 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 en sí esté en primer plano. Si una aplicación necesita crear un servicio en primer plano, la aplicación debe llamar startForegroundService().

Si usa startForegroundService(), Servicearroja el siguiente error.

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

¿Qué hay de malo en esto?

NiceGuy avatar Jan 01 '70 08:01 NiceGuy
Aceptado

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

El sistema permite que las aplicaciones llamen a Context.startForegroundService() incluso cuando 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: llame startForeground()por onCreate()el Serviceque utilizaContext.startForegroundService()

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

zhuzhumouse avatar Jul 12 '2017 02:07 zhuzhumouse

Llamé ContextCompat.startForegroundService(this, intent)para iniciar el servicio entonces.

En servicioonCreate

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

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

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

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

            startForeground(1, notification);
        }
}
humazed avatar Sep 27 '2017 14:09 humazed

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

Definitivamente se trata de un problema de marco, pero no todos los desarrolladores que se enfrentan a este problema están haciendo lo mejor que pueden:

  1. startForegrounduna notificación debe estar tanto en como onCreateen onStartCommand, porque si su servicio ya está creado y de alguna manera su actividad intenta 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 el mismo motivo.

  3. stopSelfNo se debe llamar 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, la solución alternativa es degradar la versión del 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 siquiera a entender lo que está pasando y no cree que sea su culpa; lea los puntos 36 y 56 para obtener más información.

ZhouX avatar Jun 05 '2018 03:06 ZhouX

Lo sé, ya se han publicado demasiadas respuestas, sin embargo, la verdad es que startForegroundService no se puede arreglar a nivel de aplicación y debes dejar de usarlo. Esa recomendación de Google de usar la API Service#startForeground() dentro de los 5 segundos posteriores a la llamada a Context#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 al servicio de destino que se supone debe llamar a startForeground() en 5 segundos. Si su servicio objetivo no recibió la llamada en 5 segundos, no tendrá suerte y sus usuarios experimentarán una situación ANR. En el seguimiento de tu pila verás 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 analizó la cola aquí, encontró un "abusador" y simplemente lo eliminó. El sistema está feliz y saludable ahora, mientras que los desarrolladores y usuarios no, pero dado que Google limita sus responsabilidades al sistema, ¿por qué deberían preocuparse por los dos últimos? Al parecer no es así. ¿Podrían mejorarlo? Por supuesto, por ejemplo, podrían haber mostrado el cuadro de diálogo "La aplicación está ocupada", pidiéndole al usuario que tome una decisión sobre esperar o cerrar la aplicación, pero para qué molestarse, no es su responsabilidad. Lo principal es que el sistema ahora está sano.

Según mis observaciones, esto sucede relativamente raramente; en mi caso, aproximadamente 1 falla en un mes para 1.000 usuarios. Reproducirlo es imposible, e incluso si se reproduce, no hay nada que puedas hacer para arreglarlo permanentemente.

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

Creo que la acción correcta y honesta por parte de Google sería decirles a todos que startForegourndServcie tiene una deficiencia y no debe usarse.

La pregunta sigue siendo: ¿qué utilizar en su lugar? Afortunadamente para nosotros, ahora existen JobScheduler y JobService, 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 realizar ninguna acción para garantizar que el dispositivo permanezca activo mientras dure el trabajo.

Significa que ya no necesitas preocuparte por manejar 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 servicio del sistema, presumiblemente manejará la cola correctamente y Google nunca cancelará su propio hijo :)

Samsung ha cambiado de startForegroundService a JobScheduler y JobService en su Samsung Accesorio Protocolo (SAP). Es muy útil cuando dispositivos como relojes inteligentes necesitan comunicarse 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 programador publica los trabajos en el hilo principal, esto es posible. Sin embargo, debes recordar que el trabajo se ejecuta en el subproceso principal y descarga todo el material pesado a otros subprocesos 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 subproceso/controlador/AsyncTask de su elección.

El único inconveniente de cambiar a JobScheduler/JobService es que necesitarás refactorizar el código antiguo, y no es divertido. Pasé los últimos dos días haciendo precisamente eso para utilizar la nueva implementación de SAP de Samsung. Veré mis informes de fallos y te avisaré si los vuelvo a ver. Teóricamente no debería suceder, pero siempre hay detalles de los que quizás no somos conscientes.

ACTUALIZACIÓN No más fallas reportadas 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 por todas. Espero que Google/Android lo lea y eventualmente comente/aconseje/proporcione 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 se encuentra a continuación.

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

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

Ahora necesitas descompilar el código de Samsung para ver qué sucede en su interior. 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 busque 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 JobService de Android y este es el que realiza la programación del 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í utiliza JobScheduler de Android para obtener este servicio del sistema y programar un trabajo.

En la llamada requestAgent pasamos mAgentCallback, que es una función de devolución de llamada que recibirá 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, sólo 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 usted necesitaría implementar y que procesa todos los mensajes provenientes de su dispositivo.

En pocas palabras, no es tan simple y requiere algo de investigación interna y codificación, pero funciona y, lo más importante, no falla.

Oleg Gryb avatar Jun 10 '2019 22:06 Oleg Gryb

Su aplicación fallará si llama Context.startForegroundService(...)y luego llama Context.stopService(...)antes de Service.startForeground(...)que lo llamen.

Tengo una reproducción clara aquí ForegroundServiceAPI26

Abrí un error sobre esto en: rastreador de problemas de Google

Se han abierto y cerrado varios errores sobre esto. No se solucionará.

Con suerte, el mío con pasos de reproducción claros funcionará.

Información proporcionada por el equipo de Google.

Rastreador de problemas de Google Comentario 36

Esto no es un error del marco; es intencional. Si la aplicación inicia una instancia de servicio con startForegroundService(), debe realizar la transición de 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 llame a ella, esa promesa no se cumple: esto es un error en la aplicación.

Re #31, publishing a Service that other apps can start directly is fundamentally unsafe. You can mitigate that a bit by treating all start actions of that service as requiring startForeground(), though obviously that may not be what you had in mind.

Google issue tracker Comment 56

There are a couple of different scenarios that lead to the same outcome here.

The outright semantic issue, that it's simply an error to kick something off with startForegroundService() but neglect to actually transition it to foreground via startForeground(), is just that: a semantic issue. That's treated as an app bug, intentionally. Stopping the service before transitioning it to foreground is an app error. That was the crux of the OP, and is why this issue has been marked "working as intended."

However, there are also questions about spurious detection of this problem. That's is being treated as a genuine problem, though it's being tracked separately from this particular bug tracker issue. We aren't deaf to the complaint.

swooby avatar Mar 21 '2018 23:03 swooby