Cómo iniciar un servicio en primer plano

Hay dos pasos para iniciar un servicio en primer plano desde tu app. Primero, debes llamar a context.startForegroundService() para iniciar el servicio. Luego, haz que el servicio llame a ServiceCompat.startForeground() para promocionarse a un servicio en primer plano.

Requisitos previos

Según el nivel de API al que se orienta tu app, existen algunas restricciones sobre cuándo una app puede iniciar un servicio en primer plano.

  • Las apps orientadas a Android 12 (nivel de API 31) o versiones posteriores no pueden iniciar un servicio en primer plano mientras la app está en segundo plano, con algunas excepciones específicas. Para obtener más información y sobre las excepciones a esta regla, consulta Restringir el inicio de un servicio en primer plano desde el segundo plano.

  • Las apps que se orientan a Android 14 (nivel de API 34) o versiones posteriores deben solicitar los permisos adecuados para el tipo de servicio en primer plano. Cuando la app intenta promover un servicio al primer plano, el sistema verifica los permisos adecuados y arroja SecurityException si a la app le falta alguno. Por ejemplo, si intentas iniciar un servicio en primer plano de tipo location, el sistema verifica que tu app ya tenga el permiso ACCESS_COARSE_LOCATION o ACCESS_FINE_LOCATION. En la documentación del tipo de servicio en primer plano, se enumeran los requisitos previos necesarios para cada tipo de servicio en primer plano.

Cómo iniciar un servicio

Para iniciar un servicio en primer plano, primero debes iniciarlo como un servicio ordinario (no en primer plano):

Kotlin

val intent = Intent(...) // Build the intent for the service
context.startForegroundService(intent)

Java

Context context = getApplicationContext();
Intent intent = new Intent(...); // Build the intent for the service
context.startForegroundService(intent);

Puntos clave sobre el código

  • El fragmento de código inicia un servicio. Sin embargo, el servicio aún no se ejecuta en primer plano. Dentro del servicio, debes llamar a ServiceCompat.startForeground() para promocionarlo a un servicio en primer plano.

Cómo promocionar un servicio al primer plano

Una vez que se esté ejecutando un servicio, debes llamar a ServiceCompat.startForeground() para solicitar que se ejecute en primer plano. Por lo general, llamarías a este método en el método onStartCommand() del servicio.

ServiceCompat.startForeground() toma los siguientes parámetros:

  • El servicio
  • Un número entero positivo que identifica de forma única la notificación del servicio en la barra de estado.
  • El objeto Notification
  • Los tipos de servicio en primer plano que identifican el trabajo que realiza el servicio

Los tipos de servicios en primer plano que pasas a los tipos de startForeground()declarados en el manifiesto, según el caso de uso específico Luego, si necesitas agregar más tipos de servicios, puedes volver a llamar a startForeground().

Por ejemplo, supongamos que una app de fitness ejecuta un servicio de seguimiento de actividades de correr que siempre necesita información de location, pero que puede o no necesitar reproducir contenido multimedia. Debes declarar location y mediaPlayback en el manifiesto. Si un usuario inicia una actividad de correr y solo quiere que se haga un seguimiento de su ubicación, tu app debe llamar a startForeground() y pasar solo el permiso ACCESS_FINE_LOCATION. Luego, si el usuario quiere comenzar a reproducir audio, vuelve a llamar a startForeground() y pasa la combinación de bits de todos los tipos de servicios en primer plano (en este caso, ACCESS_FINE_LOCATION|FOREGROUND_SERVICE_MEDIA_PLAYBACK).

En el siguiente ejemplo, se muestra el código que usaría un servicio de cámara para promocionarse a un servicio en primer plano:

Kotlin

class MyCameraService: Service() {

  private fun startForeground() {
    // Before starting the service as foreground check that the app has the
    // appropriate runtime permissions. In this case, verify that the user has
    // granted the CAMERA permission.
    val cameraPermission =
            PermissionChecker.checkSelfPermission(this, Manifest.permission.CAMERA)
    if (cameraPermission != PermissionChecker.PERMISSION_GRANTED) {
        // Without camera permissions the service cannot run in the foreground
        // Consider informing user or updating your app UI if visible.
        stopSelf()
        return
    }

    try {
        val notification = NotificationCompat.Builder(this, "CHANNEL_ID")
            // Create the notification to display while the service is running
            .build()
        ServiceCompat.startForeground(
            /* service = */ this,
            /* id = */ 100, // Cannot be 0
            /* notification = */ notification,
            /* foregroundServiceType = */
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
            } else {
                0
            },
        )
    } catch (e: Exception) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
                && e is ForegroundServiceStartNotAllowedException) {
            // App not in a valid state to start foreground service
            // (e.g. started from bg)
        }
        // ...
    }
  }
}

Java

public class MyCameraService extends Service {

    private void startForeground() {
        // Before starting the service as foreground check that the app has the
        // appropriate runtime permissions. In this case, verify that the user
        // has granted the CAMERA permission.
        int cameraPermission =
            ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
        if (cameraPermission == PackageManager.PERMISSION_DENIED) {
            // Without camera permissions the service cannot run in the
            // foreground. Consider informing user or updating your app UI if
            // visible.
            stopSelf();
            return;
        }

        try {
            Notification notification =
                new NotificationCompat.Builder(this, "CHANNEL_ID")
                    // Create the notification to display while the service
                    // is running
                    .build();
            int type = 0;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                type = ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
            }
            ServiceCompat.startForeground(
                    /* service = */ this,
                    /* id = */ 100, // Cannot be 0
                    /* notification = */ notification,
                    /* foregroundServiceType = */ type
            );
        } catch (Exception e) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
                    e instanceof ForegroundServiceStartNotAllowedException
            ) {
                // App not in a valid state to start foreground service
                // (e.g started from bg)
            }
            // ...
        }
    }

    //...
}

Puntos clave sobre el código

  • La app ya declaró en el manifiesto que necesita el permiso CAMERA. Sin embargo, la app también debe verificar durante el tiempo de ejecución para asegurarse de que el usuario otorgó ese permiso. Si la app no tiene los permisos correctos, debe informarle al usuario sobre el problema.
  • Se introdujeron diferentes tipos de servicios en primer plano con diferentes versiones de la plataforma de Android. Este código verifica en qué versión de Android se ejecuta y solicita los permisos adecuados.
  • El código busca ForegroundServiceStartNotAllowedException en caso de que intente iniciar un servicio en primer plano en una situación que no está permitida (por ejemplo, si intenta promocionar el servicio al primer plano mientras la app está en segundo plano).