Email copiado — support@tuurt.com
Cargando experiencia
kotlin · 08 de octubre de 2026 · 7 min

WorkManager en Android: por que una tarea diferida no corre cuando el sistema entra en modo Doze

WorkManager no promete ejecutar un trabajo en el momento que pedimos, sino dentro de las reglas que Doze y App Standby le imponen al sistema. Vemos como funciona esa negociacion y que combinacion de causas retraso una sincronizacion horaria hasta cada tres o cuatro horas.

Por Equipo Tuurt

WorkManager en Android: por qué una tarea diferida no corre cuando el sistema entra en modo Doze

WorkManager se presenta como la forma recomendada de programar trabajo en segundo plano en Android: sincronizar datos, subir un archivo pendiente, limpiar una caché. La promesa implícita es que, si el dispositivo cumple las restricciones que declaramos, el sistema va a ejecutar esa tarea en algún momento razonable. Lo que esa promesa no dice con la misma claridad es que "en algún momento razonable" puede significar minutos, o puede significar horas, dependiendo de qué tan quieto esté el dispositivo. La primera vez que un equipo ve una tarea con setInitialDelay de quince minutos ejecutarse recién dos horas después, la reacción habitual es pensar que WorkManager tiene un bug. No lo tiene: está respetando exactamente las reglas que Doze le impone, y esas reglas no están escritas en la documentación de WorkManager sino en la de administración de energía del sistema.

Qué es Doze y qué restringe en realidad

Doze es un modo de ahorro de energía que el sistema activa cuando el dispositivo lleva un rato sin moverse, con la pantalla apagada y sin estar conectado a un cargador. No hace falta que pase mucho tiempo: en los primeros minutos el sistema ya empieza a aplicar restricciones ligeras, y si esa quietud se sostiene, entra en un Doze completo con restricciones mucho más agresivas sobre acceso a red, wakelocks, sincronizaciones y, lo que nos interesa aquí, la ejecución de jobs y alarmas.

Dentro de Doze completo, el sistema abre periódicamente ventanas de mantenimiento cortas en las que suspende las restricciones, deja que los jobs pendientes corran, que la red esté disponible y que las sincronizaciones se pongan al día. Entre una ventana y la siguiente, el intervalo crece: las primeras ventanas están relativamente cerca unas de otras, pero cuanto más tiempo sigue el dispositivo quieto, más se espacian. Un trabajo que quedó listo para ejecutarse justo después de que terminó una ventana de mantenimiento no corre en ese momento: espera a la siguiente, y esa siguiente puede estar bastante más lejos en el tiempo de lo que el setInitialDelay original hacía suponer.

App Standby Buckets: una segunda restricción, independiente de Doze

Doze depende del estado físico del dispositivo -quieto, pantalla apagada, sin cargador- y afecta a todas las apps por igual mientras ese estado se mantiene. App Standby es otra cosa: clasifica cada app en un bucket de uso -activa, working set, frecuente, rara, o restringida- según cuánto y cómo la usa la persona, sin que el dispositivo necesite estar en Doze para que aplique. Una app que la persona no abre hace varios días cae en un bucket más restrictivo, y el sistema limita cuántas veces al día esa app puede ejecutar jobs en segundo plano o acceder a la red, sin relación directa con si el teléfono está quieto o en uso en ese momento.

Esto importa porque son dos mecanismos distintos que se aplican a la vez. Una tarea puede estar bloqueada por Doze, por el bucket de standby de la app, o por ambos combinados. Diagnosticar "por qué no corrió" exige revisar los dos, no solo uno.

Cómo WorkManager negocia esas restricciones

WorkManager no es el mecanismo de ejecución: es una capa que elige, según la versión de Android disponible, qué mecanismo del sistema usar por debajo, y ese mecanismo en la inmensa mayoría de los dispositivos actuales es JobScheduler. Esa elección es la que explica el comportamiento, porque JobScheduler fue diseñado desde el principio para ser compatible con Doze: un job programado a través de JobScheduler queda automáticamente diferido hasta la próxima ventana de mantenimiento si el sistema está en Doze completo cuando ese job debería correr. No hace falta declarar nada especial para obtener ese comportamiento; es el comportamiento por defecto.

