Servicio de primer plano siendo asesinado por Android


85

Actualización : no he encontrado una verdadera solución al problema. Lo que se me ocurrió fue un método para volver a conectarme automáticamente a un dispositivo bluetooth anterior cada vez que se pierde la conexión. No es ideal, pero parece funcionar bastante bien. Sin embargo, me encantaría escuchar más sugerencias al respecto.

Tengo el mismo problema que en esta pregunta: el servicio se cancela mientras se mantiene el bloqueo de activación y después de llamar a startForeground, incluido el dispositivo (Asus Transformer), el tiempo antes de que se detenga 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 a otro dispositivo y envía datos entre los dos, por lo que debe estar activa en todo momento para escuchar 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.

Según la respuesta en la pregunta vinculada, startForeground () "reduce la probabilidad de que se elimine un servicio, pero no lo evita". Entiendo que ese es 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 reducirá en gran medida sin la capacidad 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 debo tener en cuenta que esto no parece ocurrir en el otro dispositivo al que estoy conectado: HTC Legend con Cyanogen

EDITAR: Aquí está el resultado 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 la salida 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.


Eche un vistazo a esta respuesta: podría funcionar para usted stackoverflow.com/a/21157035/624109
Muzikant

Respuestas:


218

Okey dokey. He pasado por el infierno y he vuelto a este problema. He aquí cómo proceder. Hay errores. Esta publicación describe cómo analizar errores en la implementación y solucionar problemas.

Solo para resumir, así es como se supone que funcionan las cosas. Los servicios en ejecución se eliminarán de forma rutinaria y finalizarán cada 30 minutos aproximadamente. Los servicios que deseen permanecer activos durante 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 está funcionando permanentemente y potencialmente está consumiendo la vida útil de la batería. Solo 3 procesos de servicio pueden nominarse a sí mismos como servicios de primer plano en un momento dado. Si hay más de tres servicios en primer plano, Android nominará el servicio más antiguo como candidato para eliminación y terminación.

Desafortunadamente, hay errores en Android con respecto a la priorización de los servicios en primer plano, que se desencadenan por varias combinaciones de indicadores de enlace de servicios. Aunque haya designado correctamente su servicio como un servicio en primer plano, Android puede terminar su servicio de todos modos, si alguna vez se ha realizado alguna conexión a los servicios en su proceso con ciertas combinaciones de indicadores 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 duración de algún tipo que los usuarios puedan activar y desactivar, o cancelar. Ejemplos de servicios que necesitan un estado de 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 ocasionalmente, o esperando a los receptores de transmisión del sistema o eventos del sistema, sería mejor que activara su servicio con un temporizador o en respuesta a los receptores de transmisión, y luego deje que su servicio se apague una vez que se complete. Ese es el comportamiento diseñado para los servicios. Si simplemente debes seguir con vida, sigue leyendo.

Después de marcar las casillas de los requisitos bien conocidos (por ejemplo, llamar a Service.startForeground), el siguiente lugar para mirar son las banderas que usa en las llamadas de Context.bindService. Los indicadores utilizados para enlazar afectan la prioridad del proceso de servicio de destino de diversas formas inesperadas. En particular, el uso de ciertos indicadores de enlace puede hacer que Android degrade incorrectamente su servicio de primer plano a un servicio regular. El código utilizado para asignar la prioridad del proceso se ha batido bastante. En particular, hay revisiones en API 14+ que pueden causar errores al usar indicadores de enlace más antiguos; y hay errores definidos en 4.2.1.

Su amigo en todo esto es la utilidad sysdump, que puede usarse para averiguar qué prioridad le ha asignado el administrador de actividades a su proceso de servicio y detectar casos en los que ha asignado una prioridad incorrecta. Ponga en marcha su servicio y luego emita el siguiente comando desde un símbolo del sistema en su computadora host:

adb shell procesos de actividad dumpsys> tmp.txt

Utilice el bloc de notas (no el bloc de notas / escritura) para examinar el contenido.

Primero verifique que ha logrado ejecutar con éxito su servicio en el estado de 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 corresponda a su aplicación en la primera sección del archivo dumpsys:

APP UID 10068 ProcessRecord {41937d40 2205: tunein.service / u0a10068}

Verifique que foregroundServices = true en la siguiente sección. No se preocupe por los ajustes ocultos y vacíos; 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 mirar es la sección cerca del final del archivo titulado "Lista de proceso LRU (ordenada por oom_adj):". Las entradas en esta lista le permiten determinar si Android realmente ha clasificado su aplicación como un servicio de primer plano. Si su proceso está al final de esta lista, es un candidato principal para el exterminio sumario. 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 de primer plano que ha hecho todo bien. El campo clave aquí es el campo "adj =". Eso indica la prioridad que el ActivityManagerService asignó a su proceso después de que todo se dijo hecho. Quiere que sea "adj = prcp" (servicio de primer plano visible); o "adj = vis" (proceso visible con una actividad) o "fore" (proceso con una actividad en primer plano). Si se trata de "adj = svc" (proceso de servicio), "adj = svcb" (¿servicio heredado?) O "adj = bak" (proceso de fondo vacío), entonces su proceso es un candidato probable para la terminación y se cancelará. no menos de cada 30 minutos, incluso si no hay ninguna presión para recuperar la memoria. Las banderas restantes en la línea son en su mayoría información de depuración de diagnóstico para los ingenieros de Google. Las decisiones sobre la terminación se toman en función de 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 según la cual se asignó prioridad al proceso. Por lo general, debe coincidir con el campo adj =; pero el valor adj = se puede ajustar hacia arriba o hacia abajo en algunos casos debido a las banderas de enlace en enlaces activos con otros servicios o actividades.

