START_STICKY no funciona en Android KitKat

Una de mis aplicaciones tiene un servicio de backgrouod que usa el código de retorno START_STICKY de onStartCommand para reiniciarse automáticamente cuando el sistema lo mata. Parece que esto ya no funciona en Android KitKat. Hay alguna solución para esto ? ¿Debo hacer algo diferente en Kitkat para mantener el servicio en funcionamiento?

Nota: Hay una discusión similar en el grupo de Android-Devlopers acerca de deslizar la aplicación de la lista de aplicaciones recientes se comporta. ¿Podrían estos dos problemas estar relacionados? https://groups.google.com/forum/#!topic/android-developers/H-DSQ4-tiac

Editar: vi que hay errores abiertos en el rastreador de problemas de Android:

https://code.google.com/p/android/issues/detail?id=63793 https://code.google.com/p/android/issues/detail?id=63618

Edit2: Lo mismo ocurre incluso si el servicio se está ejecutando con startForeground , en un proceso separado y con el indicador android:stopWithTask="false" en el archivo AndroidManifest.xml …

Edit3: Más errores relacionados en el rastreador de problemas de Android:

https://code.google.com/p/android/issues/detail?id=62091 https://code.google.com/p/android/issues/detail?id=53313 https://code.google. com / p / android / issues / detail? id = 104308

¿Hay algún tipo de solución para obtener el comportamiento anterior?

Parece que este es un error presente en Android 4.4, lo rodeó con lo siguiente:

 @Override public void onTaskRemoved(Intent rootIntent) { Intent restartService = new Intent(getApplicationContext(), this.getClass()); restartService.setPackage(getPackageName()); PendingIntent restartServicePI = PendingIntent.getService( getApplicationContext(), 1, restartService, PendingIntent.FLAG_ONE_SHOT); AlarmManager alarmService = (AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE); alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() +1000, restartServicePI); } 

Encontré esta respuesta de esta publicación

El problema aquí parece no ocurrir en las ROM basadas en AOSP. Es decir, puedo recrear fácilmente esto en una ROM basada en CyanogenMod 11, pero en una ROM AOSP (y en un Emulador), START_STICKY se comporta exactamente como esperaba. Dicho esto, estoy viendo informes de personas en Nexus 5 que parecen estar viendo este comportamiento, por lo que tal vez siga siendo un problema en AOSP.

En un emulador y en una ROM AOSP, veo lo siguiente de un logcat cuando hago un ‘kill 5838’ contra el proceso (como era de esperar):

 12-22 18:40:14.237 D/Zygote ( 52): Process 5838 terminated by signal (15) 12-22 18:40:14.247 I/ActivityManager( 362): Process com.xxxx (pid 5838) has died. 12-22 18:40:14.247 W/ActivityManager( 362): Scheduling restart of crashed service com.xxxx/com.xxxx.NotifyingService in 5000ms 12-22 18:40:19.327 I/ActivityManager( 362): Start proc com.xxxx for service xxxx.pro/com.xxxx.NotifyingService: pid=5877 uid=10054 gids={50054, 3003, 3002, 1028} 

Veo el mismo comportamiento de reinicio si finalizo la tarea ‘deslizando’ desde la lista de tareas recientes. Así que todo esto está bien, significa que el código AOSP central se está comportando como lo ha hecho en niveles previos.

Estoy mirando el código de servicio de Cyanogenmod para tratar de descubrir por qué las cosas no están progtwigdas para reiniciarse, todavía no hay suerte. Parece que debería reprogtwigrlo. Cyanogenmod utiliza un mapa de servicio que AOSP no contiene, pero no está claro si eso es un problema o no (dudoso) https://github.com/CyanogenMod/android_frameworks_base/blob/cm-11.0/services/java/com/android/server /am/ActiveServices.java#L2092

