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.