Si ha tropezado con un error con banderas vinculantes, 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 está configurado incorrectamente en "adj = bak" (proceso de fondo vacío), que se traduce aproximadamente como "por favor, ciérreme ahora para que pueda terminar con esta existencia sin sentido" para fines de búsqueda de procesos. También tenga en cuenta la marca (fg-service) al final de la línea que indica que "las reglas de servicio en tierra se usaron para determinar la configuración" adj ". A pesar de que se usaron las reglas fg-service, a este proceso se le asignó una configuración adj. "bak", y no vivirá por mucho tiempo En pocas palabras, esto es un error.

Por lo tanto, el objetivo es garantizar que su proceso siempre obtenga "adj = prcp" (o mejor). Y el método para lograr ese objetivo es modificar las banderas de enlace hasta que logre evitar errores en la asignación de prioridad.

Estos son los errores que conozco. (1) Si CUALQUIER servicio o actividad se ha vinculado al servicio utilizando Context.BIND_ABOVE_CLIENT, corre el riesgo de que la configuración adj = se reduzca a "bak" incluso si esa vinculación ya no está activa. Esto es particularmente cierto si también tiene enlaces entre servicios. Un error claro en las fuentes 4.2.1. (2) Definitivamente nunca use BIND_ABOVE_CLIENT para un enlace de servicio a servicio. Tampoco lo use para conexiones de actividad a servicio. La bandera utilizada para implementar el comportamiento de BIND_ABOVE_CLIENT parece estar configurada 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 a servicio vinculante con la bandera puesta. También parece haber problemas para establecer la prioridad cuando hay varios servicios en el proceso, con enlaces de servicio a servicio. El uso de Context.BIND_WAIVE_PRIORITY (API 14) en enlaces de 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. Al hacerlo, aumenta la prioridad de su proceso un nivel 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 sus indicadores 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 estados de servicio en primer plano de forma independiente, más un tercero que también puede tomar el estado de servicio en primer plano; dos de los servicios se vinculan entre sí de forma condicional; el tercero se une al primero, siempre. Además, las actividades se ejecutan en un proceso separado (hace que la animación sea más fluida). Ejecutar las actividades y los servicios en el mismo proceso no pareció marcar ninguna diferencia.

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

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

Bon chance.

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 desea que 4 sea 'A' o 'S', y 5 sea "IF" o "IB", y 1 sea lo más bajo posible (probablemente por debajo de 3, ya que solo 3 tres procesos de servicio en primer plano se mantienen activos en la 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 

4
@Robin Davies, tengo una pequeña pregunta. ¿Realmente necesito llamar bindService()si necesito un Servicio en ejecución constante? ¿No es suficiente llamar startForeground()al servicio técnico? Para la comunicación con el servidor utilizo EventBus.
ar-g

Llamas a Context.bindService desde una actividad para que el servicio se ejecute en primer lugar. El método Service.startService se llama mediante código en el servicio para mover un servicio que se ha iniciado al estado de "primer plano". Supongo que la biblioteca EventBus está llamando a Context.bindService en su nombre en algún momento para iniciar el servicio. Si hay otra forma de iniciar un servicio, no la conozco.
Robin Davies

3
¡Buena publicación! Voto a favor. Una pieza que quería agregar a este comentario, que creo que es relevante. Si desea un servicio que se ejecute constantemente, como mencionó Robin, debe iniciarlo de alguna manera. Es posible llamar a startService (Intent service) directamente dentro de su actividad en lugar de bindService (), y luego, una vez que su servicio se inicia, puede llamar al método startForeground (). Llamo a esto en el onStartCommand () de la clase de servicio. Hasta donde yo sé, esto debería hacer que su servicio no esté vinculado, pero mantenerlo en ejecución problemas de recursos pendientes. Ojalá esto ayude a alguien.
Dave

¡¡Buen trabajo!! Me gustaría agregar una actualización a esto. Primero, el formato de salida de adb ha cambiado ligeramente (enero de 2016). He probado este proceso en dos dispositivos LG Volt 4.4.2 y Nexus 5x 6.0.1, ambos dispositivos todavía están afectados por el error. Solo puedo reproducir el problema usando Context.BIND_ABOVE_CLIENT: Proc # 4: cch F / S / SF trm: 0 12354: com.test / u0a78 (fg-service) El uso de la bandera problemática causa una muerte instantánea la mayor parte del tiempo en el antiguo dispositivo después de salir de la actividad. Todas las demás banderas parecen funcionar bien en ambas versiones de Android.
user3259330

1
@Dave hey Dave, utilizo exactamente ese método, además de devolver START_STICKY, pero mi servicio siempre muere después de una hora más o menos cuando el dispositivo está inactivo. ¿Tiene alguna idea de lo que podría estar pasando?
Ruchir Baronia

7

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


¡Gracias por ayudar! Edité mi pregunta con el resultado de los comandos que mencionaste. Parece que indican que el servicio se está ejecutando en primer plano.
howettl

¿Hay alguna sección de código que pueda publicar que pueda ayudar con el diagnóstico?
howettl

1
Definitivamente no debería ser eliminado mientras está en primer plano, y estoy seguro de que cosas como Música en la plataforma estándar no lo son. Considere presentar un error con código para reproducir el problema. Una cosa a tener en cuenta es si se está moviendo dentro y fuera del primer plano en cualquier punto que podría permitir su muerte.
hackbod

1
¿Es posible que la actualización de la notificación en curso llamando a notify () en lugar de llamar a startForeground () de nuevo podría sacarla del estado de primer plano? También tengo FLAG_ALERT_ONLY_ONCE habilitado en la notificación si eso importa.
howettl

2
Definitivamente no lo actualice a través del administrador de notificaciones. Está publicando esto a través del servicio y debe continuar actualizándolo a través del servicio.
hackbod
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.