Background services en Android: una negociación constante con el sistema operativo
Por qué mantener un proceso vivo en segundo plano en Android no es un problema de código, sino de negociar garantías con un sistema diseñado para matar procesos.
Cuando alguien sin experiencia en Android escucha "necesitamos un servicio en segundo plano", piensa en un problema de código: escribir una clase, registrarla en el manifiesto, listo. La realidad es distinta. En Android, un servicio en segundo plano no es algo que se implementa, sino algo que se negocia con un sistema operativo cuyo trabajo explícito es matar procesos que no están en pantalla.
El sistema no es tu enemigo, pero actúa como tal
Desde Android 6 (Doze mode) en adelante, el sistema agrupa aplicaciones inactivas, restringe su acceso a red, pospone sus alarmas y, si detecta presión de memoria, las mata sin aviso. Esto no es un bug ni una configuración agresiva de un fabricante en particular. Es el comportamiento esperado, diseñado para proteger batería y memoria en un ecosistema con miles de apps compitiendo por los mismos recursos limitados.
El problema aparece cuando tu aplicación necesita hacer algo que el sistema no puede diferenciar fácilmente de "spam en segundo plano": escuchar eventos, mantener una conexión, reaccionar a algo que ocurre sin que el usuario abra la app. Desde la perspectiva del sistema, esa necesidad legítima es indistinguible de una app maliciosa drenando batería.
Las herramientas no son intercambiables
Android ofrece varias piezas para resolver esto, y el error más común es tratarlas como si fueran equivalentes:
- Foreground service, con una notificación persistente visible, es la única forma de decirle al sistema "esto es intencional y el usuario lo sabe", a cambio de una garantía de vida más larga.
- WorkManager no garantiza ejecución inmediata, garantiza ejecución eventual, incluso si el proceso muere y el dispositivo se reinicia. Es la herramienta correcta cuando "en algún momento" es aceptable y "ahora mismo" no lo es.
- BroadcastReceiver + BootReceiver permiten reaccionar a eventos del sistema (una señal recibida, un reinicio) sin mantener un proceso vivo todo el tiempo, a cambio de no controlar cuándo exactamente se ejecuta el código.
Ninguna de estas piezas resuelve "mantén mi app viva". Cada una resuelve una garantía distinta y muy específica, y la arquitectura correcta casi siempre combina varias, no elige una.
El verdadero trade-off: latencia vs. batería
La decisión de diseño que más importa no es técnica, es de producto: ¿qué tan rápido necesita reaccionar el sistema, y quién paga el costo de esa rapidez?
Un heartbeat cada 15 segundos da una reacción casi inmediata, pero también un consumo de batería que un usuario notará y que puede llevar al sistema a restringir la app de forma más agresiva. Un heartbeat cada varios minutos ahorra batería, pero introduce una ventana de latencia que puede ser inaceptable si lo que se espera es una reacción en tiempo real a un evento sensible al tiempo.
No existe una respuesta correcta universal. Existe una respuesta correcta para el caso de uso específico, y encontrarla requiere medir, no adivinar: ¿cuál es el costo real de reaccionar tarde? ¿Cuál es el costo real, en quejas de usuarios y en restricciones del sistema, de reaccionar rápido todo el tiempo?
La lección que se generaliza
Este problema, negociar garantías con un entorno que activamente intenta terminar tu proceso, no es exclusivo de Android. Aparece en contenedores que un orquestador puede matar en cualquier momento, en workers serverless con tiempo de ejecución limitado, en pestañas de navegador que se descargan de memoria. La solución nunca es "evitar que me maten". Es diseñar el sistema para que sobrevivir a la muerte del proceso sea parte normal de su funcionamiento, no una excepción.