Estado del servicio en primer plano
En Android, un servicio de primer plano persistente es lo que mantiene la ubicación en segundo plano.
Seguimiento vivo. Pero pedir rastrear (Tracelet.start()) no es lo mismo que
el sistema operativo otorga un servicio en primer plano en ejecución. En Android 12+ un
El inicio del servicio en primer plano puede ser diferido o rechazado, incluso mientras su aplicación
cree que el seguimiento está habilitado.
Tracelet.getForegroundServiceHealth() cierra esa brecha. Informa el
estado nativo autorizado del servicio en primer plano para que pueda identificar el
diferencia entre “seguimiento solicitado” y “seguimiento en realidad
corriendo” — y reaccionan cuando divergen.
Por qué enabled no es suficiente
Tracelet.getState().enabled es el estado deseado: la intención persistente de
pista. Responde “¿la aplicación ha solicitado realizar un seguimiento?”, no “¿es realmente el sistema operativo?
ejecutando el servicio de primer plano ahora mismo?”.
Estos pueden diferir, especialmente en Android 12+ (API 31+), donde iniciar un
El servicio location en primer plano desde segundo plano está restringido:
- El inicio se puede diferir: Android lo rechaza mientras la aplicación está en segundo plano y Tracelet lo vuelve a intentar automáticamente la próxima vez que la aplicación vuelva a el primer plano.
- El inicio puede fallar por completo, p. un permiso faltante, o un NO TRADUCIR.
- El sistema puede promocionar y luego detener el servicio.
En todos estos casos, enabled permanece true, pero el seguimiento en segundo plano no
operacional. Un organismo de control de la marca de tiempo y ubicación puede eventualmente notar estancamiento, pero
no puede decirle por qué: si la promoción falló, el servicio se detuvo o
el proveedor simplemente está esperando una nueva solución. getForegroundServiceHealth()
le da la razón real y autorizada.
La API
final health = await Tracelet.getForegroundServiceHealth();Devuelve un Map<String, Object?> con las siguientes claves:
| Clave | Tipo | Significado |
|---|---|---|
desiredEnabled | bool | El estado de seguimiento persistente deseado (igual que getState().enabled). |
foregroundServiceEnabled | bool | Si la configuración activa ejecuta un servicio en primer plano. |
serviceRunning | bool | Si el proceso del servicio de ubicación nativa está activo. |
serviceForeground | bool | Si el servicio está actualmente promovido al primer plano (el último startForeground() tuvo éxito y no ha sido degradado/detenido desde entonces). |
foregroundNotificationId | int? | La identificación de la notificación durante la promoción; null en caso contrario. |
lastForegroundPromotionResult | String? | success, deferred o failed: el resultado del intento de promoción más reciente (null antes de cualquier intento). |
lastForegroundPromotionFailureClass | String? | Clase de excepción de la última promoción fallida/diferida (por ejemplo, ForegroundServiceStartNotAllowedException). |
lastForegroundPromotionFailureMessage | String? | El mensaje de excepción. |
lastForegroundTransitionAt | int? | Época: milisegundos de la última transición de ascenso. |
platform | String | android, ios o web. |
La forma del mapa es intencionalmente la misma en todas las plataformas, por lo que el código multiplataforma puede leerlo uniformemente. Sólo los valores difieren según la plataforma (ver más abajo).
Leyendo el resultado de la promoción
serviceForeground combinado con lastForegroundPromotionResult le indica el
historia completa:
desiredEnabled | serviceForeground | lastForegroundPromotionResult | Interpretación |
|---|---|---|---|
false | false | cualquiera | El seguimiento está desactivado, no hay nada de qué preocuparse. |
true | true | success | ✅ Saludable: el servicio en primer plano está en funcionamiento y promocionado. |
true | false | deferred | ⏳ Diferido: Android rechazó el inicio mientras estaba en segundo plano; Tracelet volverá a intentarlo cuando la aplicación vuelva al primer plano. |
true | false | failed | ❌ Falló: la promoción falló; El seguimiento en segundo plano no está operativo. Inspeccione la clase/mensaje de falla. |
true | false | null | ⌛ Solicitado pero aún no confirmado (transitorio; encuesta nuevamente en breve). |
Un indicador de seguimiento de la salud
El uso más común: mostrar un estado honesto al usuario (o su telemetría)
en lugar de confiar ciegamente en enabled.
Future<String> describeTrackingHealth() async {
final h = await Tracelet.getForegroundServiceHealth();
if (h['desiredEnabled'] != true) return 'Tracking off';
// iOS/web have no foreground service — enabled tracking is as good as it gets.
if (h['platform'] != 'android') return 'Tracking active';
if (h['serviceForeground'] == true) return 'Tracking active';
switch (h['lastForegroundPromotionResult']) {
case 'deferred':
return 'Waiting to start — reopen the app to resume background tracking';
case 'failed':
final reason = h['lastForegroundPromotionFailureMessage'] ?? 'unknown';
return 'Background tracking failed to start: $reason';
default:
return 'Starting…';
}
}Un perro guardián de la recuperación
Empareje la verificación de estado con un temporizador periódico para detectar y recuperarse de una falla. promoción: por ejemplo, solicitar al usuario que vuelva a abrir la aplicación o volver a solicitar la permiso faltante.
Timer.periodic(const Duration(minutes: 1), (_) async {
final h = await Tracelet.getForegroundServiceHealth();
final desired = h['desiredEnabled'] == true;
final foreground = h['serviceForeground'] == true;
final result = h['lastForegroundPromotionResult'];
if (desired && !foreground && result == 'failed') {
// Background tracking is not operational. Log it, alert your backend,
// or guide the user to fix permissions / battery settings.
await reportTrackingDegraded(
failureClass: h['lastForegroundPromotionFailureClass'],
failureMessage: h['lastForegroundPromotionFailureMessage'],
);
}
});serviceForeground refleja el resultado de la última promoción, no una encuesta en vivo de
el sistema operativo cada milisegundo. Inmediatamente después de start(), la promoción comienza a funcionar.
más tarde: vuelva a sondear después de un breve retraso (1 a 2 s) si necesita el valor más reciente.
Comportamiento de la plataforma
| Plataforma | Comportamiento |
|---|---|
| Android | Completamente completo con el estado del servicio en primer plano y el historial de promociones en vivo. |
| iOS | No hay ningún servicio en primer plano que pueda fallar después del hecho, por lo que serviceForeground es false, foregroundServiceEnabled es false y los campos de promoción son null. desiredEnabled y serviceRunning reflejan si el seguimiento está activo. platform es ios. |
| Web | Sin servicio en primer plano. Devuelve un mapa mínimo que refleja el estado deseado; platform es web. |
En Tracelet Doctor
No es necesario que construyas nada de esto tú mismo para verlo. El
La superposición tracelet_doctor ahora incluye un Primer plano
Tarjeta de servicio que muestra exactamente esta información: deseada versus real,
resultado de la promoción y cualquier clase/mensaje de error, con un estado codificado por colores
(Saludable / Diferido / Fallido / Inactivo).
tracelet_doctor es una dependencia de desarrollo (flutter pub add dev:tracelet_doctor), por lo que
guárdalo con kDebugMode:
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:tracelet_doctor/tracelet_doctor.dart';
if (kDebugMode) {
TraceletDoctor.show(context); // includes the Foreground Service card
}Los mismos campos también se capturan en el informe de error (TraceletBugReport.build()),
por lo que un informe pegado muestra si el servicio en primer plano realmente se estaba ejecutando en
el momento del problema: a menudo la pista que falta en “el seguimiento se detiene en el
informes de antecedentes”.
Es bueno saberlo
- Solo lectura y económico. La llamada solo lee el estado nativo en memoria; nunca inicia, detiene o modifica el seguimiento.
- Seguro antes de
ready(). Devuelve una instantánea predeterminada sensata en lugar de arrojando si el SDK no se ha inicializado. - Lo deseado versus lo real es el punto. Continúe usando
getState().enabledpara la intención de su aplicación; usegetForegroundServiceHealth()para verificar que el sistema operativo esté honrando esa intención.