Vinculaciones del servicio y estados del proceso

Los procesos de apps en Android no existen de forma aislada. Las aplicaciones suelen depender de los servicios que proporcionan otras aplicaciones o el propio sistema. Cuando un proceso se conecta a otro a través de una vinculación de servicio, se crea una dependencia que tiene un gran impacto en la forma en que el framework de Android administra la memoria.

Estados del proceso y puntuaciones OOM

El framework de Android usa estados del proceso para hacer un seguimiento de la importancia de cada proceso en ejecución. Luego, OomAdjuster usa estos estados para asignar un valor de ajuste de puntuación OOM (oom_score_adj), que varía de -1000 a 1000.

Un valor oom_score_adj más bajo significa que el proceso es más importante y es menos probable que Low Memory Killer (LMK) lo cierre.

Estados de proceso comunes

En la siguiente tabla, se muestran algunos de los estados de proceso más comunes y sus valores oom_score_adj típicos. Para obtener una lista completa y actualizada, consulta android.app.ActivityManager y com.android.server.am.psc.Constants en el código fuente de Android.

Estado del proceso (abreviatura) Descripción oom_score_adj habitual
PER (persistente) Procesos del sistema que siempre deben ejecutarse (p. ej., telefonía) -800
TOP El proceso con el que el usuario interactúa actualmente 0
VIS (visible) El proceso tiene una actividad visible (p. ej., detrás de un diálogo translúcido) 100
PERC (perceptible) Proceso en segundo plano que el usuario conoce (p. ej., reproducción de música) 200
FGS Proceso que aloja un servicio en primer plano De 0 a 200 (varía)
BTOP (TOP vinculado) Proceso vinculado por una aplicación TOP 100
BFGS Servicio en primer plano vinculado (por lo general, vinculado al sistema) 0
PREV (anterior) El último proceso en el que estuvo el usuario antes del actual 700
CACHED Apps en segundo plano que se pueden cerrar de forma segura De 900 a 999

El impacto de las vinculaciones de servicios

Cuando un proceso cliente (p.ej., una app en el estado TOP) se vincula a un servicio en un proceso servidor, este último suele heredar una prioridad elevada. Esto garantiza que el servicio permanezca disponible mientras el cliente lo necesite.

Diagrama que muestra el proceso A (TOP) que llama a bindService() a través de system_server y eleva el proceso B a BTOP

Controla la herencia con marcas BIND

La herencia es el comportamiento predeterminado cuando se usa Context.BIND_AUTO_CREATE. Sin embargo, los desarrolladores pueden controlar cómo la vinculación afecta la importancia del proceso de destino con varias marcas en bindService().

Marcas BIND clave para la puntuación OOM

Las siguientes marcas son más relevantes cuando se administra la presión de memoria en todo el sistema:

  • BIND_AUTO_CREATE: Es la marca más común. Garantiza que el proceso de servicio se inicie y se mantenga activo mientras exista la vinculación. De forma predeterminada, también eleva la prioridad del proceso servidor para que coincida con el cliente.
  • BIND_NOT_FOREGROUND: Evita que el proceso del servicio de destino se eleve a la prioridad de programación en primer plano (prioridad de CPU). Sin embargo, aún permite que se eleve la prioridad de memoria (oom_score_adj). Esto es útil para el trabajo en segundo plano que no debe competir con la IU por los ciclos de CPU, pero que debe protegerse para que no se cierre.
  • BIND_WAIVE_PRIORITY: Es una marca muy potente que le indica al sistema que no afecte la prioridad de programación ni de administración de memoria del proceso de destino. El proceso de servicio se administrará como si fuera un proceso en segundo plano normal en la lista LRU, lo que lo hace apto para el cierre de OOM incluso cuando está vinculado.
  • BIND_ABOVE_CLIENT: Indica que el servicio es más importante que la app cliente. Cuando el sistema necesite recuperar memoria, preferirá cerrar la app cliente antes que el servicio vinculado. Esto es "más potente" que BIND_AUTO_CREATE, ya que proporciona una capa adicional de protección para el servicio a expensas del cliente.
  • BIND_NOT_PERCEPTIBLE: Reduce la importancia del servicio de destino por debajo del nivel PERCEPTIBLE, lo que permite que el sistema recupere su memoria para dejar espacio para procesos más críticos y perceptibles por el usuario.

Práctica: Observa los efectos de la vinculación

Usaremos la aplicación MemoryLab para demostrar cómo una vinculación de una app TOP afecta el estado de un proceso independiente.

1. Inicia MemoryLab

El siguiente comando inicia la app. Después de que se abra, asegúrate de que la app permanezca en primer plano (no presiones Inicio ni cambies de app todavía).

adb shell am start -n com.android.memorylab/.MainActivity

2. Identifica procesos

Verifica los estados del proceso antes de la vinculación. MemoryLab ejecuta su IU principal en un proceso y tiene un RemoteService que se ejecuta en un proceso :remote.

adb shell dumpsys activity processes com.android.memorylab