Una solución bastante hackish que puede hacer es usar un mecanismo similar a su onTaskRemoved AlarmService para habilitar una alarma por X minutos más tarde. Luego, cada pocos minutos mientras la aplicación está funcionando, puede restablecer la alarma, por lo que solo se activa si las cosas realmente se han cancelado y no se han reiniciado. Esto no es infalible: el uso de un Manejador le brinda tiempo de actividad frente al servicio de alarma que utiliza en tiempo real, por lo que es posible que su alarma se active a pesar de que se configuró en un tiempo más largo que su manejador de “reinicio”. Pero si configura un bash adicional, puede optar por ignorar el onStartCommand si su servicio ya estaba en funcionamiento, convirtiéndolo en un noop.

No soy fanático del siguiente truco en absoluto, pero no debería hacer ningún daño real. Si el usuario hace un cierre forzado explícito, el administrador de alarmas destruirá todas las alarmas establecidas para que el servicio no se reinicie (que es lo que el usuario quiere).

Primero, crea un método de ayuda que establecerá una alarma durante 20 minutos, lo que hará que onStartCommand se active para tu servicio. Cada 2 minutos tiene un controlador que restablecerá la alarma de 20 minutos. Si el controlador se ejecuta dentro de los 20 minutos en tiempo real, la alarma nunca se disparará. Sin embargo, no se garantiza que el controlador se ejecute si el dispositivo está dormido (lo cual es bueno).

 private void ensureServiceStaysRunning() { // KitKat appears to have (in some cases) forgotten how to honor START_STICKY // and if the service is killed, it doesn't restart. On an emulator & AOSP device, it restarts... // on my CM device, it does not - WTF? So, we'll make sure it gets back // up and running in a minimum of 20 minutes. We reset our timer on a handler every // 2 minutes...but since the handler runs on uptime vs. the alarm which is on realtime, // it is entirely possible that the alarm doesn't get reset. So - we make it a noop, // but this will still count against the app as a wakelock when it triggers. Oh well, // it should never cause a device wakeup. We're also at SDK 19 preferred, so the alarm // mgr set algorithm is better on memory consumption which is good. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // A restart intent - this never changes... final int restartAlarmInterval = 20*60*1000; final int resetAlarmTimer = 2*60*1000; final Intent restartIntent = new Intent(this, NotifyingService.class); restartIntent.putExtra("ALARM_RESTART_SERVICE_DIED", true); final AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE); Handler restartServiceHandler = new Handler() { @Override public void handleMessage(Message msg) { // Create a pending intent PendingIntent pintent = PendingIntent.getService(getApplicationContext(), 0, restartIntent, 0); alarmMgr.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + restartAlarmInterval, pintent); sendEmptyMessageDelayed(0, resetAlarmTimer); } }; restartServiceHandler.sendEmptyMessageDelayed(0, 0); } } 

En su onCreate puede llamar a este método. Además, en su onStartCommand, asegúrese de ignorar esto si su servicio ya está funcionando. P.EJ:

 @Override public int onStartCommand(Intent intent, int flags, int startId) { ... if ((intent != null) && (intent.getBooleanExtra("ALARM_RESTART_SERVICE_DIED", false))) { Log.d(TAG, "onStartCommand after ALARM_RESTART_SERVICE_DIED"); if (IS_RUNNING) { Log.d(TAG, "Service already running - return immediately..."); ensureServiceStaysRunning(); return START_STICKY; } } // Do your other onStartCommand stuff.. return START_STICKY; } 

Esta no es una solución al 100% de trabajo, pero es la mejor en la medida en que elimina casi por completo el problema. Hasta ahora onTaskRemoved esta solución junto con la anulación de la onTaskRemoved (ver esta respuesta ) y una notificación de mantener vivo (ver esta respuesta ). ¡Las respuestas adicionales son muy apreciadas!

Después de una investigación más profunda, parece que el error ya existe en Jelly Bean y parece que hay una solución para eso (al menos en mi caso, eso parece funcionar. Seguirá probando y actualizando la respuesta si es necesario).

Por lo que he observado, esto solo ocurre con los servicios que reciben transmisiones establecidas por AlarmManager .

Para reproducir el error, siga estos pasos:

  1. Comienza la aplicación
  2. inicie el servicio como un servicio en primer plano (use startForeground para eso) desde dentro de la aplicación
  3. Desliza la aplicación desde la lista de “Aplicaciones recientes”
  4. Enviar una transmisión manejada por el servicio
  5. ¡El servicio es asesinado!

Usando adb shell dumpsys >C:\dumpsys.txt puede controlar el estado del servicio entre los diferentes pasos. (busque la Process LRU list en la salida dumpsys) en los pasos 2 y 3 verá algo como esto:

 Proc # 2: prcp F/S/IF trm: 0 11073:/u0a102 (fg-service) 

Específicamente, observe el F/S/IF y el (fg-service) que indican que el servicio se está ejecutando como un servicio en primer plano (más detalles sobre cómo analizar los dumpsys en este enlace: https://stackoverflow.com/a/14293528 / 624109 ).

Después del paso 4, no verá su servicio en la Process LRU list . En su lugar, puede ver el logcat del dispositivo y verá lo siguiente:

 I/ActivityManager(449): Killing 11073:/u0a102 (adj 0): remove task 

Lo que parece estar causando ese comportamiento es el hecho de que la transmisión recibida saca el servicio de su estado de primer plano y luego lo mata.

Para evitar eso, puede usar esta solución simple al crear su PendingIntent para AlarmManager (Fuente: https://code.google.com/p/android/issues/detail?id=53313#c7 )

 AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent("YOUR_ACTION_NAME"); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent, 0); 

Presta atención a los siguientes pasos:

  1. Llame a addFlags con la intención y use FLAG_RECEIVER_FOREGROUND
  2. Use un código de solicitud distinto de cero en PendingIntent.getBroadcast

Si deja alguno de estos pasos, no funcionará.

Tenga en cuenta que FLAG_RECEIVER_FOREGROUND se agregó a la API 16 (Jelly Bean) por lo que tiene sentido que este sea el momento en que apareció por primera vez el error …

Lo más probable es que KitKat sea simplemente más agresivo cuando se trata de matar procesos y esta es la razón por la que se enfatizó con KitKat, pero parece que esto ya era relevante en Jelly Bean.

Nota 2: Observe los detalles en la pregunta sobre la configuración del servicio, ejecutándose en un proceso separado, como un servicio en primer plano, con endWithTask establecido en falso en el manifiesto.

Nota 3: Lo mismo sucede cuando la aplicación recibe el mensaje android.appwidget.action.APPWIDGET_CONFIGURE y muestra una actividad de configuración para un nuevo widget (Reemplace el paso 4 anterior con la creación de un nuevo widget). Descubrí que solo ocurre cuando el proveedor de widgets (el receptor que maneja android.appwidget.action.APPWIDGET_UPDATE ) está configurado para ejecutarse en un proceso diferente al proceso de actividad. Después de cambiar eso para que tanto la actividad de configuración como el proveedor de widgets estén en el mismo proceso, esto ya no ocurre.

Encontré este truco simple para resolver este problema sin usar AlarmManager.

  1. crear un receptor de difusión que escuche la emisión cada vez que se onDestroy() método onDestroy() en el servicio:

     public class RestartService extends BroadcastReceiver { private static final String TAG = "RestartService"; public RestartService() { } @Override public void onReceive(Context context, Intent intent) { Log.e(TAG, "onReceive"); context.startService(new Intent(context, YourService.class)); } } 
  2. agregar intención de transmisión personalizada a su manifiesto

          
  3. luego, envíe la transmisión desde onDestroy() , probablemente así:

     @Override public void onDestroy() { Intent intent = new Intent("restartApps"); sendBroadcast(intent); super.onDestroy(); stopThread(); } 
  4. onDestroy() desde onTaskRemoved(Intent intent)

Este truco reiniciará su servicio cada vez que el usuario cierre el servicio desde el administrador de tareas y fuerce el cierre de la configuración. Espero que esto también lo ayude.

Intereting Posts