DEV Community

Cover image for Servicios en Android🛸
disced
disced

Posted on • Edited on

Servicios en Android🛸


Definición

Un servicio en Android se asemeja a un demonio en sistemas GNU/Linux, es decir que es un programa en ejecución en segundo plano sin necesidad de interactuar con el usuario. Por lo tanto un servicio no tiene interfaz gráfica.

El termino 'segundo plano' en el contexto de servicios en android puede crear confusión ya que existen los servicios en segundo plano (background) y los servicios en primer plano (foreground).

La creación y ejecución de un servicio en Android no es como en un sistema GNU/Linux (que creas un fichero con la configuración para systemd y utilizando systemctl lo pones en ejecución) en este caso el servicio siempre lo deberá ejecutar la aplicación principal.

Flow de ejecucion de los servicios en android (background services/foreground services)

En Android existen dos tipos de servicios, los Background Services y los Foreground Services. Los dos son muy parecidos pero tiene diferencias notables.


Diferencias

Background Services Foreground Services
No es necesario notificar al usuario de su ejecución Es obligatorio mostrar una notificación al usuario (parte superior)
Tienen baja prioridad para el SO Alta prioridad para el SO
El SO lo puede parar El SO no lo suele parar
Consume menos recursos Consume más recursos
Tiempo de vida corto Tiempo de vida largo
No afectan la experiencia del usuario Afectan la experiencia del usuario

Para que se usan

Un servicio se puede utilizar para cosas que no tenga que intervenir el usuario y que sea transparente para el. También se puede utilizar para ejecutar funcionalidades en segundo plano con una interacción con el usuario.

Background

Son útiles para tareas que no requieren interacción con el usuario y pueden ejecutarse sin su intervención.

Como casos de uso pueden ser la sincronización de datos, copias de seguridad automáticas y actualización de bases de datos.

Evitar realizar tareas de larga duración ya que el sistema lo puede parar cuando lo vea necesario

Foreground

Al tener que mostrar una notificación permanente cuando se encuentra en ejecución un servicio como este, algunos ejemplos de uso pueden ser: la reproducción de música, al usuario se le informa mediante una notificación de que se reproduce música, permitiendo interactuar con el reproductor parando, pausando, avanzando, retrocediendo.

Notificacion de un Foreground service en Android

También se puede utilizar para descargas de ficheros de gran tamaño, donde se muestra al usuario el progreso de la descarga y también se permite la pausa o la cancelación de la misma. (Por ejemplo cuando una aplicación se actualiza, se muestra una notificación de este estilo).

Otro uso que se le puede dar es el seguimiento del usuario mediante GPS e informándole que se están capturando los datos.

En una ocasión he utilizado este tipo de servicio para grabar Audio y Video en segundo plano.

El Audio lo grababa mediante MediaRecorder y video mediante CameraX, la implementación que realicé fue un Foreground Service ya que MediaRecorder a partir del API 28 no permite el acceso al micrófono desde un Background Service.

Cuando ocurría determinado evento, otro servicio ejecutaba este servicio (el de grabación) y comenzaba a grabar. Era obligatorio mostrar una notificación al usuario de que estaba siendo grabado.


Implementación

Background Services

Ejemplo de como crear un Background Service que se ejecuta desde un Composable

Servicio

Heredar de Service y sobrescribir los métodos onBind, onStartCommand y onDestroy.

En el método onStartCommand si queremos que el servicio se ejecute si se cierra la aplicación retornar la constante START_STICKY.

class MyBackgroundService : Service() {

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Tarea que va a realizar el servicio
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
    }
}
Enter fullscreen mode Exit fullscreen mode

El Composable

El composable es un botón que recibe dos funciones por parámetro, para iniciar el servicio y para parar el servicio.

Estas funciones en este caso están creadas en la clase main, lo ideal es tenerlas creadas en un ViewModel

@Composable
fun StartStopServiceButton(
    onStartService: () -> Unit, 
    onStopService: () -> Unit
) {
    var isServiceRunning by remember { mutableStateOf(false) }

    Button(onClick = {
        isServiceRunning = !isServiceRunning
        if (isServiceRunning) {
            onStartService()
        } else {
            onStopService()
        }
    }) {
        Text(if (isServiceRunning) "Detener Servicio" else "Iniciar Servicio")
    }
}
Enter fullscreen mode Exit fullscreen mode