Fragmento de resultado de muestra:

  Process OOM control (154 total, non-act at 7, non-svc at 7):
    Proc #0: fg       T/A/TOP  LCMNFUATI  t: 0 13470:com.android.memorylab/u0a417 (top-activity)
        oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
        state: cur=TOP  set=TOP   lastRss=0.00 lastCachedRss=0.00

Verás el proceso principal com.android.memorylab en el estado TOP. El proceso :remote aún no se inició.

3. Activa la vinculación

Envía una transmisión a la app para activar la vinculación del servicio:

adb shell am broadcast -a com.android.memorylab.LEAK_BINDER

4. Observa el estado elevado

Vuelve a verificar los estados del proceso:

adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"

Fragmento de resultado de muestra:

  Proc #  1: vis      F/ /BTOP ---NFUATI  t: 0 13560:com.android.memorylab:remote/u0a417 (service)
    com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
    oom: max=1001 curRaw=100 setRaw=100 cur=100 set=100
    state: cur=BTOP set=BTOP  lastRss=0.00 lastCachedRss=0.00

El proceso :remote ahora se está ejecutando y está en el estado BTOP (TOP vinculado) con un oom_score_adj de 100. Esto está mucho más protegido que un servicio en segundo plano típico (que estaría en 500 o más). La notación <=Proc{...} muestra qué proceso es responsable de esta elevación de prioridad.

5. Envía al segundo plano

Presiona el botón INICIO en el dispositivo. Vuelve a verificar los estados:

adb shell dumpsys activity processes | grep -A 10 "com.android.memorylab"

Fragmento de resultado de muestra:

    Proc #  2: prev     b/ /LAST --------I  t: 0 13560:com.android.memorylab:remote/u0a417 (service)
        com.android.memorylab/.RemoteService<=Proc{13470:com.android.memorylab/u0a417}
        oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
        state: cur=LAST set=LAST  lastRss=0.00 lastCachedRss=0.00
    Proc #  1: prev     b/ /LAST --------I  t: 0 13470:com.android.memorylab/u0a417 (previous)
        oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
        state: cur=LAST set=LAST  lastRss=209MB lastCachedRss=0.00

Ahora, ambos procesos cambiaron a un estado de prioridad más baja (PREV / oom_score_adj 700), porque el proceso cliente ya no es TOP. (Nota: LAST en el volcado de estado hace referencia al estado interno LAST_ACTIVITY, que se asigna a PREV en los resúmenes de alto nivel).

Analiza con procstats

La herramienta procstats proporciona una vista histórica de estos estados.

# View stats for MemoryLab over the last hour
adb shell dumpsys procstats --hours 1 com.android.memorylab

Fragmento de resultado de muestra:

  *   com.android.memorylab / u0a417 / v37:
      *   Prc com.android.memorylab / u0a417 / v37:
             TOTAL: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
               Top: 0.89% (0.00-0.00-0.00/0.00-0.00-0.00/210MB-210MB-210MB over 1)
      *   Prc com.android.memorylab:remote / u0a417 / v37:
             TOTAL: 0.19%
           Bnd Top: 0.19%

Aquí, Bnd Top indica el porcentaje de tiempo que el proceso remoto pasó vinculado por una aplicación en el estado TOP.

Captura y analiza vinculaciones con Perfetto

Si bien dumpsys te brinda una instantánea, Perfetto te permite ver el momento exacto en que se produce una vinculación y cómo cambia la puntuación OOM en tiempo real.

1. Registra un seguimiento

Usa una configuración que incluya linux.process_stats y la categoría am de atrace:

adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/service_bindings.perfetto-trace <<EOF
buffers: { size_kb: 65536 }
data_sources: {
    config {
        name: "linux.process_stats"
        process_stats_config { proc_stats_poll_ms: 100 }
    }
}
data_sources: {
    config {
        name: "linux.ftrace"
        ftrace_config { ftrace_events: "am/am_proc_bound" }
    }
}
duration_ms: 15000
EOF

2. Consulta las transiciones de puntuación OOM

Con PerfettoSQL, puedes ver cómo cambió la puntuación OOM del proceso remoto en relación con el proceso de la IU:

SELECT ts, p.name, value AS oom_score_adj
FROM counter c
JOIN process_counter_track t ON c.track_id = t.id
JOIN process p USING (upid)
WHERE p.name LIKE 'com.android.memorylab%'
  AND t.name = 'oom_score_adj'
ORDER BY ts;

3. Identifica eventos de vinculación

Para ver exactamente cuándo se estableció una dependencia de vinculación y qué proceso la inició, usa esta consulta:

SELECT
    s.ts,
    p.name AS process_name,
    t.name AS thread_name,
    s.name AS slice_name
FROM slice s
JOIN thread_track tt ON s.track_id = tt.id
JOIN thread t USING (utid)
JOIN process p USING (upid)
WHERE s.name LIKE 'bindService:{com.android.memorylab%';

Vinculaciones del sistema a la app

