Doze y App Standby: el teléfono quieto y el reloj roto
Cuando el dispositivo lleva un rato inmóvil, con la pantalla apagada y sin cargar, la plataforma entra en un régimen de suspensión progresiva que corta el acceso a la red, aplaza las alarmas, ignora los bloqueos de despertador y agrupa todo el trabajo pendiente en ventanas de mantenimiento cada vez más separadas. En paralelo, y de forma independiente, cada aplicación recibe una calificación de uso que la coloca en uno de cinco cubos con cuotas distintas. Esta lección explica ambos mecanismos por separado y en combinación, detalla qué sobrevive a la suspensión y qué no, describe cómo se prueba de forma reproducible desde la terminal, y examina por qué la lista de exenciones de optimización de batería es casi siempre la respuesta equivocada.
Hay una experiencia que todo desarrollador de Android vive tarde o temprano y que suele interpretar como un fallo de su código cuando en realidad es un fallo de su modelo mental. La aplicación funciona impecablemente durante toda la jornada de trabajo, con el teléfono sobre la mesa, la pantalla encendiéndose cada pocos minutos y el depurador conectado. Se publica. Y empiezan a llegar informes de usuarios que dicen que las notificaciones aparecen todas de golpe por la mañana, que la sincronización nocturna no ocurre, que la alarma de la aplicación se retrasa media hora. No hay ninguna condición de carrera ni ningún fallo de lógica: hay un teléfono quieto sobre una mesilla durante ocho horas, y un sistema operativo que ha decidido, con toda la razón del mundo, que nada de lo que tu aplicación quiere hacer merece encender la radio a las cuatro de la madrugada.
- Describir las condiciones exactas que activan
Dozey la progresión de sus ventanas de mantenimiento. - Distinguir
Doze, que es global y depende del dispositivo, deApp Standby, que es individual y depende del uso. - Enumerar qué mecanismos sobreviven a la suspensión y cuáles quedan aplazados o ignorados por completo.
- Reproducir ambos regímenes desde la terminal y decidir con criterio si una exención está justificada.
Doze: la suspensión que escala con el tiempo
Doze se activa cuando concurren tres condiciones simultáneas: la pantalla lleva un rato apagada, el dispositivo no está cargando y no ha detectado movimiento significativo. Desde Android 7 existe además una variante ligera que se activa solo con pantalla apagada y sin carga, aunque el dispositivo esté en movimiento, y que aplica un subconjunto más suave de restricciones. La distinción importa porque un teléfono en el bolsillo de alguien que camina está en el régimen ligero, y un teléfono sobre una mesilla de noche está en el profundo.
Una vez dentro, el sistema corta el acceso a la red para las aplicaciones en segundo plano, ignora los bloqueos de despertador parciales, aplaza las alarmas ordinarias, suspende los trabajos programados y las sincronizaciones, y detiene los escaneos de redes inalámbricas. La palabra clave es aplaza, no cancela: nada se pierde, todo se acumula. Y periódicamente el sistema abre una ventana de mantenimiento, un intervalo breve durante el cual se levantan las restricciones y todo el trabajo represado de todas las aplicaciones se ejecuta a la vez.
Conviene precisar qué significa exactamente movimiento significativo, porque de ahí salen sorpresas. No se trata de cualquier vibración: el sistema usa el sensor correspondiente para detectar desplazamiento real del dispositivo, de modo que un teléfono sobre una mesa junto a alguien que teclea no cuenta como en movimiento. Tampoco basta con que la pantalla se encienda un instante por una notificación: hace falta interacción para salir del régimen. Por eso las condiciones nocturnas de una mesilla de noche y las de un cajón durante el fin de semana son idénticas a efectos del planificador, y por eso el escenario que hay que probar no es una hora de inactividad sino ocho.
El detalle que cambia el diseño es que esas ventanas no son periódicas con periodo fijo: se van separando. La primera llega relativamente pronto, la siguiente al cabo de más tiempo, y el intervalo sigue creciendo hasta estabilizarse en el orden de horas. La lógica es transparente: cuanto más tiempo lleva el dispositivo sin que nadie lo toque, menos probable es que al usuario le urja nada, y más rentable resulta agrupar. Para tu aplicación esto significa que el retraso esperado de una tarea diferida no es constante, sino que crece con el tiempo que lleva el teléfono quieto.
Doze profundo
Pantalla apagada, sin carga y sin movimiento. Corta red, aplaza alarmas e ignora bloqueos de despertador parciales.
Doze ligero
Pantalla apagada y sin carga, con el dispositivo en movimiento. Restricciones más suaves y ventanas más frecuentes.
Ventana de mantenimiento
Intervalo breve en el que todo lo represado se ejecuta a la vez. Los intervalos entre ventanas se ensanchan con las horas.
App Standby
Mecanismo independiente y por aplicación. Cinco cubos según el uso, con cuotas propias de trabajos y alarmas.
App Standby y los cubos: la reputación de cada aplicación
App Standby no mira el estado del dispositivo sino el comportamiento del usuario con cada aplicación por separado. El sistema clasifica continuamente cada aplicación en uno de cinco cubos según lo reciente y lo frecuente de su uso, con la ayuda de señales adicionales del propio dispositivo. El cubo activo corresponde a lo que el usuario está usando ahora, el de trabajo fijo a lo que usa a diario, el frecuente a lo que usa con regularidad, el raro a lo que abre de vez en cuando y el restringido al fondo del catálogo, reservado a aplicaciones que consumen mucho y se usan poco.
Cada cubo lleva asociada una cuota que limita cuántos trabajos pueden ejecutarse y con qué frecuencia pueden dispararse las alarmas. La progresión es severa: entre el cubo de trabajo fijo y el raro hay un salto de un orden de magnitud en la práctica, y el cubo restringido reduce el trabajo diferido a poco más de una oportunidad diaria, típicamente ligada a la carga del dispositivo. Un usuario que instala tu aplicación, la prueba dos días y la olvida verá cómo tus notificaciones diferidas prácticamente desaparecen, lo cual es exactamente el comportamiento que el sistema pretende.
Hay una interacción entre ambos mecanismos que conviene tener clara porque explica los casos más extremos. Doze y los cubos se aplican en cascada, no en alternativa: una aplicación en el cubo raro dentro de un dispositivo en Doze profundo sufre primero el aplazamiento global hasta la siguiente ventana de mantenimiento y después, ya dentro de la ventana, la cuota de su cubo, que puede agotarse antes de que le toque el turno. El resultado observable es que hay aplicaciones para las que la ventana de mantenimiento pasa sin que se ejecute nada suyo, y que solo consiguen ejecutar cuando el usuario enchufa el teléfono o vuelve a abrirlas. No es un fallo del planificador: es el diseño funcionando exactamente como se pretende.
El cubo restringido merece una mención aparte porque no se alcanza solo por desuso. El sistema puede colocar ahí a una aplicación que muestre patrones de consumo agresivos, y el usuario puede hacerlo manualmente desde los ajustes de batería con la opción de restringir. Una vez dentro, el trabajo diferido se reduce a una oportunidad diaria y las alarmas quedan prácticamente anuladas. Merece la pena comprobar el cubo actual en el arranque y registrarlo junto con las incidencias de sincronización, porque convierte un fallo inexplicable en un dato.
val um = getSystemService(UsageStatsManager::class.java)
val cubo = um.appStandbyBucket // ACTIVE, WORKING_SET, FRECUENT, RARE, RESTRICTED
registrar("Cubo actual: " + cubo)
La consecuencia de diseño es incómoda y hay que asumirla: la fiabilidad de tu trabajo en segundo plano no es una propiedad de tu código, sino una función de cuánto usa el usuario tu aplicación. No se puede compensar con ingeniería. Lo único que se puede hacer es que el trabajo verdaderamente urgente no dependa de ese canal, y que el trabajo diferible tolere retrasos largos sin degradar la experiencia.
Ambos regímenes se pueden forzar desde la terminal, y hacerlo es la única manera seria de validar un diseño. Para Doze hay que desenchufar el dispositivo virtualmente, saltar a la fase de inactividad y después forzar el paso de una fase a la siguiente tantas veces como haga falta para atravesar una ventana de mantenimiento. Para App Standby se puede fijar directamente el cubo de tu paquete y observar el efecto sobre tus trabajos. Conviene hacer las dos pruebas sin el depurador conectado, porque su presencia altera el trato que recibe el proceso.
# Doze: simular bateria y avanzar por las fases hasta la ventana de mantenimiento
adb shell dumpsys battery unplug
adb shell dumpsys deviceidle force-idle
adb shell dumpsys deviceidle step
adb shell dumpsys battery reset
# App Standby: fijar y consultar el cubo de una aplicacion
adb shell am set-standby-bucket com.ejemplo.app rare
adb shell am get-standby-bucket com.ejemplo.app
Qué atraviesa la suspensión y qué se queda fuera
No todo queda aplazado, y conocer la lista corta de excepciones es lo que permite diseñar sin recurrir a exenciones. Los mensajes de alta prioridad del servicio de mensajería en la nube atraviesan Doze y despiertan la aplicación de inmediato, y ese es el canal previsto para lo genuinamente urgente iniciado desde el servidor. Las alarmas del reloj y las que se declaran para dispararse mientras el dispositivo está inactivo también atraviesan, aunque las segundas están sujetas a un intervalo mínimo entre disparos y no deben usarse para nada que no sea una cita real con el usuario.
Las alarmas exactas atraviesan igualmente, pero desde Android 12 exigen un permiso que la plataforma reserva para casos donde el usuario ha fijado un momento concreto, como una alarma de despertador o un recordatorio de calendario. Usarlas para sincronizar datos es un uso indebido que la tienda revisa. Los servicios en primer plano ya promocionados siguen corriendo, porque Doze no interrumpe lo que ya está visible y en marcha.
Merece una mención el trabajo expedito del planificador, que ocupa una posición intermedia y confunde a mucha gente. Es la forma de decirle al sistema que una tarea diferida es más urgente que las demás, y el sistema la atiende antes y con menos condiciones, pero consume una cuota que depende del cubo en el que esté tu aplicación y que se agota. Cuando la cuota se acaba, la política que declaraste decide si el trabajo se degrada a ordinario o falla. No es un pase libre a través de Doze: es una prioridad dentro de la cola, y hay que gastarla en lo que de verdad la merece.
Lo que no atraviesa es todo lo demás, y esa es la lista larga: trabajos programados sin condición de urgencia, sincronizaciones, alarmas ordinarias, acceso a red desde procesos en segundo plano, bloqueos de despertador parciales adquiridos por tu código y escaneos de red. Un bloqueo de despertador durante Doze no produce error alguno: simplemente no impide que el procesador se duerma, lo que da lugar al fallo más desconcertante de todos, un código correcto que no se ejecuta y no falla.
flowchart TD A[Pantalla apagada, sin carga, sin movimiento] --> B[Doze ligero] B --> C[Doze profundo] C --> D[Red cortada, alarmas aplazadas, trabajos en espera] D --> E[Ventana de mantenimiento] E --> F[Se ejecuta todo lo represado a la vez] F --> G[Vuelta a la suspension con intervalo mas largo] G --> E C --> H[Mensaje de alta prioridad desde el servidor] H --> I[Despertar inmediato de la aplicacion] A --> J[Cubo de App Standby segun el uso] J --> K[Cuota de trabajos y alarmas por dia] style D fill:#f38ba8,color:#11111b style I fill:#a6e3a1,color:#11111b
La exención: por qué casi nunca es la respuesta
Existe una lista de aplicaciones exentas de la optimización de batería, y existe una intención que abre el diálogo para pedirle al usuario que te incluya en ella. Es tentadora, y es la solución equivocada en la inmensa mayoría de los casos, por cuatro razones que conviene tener ordenadas.
Conviene además separar dos cosas que se confunden con frecuencia. Una es la exención de optimización de batería, que es un ajuste del sistema base y que se puede consultar mediante la interfaz de gestión de energía. Otra es el ajuste de uso de batería sin restricciones que ofrecen algunas capas de fabricante, que es un mecanismo distinto, con su propia pantalla y su propio estado, y que no se refleja necesariamente en la consulta anterior. Una aplicación puede estar exenta en el sistema base y seguir siendo cerrada por el gestor del fabricante, lo que produce informes contradictorios entre usuarios con dispositivos de marcas distintas.
La primera es que la política de la tienda restringe severamente qué categorías de aplicación pueden solicitarla directamente, y una solicitud injustificada es motivo de rechazo. La segunda es que la exención libera de Doze y de App Standby, pero no de los límites de ejecución en segundo plano ni de la prohibición de arrancar servicios desde el fondo, de modo que quien la pide creyendo que le devuelve el Android antiguo descubre que sigue sin poder hacer lo que quería. La tercera es que muchas capas de fabricante mantienen su propio gestor de energía por encima del sistema, y estar exento en el sistema base no te exime allí. La cuarta, y la decisiva, es que un diseño que necesita la exención para funcionar es un diseño que ha elegido el canal equivocado.
// Comprobar el estado sin pedir nada: legitimo y util para diagnosticar
val pm = getSystemService(PowerManager::class.java)
val exenta = pm.isIgnoringBatteryOptimizations(packageName)
// Trabajo urgente iniciado por el servidor: el canal correcto
// en lugar de la exencion es un mensaje de alta prioridad
val expedito = OneTimeWorkRequestBuilder<ProcesarMensajeWorker>()
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.setInputData(datos)
.build()
Existe una variante intermedia que sí resulta legítima y que muchos equipos desconocen: la lista blanca temporal que el propio sistema concede sin intervención del usuario. Cuando llega un mensaje de alta prioridad, la aplicación destinataria queda exenta de Doze durante una ventana breve, suficiente para consultar el servidor y publicar una notificación. Ese mecanismo cubre el noventa por ciento de los casos para los que la gente pide la exención permanente, y lo hace sin coste para el usuario, sin permiso adicional y sin riesgo en la revisión de la tienda.
Antes de concluir que Doze está estropeando tu funcionalidad conviene comprobar que el trabajo estaba realmente encolado y en qué estado terminó. El planificador expone el historial de cada trabajo con su estado, su número de intentos y el motivo de la última detención, y el volcado del planificador del sistema desde la terminal muestra la cola completa con sus restricciones pendientes. Buena parte de los casos que se atribuyen a la suspensión resultan ser trabajos que nunca se encolaron, condiciones que jamás llegaron a cumplirse porque exigían red no medida en un teléfono con datos móviles, o duplicados que se cancelaban entre sí por una política de unicidad mal elegida.
La alternativa correcta casi siempre tiene la misma forma. Si el trabajo lo origina el servidor y es urgente, se envía un mensaje de alta prioridad que despierta la aplicación en el acto. Si lo origina el usuario y debe ocurrir en un instante concreto, es una alarma exacta con su permiso y su justificación. Si es continuo y visible, es un servicio en primer plano. Y si no encaja en ninguna de esas tres categorías, es diferible por definición, y el trabajo del diseñador consiste en hacer que un retraso de horas resulte inocuo.
Lo que Doze y App Standby enseñan, más allá de sus detalles concretos, es que en un dispositivo móvil el reloj dejó de ser una infraestructura neutral y pasó a ser un recurso administrado. Un programa de escritorio puede pedir que algo ocurra dentro de cinco minutos y dar por hecho que ocurrirá dentro de cinco minutos, porque el coste de mantener despierto un procesador conectado a la red eléctrica es despreciable y porque nadie compite por ese privilegio. En un teléfono, cada petición de ese tipo es una solicitud para encender un procesador y quizá una radio, con un coste energético concreto que paga un tercero que no participó en la decisión. La plataforma responde con lo único razonable: acepta la petición, no la ejecuta cuando se pide, y la agrupa con las de las demás aplicaciones en el momento en que encender el hardware ya está justificado por otros motivos. Esto convierte el planificador del sistema en algo parecido a un mercado con precios, donde lo urgente se paga con visibilidad o con permisos revisables, y lo demás viaja en la clase económica del trabajo agrupado. La consecuencia para quien diseña es que la variable temporal deja de ser un parámetro y pasa a ser un rango con cola larga. Escribir código que asume que una tarea programada a las tres se ejecutará a las tres es tan ingenuo como asumir que una petición de red tardará exactamente lo que tardó en tu oficina. Y el criterio de calidad se desplaza en consecuencia: un diseño bueno no es el que consigue ejecutarse puntualmente contra la voluntad del sistema, sino el que resulta indistinguible para el usuario tanto si se ejecuta a los cinco minutos como si se ejecuta seis horas después. Cuando alguien insiste en que su caso concreto no tolera esa incertidumbre, la respuesta correcta casi nunca es pelear con el planificador: es reconocer que ese trabajo pertenece a otra categoría y merece el canal que la plataforma reservó para ella, con su coste de visibilidad y su justificación explícita.
- Fuerza
Dozedesde la terminal y atraviesa varias ventanas de mantenimiento, anotando en cada una qué trabajo tuyo se ejecutó y cuál siguió esperando. - Coloca tu paquete en el cubo raro y después en el restringido, y mide cuántos trabajos consigues completar en veinticuatro horas simuladas en cada caso.
- Adquiere un bloqueo de despertador parcial y demuestra, con registros con marca de tiempo, que durante
Dozeno impide que el dispositivo se duerma. - Toma tu notificación más importante y traza el camino completo que recorre desde el servidor, decidiendo si merece prioridad alta y justificando la respuesta.
- Escribe la lista de todo lo que tu aplicación hace de noche y elimina de ella cualquier cosa que el usuario no echaría de menos si ocurriera por la mañana.