android - startservice - startforeground notification
Context.startForegroundService() no llamó a Service.startForeground() (29)
Estoy usando
Service
Class en Android O OS.
Planeo usar el
Service
en segundo plano.
La recomendación de Android establece que
startService()
debe usar
startForegroundService()
.
Si usa
startForegroundService()
, el
Service
arroja
Context.startForegroundService () no llamó a Service.startForeground ()
error.
¿Qué tiene de malo esto?
Actualización de datos en
onStartCommand(...)
onBind (...)
onBind(...)
es un mejor evento de ciclo de vida para iniciar
startForeground
vs.
onCreate(...)
porque
onBind(...)
pasa en un
Intent
que puede contener datos importantes
Bundle
necesarios para inicializar
Service
.
Sin embargo, no es necesario, ya que
onStartCommand(...)
se llama cuando
Service
se crea por primera vez o se llama después.
onStartCommand (...)
startForeground
en
onStartCommand(...)
es importante con el fin de actualizar la
Service
vez que ya ha sido creado.
Cuando
ContextCompat.startForegroundService(...)
se llama después de que
Service
se ha creado un
onBind(...)
y
onCreate(...)
no se llama.
Por lo tanto, los datos actualizados se pueden pasar a
onStartCommand(...)
través de
Intent
Bundle
para actualizar los datos en
Service
.
Muestra
Estoy usando este patrón para implementar el
PlayerNotificationManager
en el
Coinverse
aplicación de noticias criptomoneda.
Activity / Fragment.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)
}
AudioService.kt
context.startForegroundService(new Intent(context, TaskQueueExecutorService.class));
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Servicio
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)
}
Ensayador
D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
Resultado
@Override
public void onCreate() {
try{
}
catch(Exception e)
{
}
finally{
startForeground(1, notificationbuilder.build());
}
}
Asegúrese de que todas las rutas de código llamen al método startForeground, por ejemplo, el código puede generar una excepción en el método onCreate de su servicio que evite que se llame a startForeground
@Override public void onCreate() { try{ } catch(Exception e) { } finally{ startForeground(1, notificationbuilder.build()); } }
Después de llamar a StartForgroundService, debe llamar a StartForground en su servicio dentro de los 5 segundos para mostrar una notificación.
Si su aplicación está dirigida al nivel de API 26 o superior, debe usar los servicios de primer plano y mostrar una notificación cuando desee que su servicio continúe ejecutándose incluso cuando el usuario no interactúa con la aplicación. Debido a que el sistema impone restricciones para ejecutar servicios en segundo plano cuando la aplicación en sí no está en primer plano. En la mayoría de los casos como este, su aplicación debería usar un trabajo programado.
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í.
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()
})
})
Estoy agregando transparentColor en un pequeño ícono y color en la notificación. Funcionará.
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.
Incluso después de llamar al
startForeground
en
Service
, Se estrella en algunos dispositivos si llamamos
stopService
justo antes de
onCreate
que se llama.
Entonces, solucioné este problema al iniciar el servicio con un indicador adicional:
@Override
public int onStartCommand(Intent intent,
int flags, int startId) {
//call startForeground first
boolean stopService = false;
if (intent != null) {
stopService = intent.getBooleanExtra("request_stop", false);
}
if (stopService) {
stopSelf();
return START_STICKY;
}
//Continue with the background task
return START_STICKY;
}
y agregó un control en onStartCommand para ver si realmente comenzó a detenerse:
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);
}
}
PD: si el servicio no se estaba ejecutando, comenzará primero el servicio, que es una sobrecarga.
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 encontrar la manera de solucionar los sucesos que estoy viendo. Por simplicidad, digamos que tengo un método que llama a esto desde el presentador.
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();
}
}
});
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 fueron llamados de forma consecutiva, el tiempo de bloqueo.
El viejo
startService
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 inmediatamente, así que comencé a experimentar? Aunque esto NO lo inicia de inmediato, no se bloquea.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
No pretendo 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.
Sé que es una respuesta tardía, pero creo que podría ayudar en el futuro, simplemente utilicé en
JobIntentService
lugar de
IntentService
eso incluir JobScheduler y manejar todo por mí.
mira este
example
Solo verifico el
PendingIntent
nulo o no antes de llamar a la
context.startForegroundService(service_intent)
función.
esto funciona para mi
import android.app.Notification;
public class AuthenticationService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(1,new Notification());
}
}
Todo esto desaparece si reescribe su servicio para que se inicie con bindService.
Puede ver un ejemplo de cómo hacerlo en: https://github.com/paulpv/ForegroundServiceAPI26/tree/bound
Se puede ver una diferencia de mi rama "repro" en: https://github.com/paulpv/ForegroundServiceAPI26/compare/repro...bound?expand=1
para mí agregué servicio en primer plano en manifiesto en
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)
}
}
}
si funciona, comente a continuación, hágamelo saber
simplemente llame al método startForeground inmediatamente después de crear Service o IntentService. Me gusta 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)
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: llame a
startForeground()
en
onCreate()
para el
Service
que utiliza
Context.startForegroundService()
Consulte también: Límites de ejecución en segundo plano para Android 8.0 (Oreo)
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:
-
startForeground
una notificación debe estar enonCreate
yonStartCommand
, porque si su servicio ya está creado y de alguna manera su actividad está intentando iniciarlo de nuevo, no se llamará aonCreate
. -
el ID de notificación no debe ser 0; de lo contrario, se producirá el mismo bloqueo, aunque no sea la misma razón.
-
stopSelf
debe llamar astartForeground
antes destartForeground
.
Con todo lo anterior, 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 culpa suya, lea issuetracker.google.com/issues/76112072#comment36 y issuetracker.google.com/issues/76112072#comment56 para obtener más información
Este error también ocurre en Android 8+ cuando se llama a Service.startForeground (int id, Notificación de notificación) mientras el id se establece en 0.
id int: el identificador para esta notificación según NotificationManager.notify (int, Notification); no debe ser 0 .
Estoy enfrentando el mismo problema y después de pasar tiempo encontrando una solución, puedes probar el siguiente código.
Si está utilizando el
Service
entonces ponga este código en onCreate, de lo contrario, está usando el
Intent Service
luego coloque este 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);
}
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 de
onCreate()
de
MyForegroundService
porque Android programa la creación del servicio en el controlador de subprocesos principal, pero se llama a
BinderThread
en un
BinderThread
, que es una condición de carrera.
Significa que
MyForegroundService
no tuvo la oportunidad de llamar a
startForeground
lo que causará el bloqueo.
Para solucionar esto, debemos asegurarnos de que
bringDownServiceLocked
no se llame antes que
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));
}
}
Al usar transmisiones
stopReceiver
nos aseguramos de que la transmisión no se pierda y
stopReceiver
reciba la intención de detención tan pronto como se haya registrado en
onCreate()
de
MyForegroundService
.
En este momento ya hemos llamado a
startForeground(...)
.
También tenemos que eliminar esa transmisión fija para evitar que se notifique a stopReceiver la próxima vez.
Tenga en cuenta
que el método
sendStickyBroadcast
está en desuso y lo uso solo como una solución temporal para solucionar este problema.
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
}
}
Llamé a
ContextCompat.startForegroundService(this, intent)
para iniciar el servicio y luego
En servicio en
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);
}
}
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 usar 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:
Intent intent = new Intent(context,
YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);
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 como 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 nuevamente, esto 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.
El qs aún permanece: ¿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 manejar wakelocks y por eso no es diferente de los servicios en primer plano. Desde la implementación, PoV 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. Como 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 podríamos no estar al tanto.
ACTUALIZACIÓN No hay más bloqueos informados 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.
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 un 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)
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);
Solo un aviso porque perdí demasiadas horas en esto.
Seguí recibiendo esta excepción a pesar de que estaba llamando a
startForeground(..)
como lo primero en
onCreate(..)
.
Al final descubrí que el problema fue causado por el uso de
NOTIFICATION_ID = 0
.
Usar cualquier otro valor parece arreglar esto.
Su aplicación se bloqueará si llama a
Context.startForegroundService(...)
y luego llama a
Context.stopService(...)
antes de llamar a
Context.stopService(...)
.
Tengo una clara reproducción aquí ForegroundServiceAPI26
He abierto un error en esto en: issuetracker.google.com/issues/76112072
Se han abierto y cerrado varios errores en esto.
Esperemos que la mía con pasos claros de reproducción haga el corte.
Información proporcionada por el equipo de google
issuetracker.google.com/issues/76112072#comment36
Esto no es un error del 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 que se
startForeground()
, 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 que requieren
startForeground()
, aunque obviamente eso no es lo que tenía en mente.
issuetracker.google.com/issues/76112072#comment56
Hay un par de escenarios diferentes que conducen al mismo resultado aquí.
El problema semántico absoluto, que es simplemente un error al iniciar algo con
startForegroundService()
pero descuidar realmente la transición a primer plano a través de
startForeground()
, 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 ha sido 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.
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
Cadena final estática privada ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel"; privado estático final int NOTIFICATION_ID = 555;
Entonces, todo fue perfecto, pero todavía 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.
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 todas y cada una de las rutas de código se cubrieron con
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 eliminar 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();
}
Tengo una solución para este problema. Verifiqué 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 inicie el servicio, como documenta Google. 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.
-
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);
-
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(); } }
-
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); } } }
-
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.
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 a
Service.startForeground(int, android.app.Notification)
en onCreate () para asegurarse de que se llamará ... si tiene alguna condición que le impida hacerlo, será mejor que lo haga. usando el
Context.startService(Intent)
normal y llame al
Service.startForeground(int, android.app.Notification)
usted mismo.
Parece que
Context.startForegroundService()
agrega un perro guardián para asegurarse de que llamó a
Service.startForeground(int, android.app.Notification)
antes de que fuera destruido ...