El sistema Android suele vincularse a servicios en apps de terceros para proporcionar funcionalidad principal. Por lo general, el objetivo de estas vinculaciones es la reducción de la latencia. Al mantener un proceso activo y en la memoria, el sistema evita la costosa sobrecarga de un "inicio en frío" (cargar el APK, inicializar el tiempo de ejecución y crear el objeto Application) cuando se produce una interacción crítica del usuario. Existen otras vinculaciones para evitar inicios en frío frecuentes para las apps que necesitan controlar flujos de eventos en segundo plano.

Estos son algunos ejemplos del mundo real que puedes observar en un dispositivo típico:

VoiceInteractor

Los usuarios esperan que un asistente digital esté integrado en el SO de su teléfono, que puedan invocarlo al instante con una palabra clave hablada o un gesto de entrada rápida, y que la interacción sea fluida y sin problemas.

Cuando se activa un asistente (como la palabra clave "OK Google" en los teléfonos Google Pixel), el asistente digital debe responder al instante. Para garantizar esto, system_server mantiene una vinculación permanente con el servicio de interacción por voz que selecciona el usuario.

Diagrama que muestra la vinculación de system_server al proceso del interactor de la app de Google

Si verificas los estados del proceso (p.ej., con dumpsys activity processes), es posible que veas un proceso como com.google.android.googlequicksearchbox:interactor en el estado BFGS (servicio en primer plano vinculado), que se mantiene activo mediante una vinculación de system_server (UID 1000).

NotificationListenerService

Para algunas vinculaciones del sistema a la app, el objetivo no es la latencia, sino evitar inicios en frío frecuentes. NotificationListenerService, un servicio que recibe llamadas del sistema cuando se publican o quitan notificaciones nuevas, es un excelente ejemplo. Un usuario típico de smartphone puede recibir cientos de notificaciones durante el día. Si el sistema se desvincula de un objeto de escucha de notificaciones, es probable que el proceso de esa app pase al estado almacenado en caché y que LMK lo cierre.

Cuando llegue la siguiente notificación (posiblemente segundos después), el sistema se verá obligado a iniciar en frío el proceso de la app nuevamente solo para entregar el evento. Este ciclo constante de cierre e inicio en frío consumiría mucha más CPU y batería que simplemente mantener el proceso vinculado y activo en segundo plano.

La "pantalla -1" del selector (feed de noticias)

Las apps de selector modernas suelen combinar la funcionalidad de navegación principal (íconos y widgets de la pantalla principal) con un feed de noticias que está disponible en una de las pantallas del selector y que se integra sin problemas con la UX del selector. El feed de noticias puede proporcionarlo otra app. Por ejemplo, en Google Pixel, el selector se integra con un feed que proporciona la app de Google.

Cuando deslizas el dedo hacia la izquierda en la pantalla principal para ver el feed de noticias, la transición debe ser fluida. El selector lo logra vinculándose a una interfaz de servicio en la app que proporciona el feed de noticias y manteniendo esa vinculación activa mientras el selector esté activo. Esto mantiene el contenido del feed renderizado y listo en la memoria, incluso cuando no lo estás mirando.

Otros ejemplos comunes

  • El selector (HOME_APP_ADJ): La app de selector (Inicio) tiene su propio espacio especial en la lista de prioridades. Si bien no siempre está vinculado por un servicio, se le asigna el HOME_APP_ADJ (por lo general, 600). El sistema prefiere mantener activo el selector, ya que el usuario vuelve a él con frecuencia. De hecho, el sistema preferiría cerrar la app usada anteriormente (PREV_APP_ADJ = 700) que el selector, ya que cerrar el selector generaría una experiencia del usuario lenta cuando se salga de cualquier app, ya que el usuario deberá esperar a que se inicie en frío el selector.
  • Editor de método de entrada (IME): Cuando escribes, el sistema se vincula a la app de teclado elegida (p.ej., Gboard). Esto mantiene el proceso del teclado en un estado elevado, incluso si el teclado está oculto temporalmente. Esto garantiza que el teclado pueda volver a aparecer al instante cuando presiones otro campo de texto.
  • Pagos NFC: Cuando acercas el teléfono para pagar, el sistema se vincula al servicio de pago NFC (p.ej., Billetera de Google). Estas transacciones suelen tener requisitos estrictos en tiempo real desde la terminal del comercio. Si la app de pago tuviera que iniciarse en frío, la transacción podría agotarse y fallar.

Compensaciones y el límite de rendimiento

Si bien las vinculaciones son necesarias para el rendimiento y la exactitud, tienen un costo para el estado de la memoria del sistema.

  • Flexibilidad reducida: Cada proceso vinculado es un proceso que LMK no puede cerrar fácilmente. Esto reduce el "colchón" de procesos almacenados en caché que el sistema puede usar para liberar memoria bajo presión.
  • Agrava el límite de rendimiento: Si se vinculan demasiados procesos, es posible que el sistema no tenga casi ningún proceso en segundo plano que se pueda cerrar. Cuando aumenta la presión de memoria, el sistema "se caerá del límite de rendimiento" mucho más rápido, ya que se ve obligado a cerrar procesos más importantes o a vaciar la caché de páginas.

← Localidad | ↑ Arriba | En todo el sistema →