Esto es, en el fondo, una negociación en la dirección correcta: WorkManager no promete ejecutar la tarea en el momento exacto que pedimos, promete ejecutarla quedando dentro de las reglas del sistema, lo cual evita que la app se pelee con el administrador de energía y termine en una lista de apps que gastan batería de forma agresiva. El costo de esa negociación es que cualquier initialDelay, cualquier intervalo de un PeriodicWorkRequest, es un mínimo, no una garantía: el trabajo no va a correr antes de ese tiempo, pero puede correr bastante después si coincide con un tramo de Doze profundo.

Para trabajo que sí necesita ejecutarse con una latencia menor, desde WorkManager 2.7 existe el trabajo expedito (setExpedited), que en dispositivos con Android 12 o superior usa jobs expeditos de JobScheduler con una prioridad de ejecución más alta y menos sujeta a las ventanas de mantenimiento, dentro de una cuota diaria limitada que el sistema asigna según el bucket de standby de la app. En versiones anteriores, WorkManager simula ese comportamiento con un servicio en primer plano de corta duración. Expedited no es una forma de evitar Doze por completo: es una forma de pedir prioridad dentro de un presupuesto que el sistema sigue controlando.

Por qué usar AlarmManager directo no resuelve el problema de raíz

Una reacción común al ver una tarea diferida de más es abandonar WorkManager y programar la alarma directamente con AlarmManager, usando setExactAndAllowWhileIdle para forzar que corra incluso dentro de Doze. Funciona, pero con una restricción que suele descubrirse tarde: el sistema limita a una alarma exacta de este tipo cada nueve minutos por aplicación durante Doze. Si el caso de uso necesita varias ejecuciones espaciadas con menos margen que eso, la mayoría se van a diferir igual, solo que ahora gestionadas por un mecanismo que nosotros mismos tenemos que mantener, en lugar de uno que el sistema ya resuelve por nosotros. AlarmManager resuelve el síntoma de un caso puntual -una alarma aislada que de verdad necesita sonar en un momento preciso, como un recordatorio- pero no es un reemplazo general de WorkManager para trabajo recurrente en segundo plano.

Lo que encontramos al revisar un caso de retraso real

Al revisar un PeriodicWorkRequest que debía sincronizar datos cada hora y que en la práctica corría cada tres o cuatro horas cuando el dispositivo pasaba la noche quieto, la causa no fue una sola. El intervalo de una hora ya convive mal con Doze profundo, porque las ventanas de mantenimiento durante la noche pueden espaciarse bastante más que eso. A eso se sumó una restricción de red (setRequiredNetworkType(NetworkType.UNMETERED)) que, combinada con que el teléfono de prueba tenía el wifi configurado para apagarse con la pantalla, dejaba la tarea sin la red que pedía incluso en las ventanas de mantenimiento en las que sí se la intentaba ejecutar: el job se marcaba como listo, la restricción de red no se cumplía, y volvía a quedar en espera hasta la próxima ventana. Ninguna de las dos causas por separado explicaba el retraso completo; juntas sí.

Qué revisamos antes de asumir que una tarea diferida se va a ejecutar a tiempo

Antes de dar por buena la programación de un trabajo en segundo plano, confirmamos qué mecanismo usa WorkManager por debajo en las versiones de Android que el proyecto soporta, porque el comportamiento frente a Doze no es el mismo en todas; revisamos si las constraints declaradas -tipo de red, carga, almacenamiento- son razonables de cumplir durante una ventana de mantenimiento nocturna y no solo durante el uso activo del dispositivo; separamos qué trabajo tolera de verdad ejecutarse con horas de margen de cuál necesita trabajo expedito o, en casos puntuales y aislados, una alarma exacta con su límite de frecuencia asumido; y probamos el comportamiento real con el dispositivo en reposo prolongado usando adb shell dumpsys deviceidle force-idle, en vez de confiar en que el comportamiento en un emulador con la pantalla encendida represente lo que va a pasar en el teléfono de una persona que lo deja quieto toda la noche.

android workmanager doze background-work
← Volver al blog