Servicio de primer plano destruido por Android

Actualización : no he encontrado una verdadera solución al problema. Lo que sí se me ocurrió fue un método de reconexión automática a un dispositivo Bluetooth anterior cada vez que se pierde la conexión. No es ideal, pero parece funcionar bastante bien. Sin embargo, me gustaría escuchar más sugerencias sobre esto.

Estoy teniendo el mismo problema que en esta pregunta: servicio siendo matado mientras se mantiene activado el locking y después de llamar a startForeground, incluido el dispositivo (Asus Transformer), el tiempo antes de detener el servicio (30-45 minutos), el uso de wake lock, el uso de startForeground (), y el hecho de que el problema no ocurre si la aplicación está abierta cuando la pantalla se apaga.

Mi aplicación mantiene una conexión bluetooth con otro dispositivo y envía datos entre los dos, por lo que debe estar activo en todo momento para escuchar los datos. El usuario puede iniciar y detener el servicio a voluntad y, de hecho, esta es la única forma que he implementado para iniciar o detener el servicio. Una vez que se reinicia el servicio, se pierde la conexión bluetooth con el otro dispositivo.

De acuerdo con la respuesta en la pregunta vinculada, startForeground () “reduce la probabilidad de que se mate un servicio, pero no lo impide”. Entiendo que ese sea el caso, sin embargo, he visto muchos ejemplos de otras aplicaciones que no tienen este problema (Tasker, por ejemplo).

La utilidad de mi aplicación se verá enormemente reducida sin la posibilidad de que el servicio se ejecute hasta que el usuario lo detenga. ¿¿¿Hay alguna manera de evitar esto???

Veo esto en mi logcat cada vez que se detiene el servicio:

ActivityManager: No longer want com.howettl.textab (pid 32321): hidden #16 WindowManager: WIN DEATH: Window{40e2d968 com.howettl.textab/com.howettl.textab.TexTab paused=false ActivityManager: Scheduling restart of crashed service com.howettl.textab/.TexTabService in 5000ms 

EDITAR: También debería tener en cuenta que esto no parece ocurrir en el otro dispositivo al que estoy conectado: HTC Legend corriendo Cyanogen

EDITAR: Aquí está la salida de los adb shell dumpsys activity services de adb shell dumpsys activity services :

 * ServiceRecord{40f632e8 com.howettl.textab/.TexTabService} intent={cmp=com.howettl.textab/.TexTabService} packageName=com.howettl.textab processName=com.howettl.textab baseDir=/data/app/com.howettl.textab-1.apk resDir=/data/app/com.howettl.textab-1.apk dataDir=/data/data/com.howettl.textab app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104} isForeground=true foregroundId=2 foregroundNoti=Notification(contentView=com.howettl.textab/0x1090087 vibrate=null,sound=null,defaults=0x0,flags=0x6a) createTime=-25m42s123ms lastActivity=-25m42s27ms executingStart=-25m42s27ms restartTime=-25m42s124ms startRequested=true stopIfKilled=false callStart=true lastStartId=1 Bindings: * IntentBindRecord{40a02618}: intent={cmp=com.howettl.textab/.TexTabService} binder=android.os.BinderProxy@40a9ff70 requested=true received=true hasBound=true doRebind=false * Client AppBindRecord{40a3b780 ProcessRecord{40bb0098 2995:com.howettl.textab/10104}} Per-process Connections: ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8} All Connections: ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8} 