Fichero manifest

Hay que declarar el servicio en el manifest del proyecto.

Exported es para permitir o no que otra aplicación pueda ejecutar el servicio.

<manifest ...>
    <application ...>
        ...
        <service
            android:name=".MyBackgroundService"
            android:exported="false" />
    </application>
</manifest>
Enter fullscreen mode Exit fullscreen mode

Main Activity

La clase main consta de dos funciones , una para ejecutar el servicio y otra para parar el servicio.

También muestra el botón y pasa las funciones por parámetro al composable.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Surface {
                    // Boton
                    StartStopServiceButton(
                        onStartService = {
                            startMyBackgroundService() 
                        },
                        onStopService = {
                            stopMyBackgroundService() 
                        }
                    )
                }
            }
        }
    }

    private fun startMyBackgroundService() {
        val intent = Intent(
            this, 
            MyBackgroundService::class.java
        )
        startService(intent)
    }

    private fun stopMyBackgroundService() {
        val intent = Intent(
            this, 
            MyBackgroundService::class.java
        )
        stopService(intent)
    }
}
Enter fullscreen mode Exit fullscreen mode

Foreground Services

Servicio

La declaracion de un foreground service es muy similar al background service, lo unico que cambia es lo siguiente:

  • Crear la notificación para mostrar al usuario
  • En el método onStartCommand hay que ejecutar el método startForeground con la notificación creada.

Para versiones iguales o superiores a Android 8 (API 26) hay que crear el canal para la notificación como en el método createNotificationChannel().

La notificación se crea mediante NotificationCompat.Builder en el método createNotification(), dicho método retorna una Notification que la recibirá el método startForeground()

class MyForegroundService : Service() {

    companion object {
        const val CHANNEL_ID = "my_channel_id"
        const val NOTIFICATION_ID = 1
    }

    override fun onCreate() {
        super.onCreate()
        createNotificationChannel()
    }

    override fun onStartCommand(
            intent: Intent?, 
            flags: Int, 
            startId: Int): Int {

        val notification = createNotification()

        startForeground(NOTIFICATION_ID, notification)
        return START_NOT_STICKY
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    // Crea el canal para la notificación
    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                "My Foreground Service Channel",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            val notificationManager =
             getSystemService(NotificationManager::class.java)

            notificationManager
                .createNotificationChannel(channel)
        }
    }

    // Crea la notifcación
    private fun createNotification(): Notification {
        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("Foreground Service")
            .setContentText("Servicio en primer plano...")
            .setSmallIcon(R.drawable.ic_notification_icon)
            .build()
    }
}
Enter fullscreen mode Exit fullscreen mode

Composable

El composable recibe por parámetro una función que al hacer click en el botón se ejecuta dicha función.

@Composable
fun MyButton(onStartService: () -> Unit) {
    Button(onClick = { onStartService() }) {
        Text("Iniciar Foreground Service")
    }
}
Enter fullscreen mode Exit fullscreen mode

Fichero manifest

Para versiones iguales o superiores a la API 28 hay que añadir el permiso FOREGROUND_SERVICE.

Cada servicio se declara dentro de application mediante la etiqueta service.

<manifest ... >

    <uses-permission
        android:name="android.permission.FOREGROUND_SERVICE"/>

    <application ...>
        <service
            android:name=".MyForegroundService"
            android:enabled="true"
            android:exported="false" />

        ...

    </application>
</manifest>
Enter fullscreen mode Exit fullscreen mode

Main Activity

La clase main se encarga de mostrar el botón y vincular el método privado que inicia el servicio, muy parecido en el anterior ejemplo.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyButton(
                onStartService = { startMyForegroundService() }
            )
        }
    }

    private fun startMyForegroundService() {
        val intent = Intent(
                this, MyForegroundService::class.java
            )
        startService(intent)
    }
}
Enter fullscreen mode Exit fullscreen mode

Tareas Programadas

Si necesitas gestionar notificaciones, como un aviso programado en una fecha específica, lo ideal es utilizar un WorkManager en lugar de los servicios. Dicha funcionalidad solo está disponible para Android Jetpack.

Referencias

Top comments (0)