Contenido del curso

Servicios de Localización

Servicios en segundo plano en Android

Resumen

Crear un Service en Android te permite ejecutar tareas en segundo plano, como hacer tracking de la ubicación del usuario mientras la app no está visible. Aquí aprenderás a implementar un servicio normal con Kotlin, inyección de dependencias con Hilt y corutinas, entendiendo por qué este tipo de servicio no garantiza persistencia cuando el sistema operativo libera recursos.

Qué componentes principales tiene Android

Android se construye sobre varios bloques que cumplen funciones específicas dentro de una aplicación.

Los dos protagonistas de esta clase son las activities y los services. Las primeras muestran la interfaz de usuario mediante composables; los segundos ejecutan procesos en segundo plano sin interfaz visible.

¿Qué es un Service en Android? Es un componente que ejecuta operaciones de larga duración en segundo plano sin interfaz de usuario, ideal para tareas como rastrear la ubicación o reproducir audio.

Cuál es la diferencia entre service y foreground service

Los services normales y los foreground services se diferencian por su prioridad ante el sistema operativo:

  • Los services normales no tienen garantía de persistencia. Si el sistema necesita liberar recursos, los termina sin aviso.
  • Los foreground services tienen prioridad alta y siguen activos aunque cierres la aplicación.
  • La condición de un foreground service es mostrar una notificación visible mientras el proceso esté corriendo.

Cómo crear un service con inyección de dependencias en Kotlin

Dentro de la carpeta del proyecto creamos la clase TrackitService y la anotamos con @AndroidEntryPoint para habilitar Hilt. La clase debe extender de Service, lo que obliga a implementar el método onBind, que en este caso devuelve null porque no vamos a ligar la actividad con el servicio.

Luego inyectamos el locationTracker como dependencia. A diferencia de las activities o los ViewModels, un servicio no trae un scope propio, así que debemos crearlo manualmente con CoroutineScope(Dispatchers.Main + SupervisorJob()). Este scope nos permite lanzar corutinas controladas dentro del ciclo de vida del servicio.

Cómo controlar el estado del servicio con StateFlow

Para evitar iniciar el servicio dos veces, declaramos una variable interna _isServiceActive y la exponemos públicamente con asStateFlow(). Así, cualquier composable puede consultar si el servicio ya está corriendo.

En un companion object definimos dos constantes que actúan como comandos:

  • ACTION_START para iniciar el seguimiento.
  • ACTION_STOP para detenerlo.
  • Una variable mutable isServiceActive que cambia de estado según el flujo.

¿Qué es un Intent en Android? Es un mensaje que comunica componentes del sistema, por ejemplo, una activity enviando una orden a un service para iniciar o detener una acción.

Cómo implementar onStartCommand y manejar el ciclo del servicio

El método onStartCommand recibe el intent con la acción enviada desde la actividad. Dentro de él hacemos un when sobre intent?.action y disparamos start() o stop() según el comando.

Devolvemos START_STICKY, una bandera que le indica al sistema que intente reiniciar el servicio si fue terminado, reutilizando la misma instancia cuando sea posible.

Qué hacen las funciones start y stop

La función start valida si el servicio ya está activo. Si no lo está, cambia la bandera a true y lanza una corutina dentro del serviceScope que escucha el elapsedTime del locationTracker con onEach, imprimiendo un log con el tiempo transcurrido.

La función stop hace lo contrario:

  1. Cambia isServiceActive a false.
  2. Cancela el serviceScope actual con todas sus tareas asociadas.
  3. Crea un nuevo CoroutineScope con SupervisorJob, porque las corutinas canceladas no se pueden reutilizar.

Por eso serviceScope debe declararse como var y no como val: necesitamos reasignarlo cuando reactivemos el servicio.

Cómo declarar el service en el AndroidManifest

Ningún componente de Android funciona si no está registrado en el AndroidManifest.xml. Dentro del bloque <application>, junto a las activities, agregamos la etiqueta <service> apuntando a TrackitService. Sin este paso, el sistema operativo bloqueará la inicialización del servicio.

Cómo invocar el service desde un Composable

En la MapScreen, cuando el usuario concede los permisos de ubicación, construimos un Intent apuntando a TrackitService::class.java y le asignamos el action:

kotlin val intent = Intent(context, TrackitService::class.java).apply { action = TrackitService.ACTION_START }

Antes de lanzarlo, verificamos con if (!TrackitService.isServiceActive.value) que no esté ya corriendo. Esta misma lógica se reutiliza en el LaunchedEffect para resumir el tracking si el usuario vuelve a la pantalla, y en el onClick del floating action button dentro de un when que distingue entre iniciar, pausar o reanudar el recorrido.

Por qué un service normal no persiste en segundo plano

Al probar la app en el emulador, el contador de tiempo avanza normalmente. Si llevas la aplicación a segundo plano, el conteo continúa. Pero al hacer slide sobre la tarjeta de la app y matar la actividad, el sistema operativo termina el proceso aproximadamente a los 33 segundos.

Esto pasa porque un service normal no recibe prioridad. Para mantener un proceso vivo incluso cuando el usuario cierra la app, necesitas un foreground service, que es justo el siguiente paso en el aprendizaje.

¿Has tenido que implementar tracking en segundo plano en alguna app? Cuéntame qué desafíos te encontraste con la persistencia de procesos.