Y el resultado de la adb shell dumpsys activity de adb shell dumpsys activity :

 * TaskRecord{40f5c050 #23 A com.howettl.textab} numActivities=1 rootWasReset=false affinity=com.howettl.textab intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab} realActivity=com.howettl.textab/.TexTab lastActiveTime=4877757 (inactive for 702s) * Hist #1: ActivityRecord{40a776c8 com.howettl.textab/.TexTab} packageName=com.howettl.textab processName=com.howettl.textab launchedFromUid=2000 app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104} Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab } frontOfTask=true task=TaskRecord{40f5c050 #23 A com.howettl.textab} taskAffinity=com.howettl.textab realActivity=com.howettl.textab/.TexTab base=/data/app/com.howettl.textab-1.apk/data/app/com.howettl.textab-1.apk data=/data/data/com.howettl.textab labelRes=0x7f060000 icon=0x7f020000 theme=0x0 stateNotNeeded=false componentSpecified=true isHomeActivity=false configuration={ scale=1.0 imsi=0/0 loc=en_CA touch=3 keys=2/1/1 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=6} launchFailed=false haveState=true icicle=Bundle[mParcelledData.dataSize=1644] state=STOPPED stopped=true delayedResume=false finishing=false keysPaused=false inHistory=true visible=false sleeping=true idle=true fullscreen=true noDisplay=false immersive=false launchMode=2 frozenBeforeDestroy=false thumbnailNeeded=false connections=[ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}] 

 Proc #15: adj=prcp /F 40e75070 959:android.process.acore/10006 (provider) com.android.providers.contacts/.ContactsProvider2<=Proc{40bb0098 2995:com.howettl.textab/10104} Proc #16: adj=bak+2/F 40bb0098 2995:com.howettl.textab/10104 (foreground-service) 

Estos parecen mostrar que el servicio se está ejecutando en primer plano.

Okey dokey. He pasado por el infierno y volví a este problema. A continuación, le indicamos cómo proceder. Hay errores. Esta publicación describe cómo analizar errores en la implementación y solucionar problemas.

Para resumir, así es como se supone que funcionan las cosas. Los servicios en ejecución serán eliminados y terminados rutinariamente cada 30 minutos más o menos. Los servicios que desean permanecer con vida por más tiempo deben llamar a Service.startForeground, que coloca una notificación en la barra de notificaciones para que los usuarios sepan que su servicio se está ejecutando de manera permanente y que potencialmente se está agotando la duración de la batería. Solo 3 procesos de servicio pueden nominarse a sí mismos como servicios en primer plano en cualquier momento dado. Si hay más de tres servicios en primer plano, Android nominará el servicio más antiguo como candidato para eliminación de basura y terminación.

Lamentablemente, existen errores en Android con respecto a la priorización de los servicios de primer plano, que se desencadenan mediante diversas combinaciones de indicadores de enlace de servicio. Aunque haya designado correctamente su servicio como un servicio en primer plano, Android puede terminar su servicio de todos modos, si alguna conexión con servicios en su proceso se ha realizado alguna vez con ciertas combinaciones de banderas vinculantes. Los detalles se dan a continuación.

Tenga en cuenta que muy pocos servicios deben ser servicios de primer plano. En general, solo necesita ser un servicio en primer plano si tiene una conexión a Internet constantemente activa o de larga ejecución de algún tipo que se puede activar o desactivar o cancelar por los usuarios. Ejemplos de servicios que necesitan estado en primer plano: servidores UPNP, descargas de larga duración de archivos muy grandes, sincronización de sistemas de archivos por wi-fi y reproducción de música.

Si solo está sondeando de vez en cuando, o esperando receptores de difusión del sistema o eventos del sistema, sería mejor que despertara su servicio en un temporizador, o en respuesta a los receptores de difusión, y luego dejar que su servicio muera una vez que se complete. Ese es el comportamiento diseñado para los servicios. Si simplemente debes mantenerte vivo, sigue leyendo.

Habiendo marcado las casillas según los requisitos bien conocidos (por ejemplo, llamando a Service.startForeground), el siguiente lugar para buscar es en los indicadores que usa en las llamadas a Context.bindService. Los indicadores utilizados para vincular afectan a la prioridad del proceso de servicio de destino en una variedad de formas inesperadas. Más particularmente, el uso de ciertos indicadores de enlace puede hacer que Android reduzca de forma incorrecta su servicio de primer plano a un servicio regular. El código utilizado para asignar prioridad de proceso ha sido batido bastante. En particular, hay revisiones en la API 14+ que pueden causar errores cuando se usan indicadores de enlace anteriores; y hay errores definidos en 4.2.1.

Su amigo en todo esto es la utilidad sysdump, que se puede usar para determinar qué prioridad ha asignado el administrador de actividades a su proceso de servicio y detectar casos en los que asignó una prioridad incorrecta. Obtenga su servicio en funcionamiento y luego emita el siguiente comando desde el símbolo del sistema en su computadora host:

Procesos de actividad de adb shell dumpsys> tmp.txt

Use el bloc de notas (no el teclado / escritura) para examinar los contenidos.

Primero verifique que haya logrado ejecutar su servicio en primer plano. La primera sección del archivo dumpsys contiene una descripción de las propiedades de ActivityManager para cada proceso. Busque una línea como la siguiente que corresponde a su aplicación en la primera sección del archivo dumpsys:

UID de la aplicación 10068 ProcessRecord {41937d40 2205: tunein.service / u0a10068}

Verifique que foregroundServices = true en la siguiente sección. No te preocupes por la configuración oculta y vacía; describen el estado de las Actividades en el proceso, y no parecen ser particularmente relevantes para procesos con servicios en ellos. Si foregroundService no es verdadero, debe llamar a Service.startForeground para que sea verdadero.

Lo siguiente que debe observar es la sección que se encuentra cerca del final del archivo titulada “Lista de LRU de proceso (ordenada por oom_adj):”. Las entradas en esta lista le permiten determinar si Android realmente ha clasificado su aplicación como un servicio en primer plano. Si su proceso está en la parte inferior de esta lista, es un candidato principal para el exterminio sumrio. Si su proceso está cerca de la parte superior de la lista, es prácticamente indestructible.

Veamos una línea en esta tabla:

  Proc #31: adj=prcp /FS trm= 0 2205:tunein.service/u0a10068 (fg-service) 

Este es un ejemplo de un servicio en primer plano que ha hecho todo bien. El campo clave aquí es el campo “adj =”. Eso indica la prioridad que el ActivityManagerService le asignó a su proceso después de que todo se haya dicho terminado. Desea que sea “adj = prcp” (servicio visible en primer plano); o “adj = vis” (proceso visible con una actividad) o “delantero” (proceso con una actividad de primer plano). Si es “adj = svc” (proceso de servicio), o “adj = svcb” (servicio heredado?), O “adj = bak” (proceso de fondo vacío), entonces su proceso es un posible candidato para la terminación, y será terminado no menos de cada 30 minutos, incluso si no hay presión para reclamar memoria. Los indicadores restantes en la línea son principalmente información de depuración de diagnóstico para los ingenieros de Google. Las decisiones sobre la terminación se toman en base a los campos adj. Brevemente, / FS indica un servicio en primer plano; / FA indica un proceso en primer plano con una actividad. / B indica un servicio en segundo plano. La etiqueta al final indica la regla general bajo la cual se le asignó una prioridad al proceso. Por lo general, debe coincidir con el campo adj =; pero el valor adj = puede ajustarse hacia arriba o hacia abajo en algunos casos debido a indicadores de enlace en enlaces activos con otros servicios o actividades.

Si ha tropezado con un error con banderas de encuadernación, la línea dumpsys se verá así:

  Proc #31: adj=bak /FS trm= 0 2205:tunein.service/u0a10068 (fg-service) 

Observe cómo el valor del campo adj se establece incorrectamente en “adj = bak” (proceso de fondo vacío), que se traduce aproximadamente en “por favor termíneme ahora para que pueda poner fin a esta existencia sin sentido” a los efectos de la eliminación de procesos. Observe también el indicador (fg-service) al final de la línea que indica que “se usaron las reglas del servicio forground para determinar la configuración” adj. “A pesar de que se usaron las reglas del servicio fg, a este proceso se le asignó una configuración adj. “bak”, y no vivirá por mucho tiempo. En pocas palabras, esto es un error.

Entonces, el objective es garantizar que su proceso siempre tenga “adj = prcp” (o mejor). Y el método para lograr ese objective es ajustar las banderas de encuadernación hasta que consiga evitar errores en la asignación de prioridad.

Aquí están los errores que conozco. (1) Si CUALQUIER servicio o actividad alguna vez se ha vinculado al servicio utilizando Context.BIND_ABOVE_CLIENT, corre el riesgo de que la configuración adj = se degradará a “bak” incluso si ese enlace ya no está activo. Esto es particularmente cierto si también tiene enlaces entre servicios. Un error claro en las fonts 4.2.1. (2) Definitivamente nunca use BIND_ABOVE_CLIENT para un enlace servicio-a-servicio. No lo use para conexiones de actividad a servicio tampoco. El indicador utilizado para implementar el comportamiento BIND_ABOVE_CLIENT parece establecerse por proceso, en lugar de por conexión, por lo que desencadena errores con enlaces de servicio a servicio incluso si no hay una actividad activa para el servicio. vinculante con el conjunto de banderas. También parece haber problemas para establecer prioridades cuando hay múltiples servicios en el proceso, con enlaces de servicio a servicio. El uso de Context.BIND_WAIVE_PRIORITY (API 14) en los enlaces servicio-a-servicio parece ayudar. Context.BIND_IMPORTANT parece ser una idea más o menos buena cuando se vincula desde una actividad a un servicio. Si lo hace, la prioridad de su proceso se eleva un escalón más alto cuando la actividad está en primer plano, sin causar ningún daño aparente cuando la actividad se detiene o finaliza.

Pero, en general, la estrategia es ajustar las banderas bindService hasta que sysdump indique que su proceso ha recibido la prioridad correcta.

Para mis propósitos, usando Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT para enlaces de actividad a servicio y Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY para enlaces de servicio a servicio parece hacer lo correcto. Su kilometraje puede diferir.

Mi aplicación es bastante compleja: dos servicios en segundo plano, cada uno de los cuales puede tener independientemente estados de servicio en primer plano, más un tercero que también puede tomar el estado de servicio en primer plano; dos de los servicios se vinculan entre sí condicionalmente; el tercero se une al primero, siempre. Además, Activites se ejecuta en un proceso separado (hace que la animación sea más fluida). La ejecución de las actividades y servicios en el mismo proceso no pareció hacer ninguna diferencia.

La implementación de las reglas para los procesos de eliminación (y el código fuente utilizado para generar los contenidos de los archivos sysdump) se puede encontrar en el archivo de Android central

 frameworks\base\services\java\com\android\server\am\ActivityManagerService.java. 

Bon oportunidad.

PD: Aquí está la interpretación de las cadenas de sysdump para Android 5.0. No he trabajado con ellos, así que haz de ellos lo que quieras. Creo que quiere que 4 sea ‘A’ o ‘S’, y 5 sea “SI” o “IB”, y 1 sea lo más bajo posible (probablemente por debajo de 3, ya que solo 3 procesos de servicio en primer plano se mantienen activos en configuración predeterminada).

 Example: Proc # : prcp F/S/IF trm: 0 31719: neirotech.cerebrum.attention:blePrcs/u0a77 (fg-service) Format: Proc # {1}: {2} {3}/{4}/{5} trm: {6} {7}: {8}/{9} ({10} 1: Order in list: lower is less likely to get trimmed. 2: Not sure. 3: B: Process.THREAD_GROUP_BG_NONINTERACTIVE F: Process.THREAD_GROUP_DEFAULT 4: A: Foreground Activity S: Foreground Service ' ': Other. 5: -1: procState = "N "; ActivityManager.PROCESS_STATE_PERSISTENT: procState = "P "; ActivityManager.PROCESS_STATE_PERSISTENT_UI:procState = "PU"; ActivityManager.PROCESS_STATE_TOP: procState = "T "; ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: procState = "IF"; ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: procState = "IB"; ActivityManager.PROCESS_STATE_BACKUP:procState = "BU"; ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: procState = "HW"; ActivityManager.PROCESS_STATE_SERVICE: procState = "S "; ActivityManager.PROCESS_STATE_RECEIVER: procState = "R "; ActivityManager.PROCESS_STATE_HOME: procState = "HO"; ActivityManager.PROCESS_STATE_LAST_ACTIVITY: procState = "LA"; ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: procState = "CA"; ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: procState = "Ca"; ActivityManager.PROCESS_STATE_CACHED_EMPTY: procState = "CE"; {6}: trimMemoryLevel {8} Process ID. {9} process name {10} appUid 

Si dice “ya no desea …”, entonces ese proceso no tiene un servicio activo en ese momento que esté actualmente en el estado startForeground (). Verifique que su llamada a eso sea exitosa, que está viendo la notificación publicada, que en ese momento no hay mensajes en el registro quejándose de algo, etc. También use “servicios de actividad de adb shell dumpsys” para ver el estado de su servicio y asegúrese de que esté marcado como primer plano. Además, si está en primer plano correctamente, en la salida de “actividad de shell dumpsys de adb” verá en la sección que muestra el ajuste de OOM de los procesos en los que su proceso se encuentra actualmente en primer plano debido a ese servicio.