Descripción general de los servicios

Un Service es un componente de una aplicación que puede realizar operaciones de larga ejecución en segundo plano y que no proporciona una interfaz de usuario. Otro componente de la aplicación puede iniciar un servicio y continuar ejecutándose en segundo plano aunque el usuario cambie a otra aplicación. Además, un componente puede enlazarse con un servicio para interactuar con él e incluso realizar una comunicación entre procesos (IPC). Por ejemplo, un servicio puede manejar transacciones de red, reproducir música, realizar I/O de archivos o interactuar con un proveedor de contenido, todo en segundo plano.

Estos son los tres tipos diferentes de servicios:

Primer plano
Un servicio en primer plano realiza una operación que el usuario puede notar. Por ejemplo, una aplicación de audio usa un servicio en primer plano para reproducir una pista de audio. Los servicios en primer plano deben mostrar una notificación. Estos servicios continúan ejecutándose incluso si el usuario deja de interactuar con la aplicación.
Segundo plano
Un servicio en segundo plano realiza una operación que el usuario no nota directamente. Por ejemplo, si una aplicación usa un servicio para comprimir su almacenamiento, suele tratarse de un servicio en segundo plano.

Nota: Si tu aplicación está orientada al nivel de API 26 o un nivel superior, el sistema impone restricciones en la ejecución de servicios en segundo plano cuando la aplicación misma no se encuentra en primer plano. La mayoría de las veces, es mejor que tu aplicación use una tarea programada.

Enlace
Un servicio es de enlace cuando un componente de la aplicación se vincula a él llamando a bindService(). Un servicio de enlace ofrece una interfaz cliente-servidor que permite que los componentes interactúen con el servicio, envíen solicitudes, reciban resultados e incluso lo hagan en distintos procesos con la comunicación entre procesos (IPC). Un servicio de enlace se ejecuta solamente mientras otro componente de aplicación está enlazado a él. Se pueden enlazar varios componentes con el servicio a la vez, pero el servicio se destruye cuando todos ellos se desenlazan.

Aunque la presente documentación suele analizar por separado los servicios iniciados y enlazados, un servicio puede funcionar de ambas maneras: puede iniciarse (para ejecutarse de modo indefinido) y, a la vez, permitir la vinculación. Todo se reduce a implementar un par de métodos de devolución de llamada: onStartCommand() para permitir que los componentes inicien el servicio y onBind() para permitir la vinculación.

Independientemente de que tu aplicación esté iniciada, enlazada o ambas, cualquier componente de la aplicación puede utilizar el servicio (incluso desde otra aplicación), de la misma forma que cualquier componente puede utilizar una actividad (iniciándola con una Intent). Sin embargo, puedes declarar el servicio como privado en el archivo de manifiesto y bloquear el acceso de otras aplicaciones. Esto se explica con más detalle en la sección Declaración del servicio en el manifiesto.

Advertencia: Un servicio se ejecuta en el subproceso principal de su proceso de alojamiento; el servicio no crea su propio subproceso ni se ejecuta en un proceso separado (salvo que especifiques lo contrario). Si tu servicio va a realizar un trabajo que consume más CPU u operaciones de bloqueo (como reproducción MP3 o funciones de red), debes crear un subproceso nuevo dentro del servicio para completar ese trabajo. Al utilizar un subproceso independiente, puedes reducir el riesgo de que se produzcan errores de tipo "La aplicación no responde (ANR)", y el subproceso principal de la aplicación puede continuar dedicado a la interacción del usuario con tus actividades.

Diferencia entre un servicio y un subproceso

Un servicio es simplemente un componente que puede ejecutarse en segundo plano, incluso cuando el usuario no está interactuando con tu aplicación. Por eso, solo debes crear un servicio si es lo que necesitas.

En cambio, es necesario crear un subproceso si debes trabajar fuera de tu subproceso principal solamente mientras el usuario interactúa con tu aplicación. Por ejemplo, si deseas reproducir música, pero solo mientras se ejecute tu actividad, puedes crear un subproceso en onCreate(), comenzar a ejecutarlo en onStart() y detenerlo en onStop(). También considera la posibilidad de utilizar AsyncTask o HandlerThread en lugar de la clase Thread tradicional. Consulta el documento Procesos y subprocesos para obtener más información sobre los subprocesos.

Recuerda que, si utilizas un servicio, este se ejecuta en el subproceso principal de tu aplicación de forma predeterminada, por lo que debes crear un subproceso nuevo dentro del servicio si este realiza operaciones intensivas o de bloqueo.

Conceptos básicos

Para crear un servicio, debes crear una subclase de Service o usar una de sus subclases existentes. En tu implementación, debes anular algunos métodos de devolución de llamada que manejan aspectos fundamentales del ciclo de vida del servicio y proporcionar un mecanismo que permita que los componentes se enlacen con el servicio (si corresponde). Estos son los métodos de devolución de llamada más importantes que debes anular:

onStartCommand()
El sistema invoca a este método llamando a startService() cuando otro componente, como una actividad, solicita que se inicie el servicio. Una vez que se ejecuta este método, el servicio se inicia y se puede ejecutar en segundo plano de manera indefinida. Si implementas esto, será tu responsabilidad detener el servicio cuando tu trabajo esté terminado. Para ello, llama a stopSelf() o stopService(). Si solo deseas proporcionar enlaces, no necesitas implementar este método.
onBind()
El sistema invoca a este método llamando a bindService() cuando otro componente quiere enlazarse con el servicio (por ejemplo, para ejecutar RPC). En tu implementación de este método, debes proporcionar una interfaz que los clientes utilicen para comunicarse con el servicio devolviendo una IBinder. Siempre debes implementar este método, pero debes devolver NULL si no deseas permitir los enlaces.
onCreate()
El sistema invoca a este método para realizar procedimientos únicos de configuración cuando el servicio se crea por primera vez (antes de llamar a onStartCommand() o onBind()). Si el servicio ya se está ejecutando, no se llama a este método.
onDestroy()
El sistema invoca a este método cuando el servicio ya no se usa y se va a destruir. En el caso de tu servicio, debería implementarse para borrar recursos, como subprocesos, receptores registrados o receptores. Esta es la última llamada que el servicio recibe.

Si un componente inicia el servicio llamando a startService() (lo que da como resultado una llamada a onStartCommand()), el servicio continúa ejecutándose hasta que se detiene a sí mismo con stopSelf() o hasta que otro componente lo detiene llamando a stopService().

Si un componente llama a bindService() para crear el servicio y no se llama a onStartCommand(), el servicio se ejecuta solamente si el componente está enlazado a él. Una vez que el servicio se desenlaza de todos los clientes, el sistema lo destruye.

El sistema Android detiene un servicio solamente cuando hay poca memoria y debe recuperar los recursos del sistema para la actividad en la que está enfocado el usuario. Si el servicio está enlazado a una actividad en la que está enfocado el usuario, es menos probable que finalice. Rara vez finaliza un servicio declarado como servicio en segundo plano. Si se inicia un servicio de ejecución prolongada, el sistema, con el paso del tiempo, desciende su posición en la lista de tareas en segundo plano, y es mucho más probable que dicho servicio finalice. Si se inicia tu servicio, debes diseñarlo de manera tal que el sistema administre los reinicios correctamente. Si el sistema finaliza tu servicio, lo reinicia apenas los recursos vuelven a estar disponibles, aunque esto también depende del valor que devuelvas desde onStartCommand(). Para obtener más información sobre los casos en los que el sistema puede destruir un servicio, consulta el documento Procesos y subprocesos.

En las siguientes secciones, aprenderás a crear los métodos de los servicios startService() y bindService(), y a usarlos desde otros componentes de la aplicación.

Declarar un servicio en el manifiesto

Debes declarar todos los servicios en el archivo de manifiesto de tu aplicación, tal como lo haces con las actividades y otros componentes.

Para declarar tu servicio, agrega un elemento <service> como campo secundario del elemento de <application>. A continuación, te mostramos un ejemplo:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

Consulta la referencia del elemento <service> para obtener más información sobre la manera de declarar tu servicio en el manifiesto.

Existen otros atributos que puedes incluir en el elemento <service> a fin de definir propiedades tales como los permisos necesarios para iniciar el servicio y el proceso en el cual se debe ejecutar el servicio. El atributo android:name es el único atributo obligatorio y especifica el nombre de clase del servicio. Después de que publiques tu aplicación, no debes cambiar este nombre para evitar el riesgo de dañar el código debido a la dependencia de intenciones explícitas de iniciar o enlazar el servicio (consulta la entrada de blog Cosas que no pueden cambiar).

Advertencia: Para garantizar que tu aplicación sea segura, siempre usa una intent explícita cuando inicies un Service y no declares filtros de intents para los servicios. El uso de una intent explícita para iniciar un servicio es un riesgo de seguridad porque no es posible estar seguro de qué servicio responderá a la intent, y el usuario no puede ver qué servicio se inicia. A partir de Android 5.0 (nivel de API 21), el sistema arroja una excepción si llamas a bindService() con una intent implícita.

Puedes garantizar que tu servicio esté disponible solo para tu aplicación incluyendo el atributo android:exported y configurándolo en false. Esto impide que otras aplicaciones inicien tu servicio, incluso cuando se utiliza una intent explícita.

Nota: Los usuarios pueden ver qué servicios se ejecutan en su dispositivo. Si ven uno que no reconocen o en el que no confían, pueden detenerlo. Para evitar que los usuarios detengan tu servicio de manera inadvertida, debes agregar el atributo android:description al elemento <service> en el manifiesto de la aplicación. En la descripción, escribe una oración breve para explicar qué hace el servicio y qué beneficios ofrece.

Crear un servicio iniciado

Un servicio iniciado es un servicio que otro componente inicia llamando a startService(), lo cual da como resultado una llamada al método onStartCommand() del servicio.

Cuando se inicia un servicio, tiene un ciclo de vida independiente del componente que lo inició. El servicio puede ejecutarse en segundo plano de manera indefinida, incluso si se destruye el componente que lo inició. Por ende, cuando su trabajo está terminado, el servicio debe detenerse a sí mismo llamando a stopSelf(), u otro componente puede detenerlo llamando a stopService().

Un componente de una aplicación, como una actividad, puede iniciar el servicio llamando a startService() y pasando una Intent que especifique el servicio e incluya datos para el servicio. El servicio recibe esta Intent en el método onStartCommand().

Por ejemplo, imagina que una actividad necesita guardar datos en una base de datos en línea. La actividad puede iniciar un servicio complementario y enviar a este los datos que se guardarán pasando una intent a startService(). El servicio recibe la intent en onStartCommand(), se conecta a Internet y realiza la transacción de la base de datos. Cuando la transacción está terminada, el servicio se detiene a sí mismo y es destruido.

Advertencia: De forma predeterminada, un servicio se ejecuta en el mismo proceso que la aplicación en la cual está declarado y en el subproceso principal de esta. Por lo tanto, si tu servicio realiza operaciones intensivas o de bloqueo mientras el usuario interactúa con una actividad desde la misma aplicación, el servicio retarda el rendimiento de la actividad. Para evitar el impacto en el rendimiento de la aplicación, inicia un subproceso nuevo dentro del servicio.

Tradicionalmente, existen dos clases que puedes extender para crear un servicio iniciado:

Service
Esta es la clase base para todos los servicios. Cuando extiendes esta clase, es importante que crees un subproceso nuevo, en el que el servicio pueda completar todo el trabajo. El servicio utiliza el subproceso principal de tu aplicación de forma predeterminada, lo que puede ralentizar el rendimiento de cualquier actividad que tu aplicación esté ejecutando.
IntentService
Esta es una subclase de Service que utiliza un subproceso de trabajo para manejar todas las solicitudes de inicio, una por una. Esta es la mejor opción si no necesitas que tu servicio maneje múltiples solicitudes simultáneamente. Implementa onHandleIntent(), que recibe la intent de cada solicitud de inicio para que puedas completar el trabajo en segundo plano.

En las secciones siguientes, se describe cómo puedes implementar tu servicio utilizando cualquiera de estas clases.

Extender la clase IntentService

Dado que la mayoría de los servicios iniciados no necesita manejar múltiples solicitudes simultáneamente (lo que, en realidad, puede ser una situación peligrosa de subprocesos múltiples), es mejor que implementes tu servicio utilizando la clase IntentService.

La clase IntentService hace lo siguiente:

  • Crea un subproceso de trabajo predeterminado que ejecuta todas las intents enviadas a onStartCommand(), independientemente del subproceso principal de tu aplicación.
  • Crea una cola de trabajo que pasa una intent por vez a tu implementación onHandleIntent() para que nunca debas preocuparte por varios subprocesos.
  • Detiene el servicio una vez que se han manejado todas las solicitudes de inicio; de ese modo, nunca tienes que llamar a stopSelf().
  • Proporciona una implementación predeterminada de onBind() que muestra un valor nulo.
  • Proporciona una implementación predeterminada de onStartCommand() que envía la intent a la cola de trabajo y, luego, a tu implementación de onHandleIntent().

Para completar el trabajo proporcionado por el cliente, debes implementar onHandleIntent(). Sin embargo, también debes proporcionar un pequeño constructor para el servicio.

A continuación, se incluye un ejemplo de implementación de IntentService:

Kotlin

/**
 * A constructor is required, and must call the super [android.app.IntentService.IntentService]
 * constructor with a name for the worker thread.
 */
class HelloIntentService : IntentService("HelloIntentService") {

    /**
     * The IntentService calls this method from the default worker thread with
     * the intent that started the service. When this method returns, IntentService
     * stops the service, as appropriate.
     */
    override fun onHandleIntent(intent: Intent?) {
        // Normally we would do some work here, like download a file.
        // For our sample, we just sleep for 5 seconds.
        try {
            Thread.sleep(5000)
        } catch (e: InterruptedException) {
            // Restore interrupt status.
            Thread.currentThread().interrupt()
        }

    }
}

Java

public class HelloIntentService extends IntentService {

  /**
   * A constructor is required, and must call the super <code><a href="/reference/android/app/IntentService.html#IntentService(java.lang.String)">IntentService(String)</a></code>
   * constructor with a name for the worker thread.
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * The IntentService calls this method from the default worker thread with
   * the intent that started the service. When this method returns, IntentService
   * stops the service, as appropriate.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      try {
          Thread.sleep(5000);
      } catch (InterruptedException e) {
          // Restore interrupt status.
          Thread.currentThread().interrupt();
      }
  }
}

Eso es todo lo que necesitas: un constructor y una implementación de onHandleIntent().

Si decides reemplazar también otros métodos de devolución de llamada, como onCreate(), onStartCommand() o onDestroy(), asegúrate de llamar a la superimplementación para que el IntentService pueda manejar correctamente el ciclo de vida del subproceso de trabajo.

Por ejemplo, onStartCommand() debe mostrar la implementación predeterminada, que es la forma en la que se entrega la intent a onHandleIntent():

Kotlin

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()
    return super.onStartCommand(intent, flags, startId)
}

Java

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
    return super.onStartCommand(intent,flags,startId);
}

Además de onHandleIntent(), el único método que no exige llamar a la superclase es onBind(). Solo debes implementar este método si tu servicio permite el enlace.

En la siguiente sección, verás cómo la misma clase de servicio se implementa al extender la clase base Service, lo cual implica usar mucho más código, pero puede ser adecuado si necesitas manejar solicitudes de inicio simultáneas.

Extender la clase Service

El uso de IntentService hace que la implementación de un servicio iniciado sea muy simple. Sin embargo, si necesitas que tu servicio realice varios subprocesos (en lugar de procesar las solicitudes de inicio a través de una cola de trabajo), puedes extender la clase Service para manejar cada intent.

Para fines comparativos, el código de ejemplo siguiente es una implementación de la clase Service que realiza exactamente el mismo trabajo que el ejemplo anterior usando IntentService. Es decir, para cada solicitud de inicio, utiliza un subproceso de trabajo para realizar el trabajo y procesa solo una solicitud a la vez.

Kotlin

class HelloService : Service() {

    private var serviceLooper: Looper? = null
    private var serviceHandler: ServiceHandler? = null

    // Handler that receives messages from the thread
    private inner class ServiceHandler(looper: Looper) : Handler(looper) {

        override fun handleMessage(msg: Message) {
            // Normally we would do some work here, like download a file.
            // For our sample, we just sleep for 5 seconds.
            try {
                Thread.sleep(5000)
            } catch (e: InterruptedException) {
                // Restore interrupt status.
                Thread.currentThread().interrupt()
            }

            // Stop the service using the startId, so that we don't stop
            // the service in the middle of handling another job
            stopSelf(msg.arg1)
        }
    }

    override fun onCreate() {
        // Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.  We also make it
        // background priority so CPU-intensive work will not disrupt our UI.
        HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
            start()

            // Get the HandlerThread's Looper and use it for our Handler
            serviceLooper = looper
            serviceHandler = ServiceHandler(looper)
        }
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()

        // For each start request, send a message to start a job and deliver the
        // start ID so we know which request we're stopping when we finish the job
        serviceHandler?.obtainMessage()?.also { msg ->
            msg.arg1 = startId
            serviceHandler?.sendMessage(msg)
        }

        // If we get killed, after returning from here, restart
        return START_STICKY
    }

    override fun onBind(intent: Intent): IBinder? {
        // We don't provide binding, so return null
        return null
    }

    override fun onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
    }
}

Java

public class HelloService extends Service {
  private Looper serviceLooper;
  private ServiceHandler serviceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              // Restore interrupt status.
              Thread.currentThread().interrupt();
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service. Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block. We also make it
    // background priority so CPU-intensive work doesn't disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    serviceLooper = thread.getLooper();
    serviceHandler = new ServiceHandler(serviceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = serviceHandler.obtainMessage();
      msg.arg1 = startId;
      serviceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

Como puedes ver, es mucho más trabajo que utilizar IntentService.

Sin embargo, como tú mismo manejas cada llamada a onStartCommand(), puedes realizar múltiples solicitudes simultáneamente. Esto no es lo que hace este ejemplo, pero, si es lo que deseas, puedes crear un subproceso nuevo para cada solicitud y ejecutarlas de inmediato en lugar de esperar a que finalice la solicitud anterior.

Ten en cuenta que el método onStartCommand() debe devolver un número entero. El número entero es un valor que describe la forma en que el sistema debe continuar el servicio en caso de que el sistema lo finalice. La implementación predeterminada de IntentService se encarga de esto, aunque es algo que puedes modificar. El valor de retorno de onStartCommand() debe ser una de las siguientes constantes:

START_NOT_STICKY
Si el sistema finaliza el servicio después de que se devuelve onStartCommand(), no vuelvas a crear el servicio, salvo que existan intents pendientes para entregar. Esta es la opción más segura para evitar que tu servicio se ejecute cuando no es necesario y cuando tu aplicación puede simplemente reiniciar los trabajos no finalizados.
START_STICKY
Si el sistema finaliza el servicio después de que se devuelve onStartCommand(), vuelve a crear el servicio y llama a onStartCommand(), pero no vuelvas a entregar la última intent. En cambio, el sistema llamará a onStartCommand() con una intent nula (salvo que haya intents pendientes para iniciar el servicio). En este caso, se entregarán esas intents. Esto es adecuado para reproductores multimedia (o servicios similares) que no ejecutan comandos, sino que se ejecutan de forma indefinida y esperan un trabajo.
START_REDELIVER_INTENT
Si el sistema finaliza el servicio después de que se devuelve onStartCommand(), vuelve a crear el servicio y llama a onStartCommand() con la última intent que se haya entregado al servicio. A su vez, se entregarán todas las intents pendientes. Esto es adecuado para servicios que ejecutan activamente un trabajo y deben reanudarse inmediatamente, por ejemplo, descargar un archivo.

Para obtener más detalles sobre estos valores que se devuelven, consulta la documentación de referencia vinculada de cada constante. Para conocer un ejemplo más elaborado de este tipo de implementación de servicio, consulta la clase MessagingService en el ejemplo de MessagingService en GitHub.

Iniciar un servicio

Puedes iniciar un servicio desde una actividad u otro componente de la aplicación pasando una Intent a startService() o startForegroundService(). El sistema de Android llama al método del servicio onStartCommand() y le pasa la Intent, que especifica qué servicio iniciar.

Nota: Si tu aplicación está orientada al nivel de API 26 o a un nivel superior, el sistema impone restricciones en el uso o la creación de servicios en segundo plano, salvo que la aplicación misma se encuentre en primer plano. Si la aplicación necesita crear un servicio en primer plano, la aplicación debe llamar a startForegroundService(). Este método crea un servicio en segundo plano, pero le indica al sistema que el servicio se promocionará en primer plano. Una vez que se crea el servicio, debe llamar a su método de startForeground() dentro de los siguientes cinco segundos.

Por ejemplo, una actividad puede iniciar el servicio del ejemplo de la sección anterior (HelloService) usando una intent explícita con startService(), tal como se muestra aquí:

Kotlin

Intent(this, HelloService::class.java).also { intent ->
    startService(intent)
}

Java

Intent intent = new Intent(this, HelloService.class);
startService(intent);

Se devuelve inmediatamente el método startService() y el sistema Android llama al método del servicio onStartCommand(). Si el servicio no se está ejecutando, el sistema llama primero a onCreate() y, luego, a onStartCommand().

Si el servicio no proporciona también enlaces, la intent que se entrega con startService() es el único modo de comunicación entre el componente de la aplicación y el servicio. Sin embargo, si deseas que el servicio devuelva un resultado, el cliente que inicia el servicio puede crear una PendingIntent para una emisión (con getBroadcast()) y entregarla al servicio en la Intent que inicia el servicio. Luego, el servicio puede utilizar el mensaje para entregar un resultado.

Las múltiples solicitudes para iniciar el servicio dan como resultado múltiples llamadas correspondientes al onStartCommand() del servicio. Sin embargo, solo se necesita una solicitud de detención del servicio (con stopSelf() o stopService()) para detenerlo.

Detener un servicio

Un servicio iniciado debe administrar su propio ciclo de vida. Es decir, el sistema no detiene ni destruye el servicio (salvo que deba recuperar memoria del sistema) y el servicio continúa ejecutándose después de que se devuelve onStartCommand(). Por lo tanto, el servicio debe detenerse a sí mismo llamando a stopSelf(), u otro componente puede detenerlo llamando a stopService().

Una vez que se solicita la detención con stopSelf() o stopService(), el sistema destruye el servicio lo más pronto posible.

Si tu servicio maneja varias solicitudes a onStartCommand() simultáneamente, no debes detener el servicio cuando terminas de procesar una solicitud de inicio, ya que probablemente recibas una nueva solicitud (la detención al final de la primera solicitud interrumpiría la segunda solicitud). Para evitar este problema, puedes utilizar stopSelf(int) para garantizar que tu solicitud de detener el servicio se base siempre en la solicitud de inicio más reciente. Es decir, cuando llamas a stopSelf(int), pasas el ID de la solicitud de inicio (el startId entregado a onStartCommand()) al cual corresponde tu solicitud de detención. Luego, si el servicio recibe una nueva solicitud de inicio antes de que puedas llamar a stopSelf(int), el ID no coincidirá y el servicio no se detendrá.

Advertencia: Para evitar que se desperdicien recursos del sistema y se consuma la carga de la batería, asegúrate de que tu aplicación detenga los servicios cuando termine de trabajar. Si es necesario, otros componentes pueden detener el servicio llamando a stopService(). Aun cuando habilites los enlaces para el servicio, siempre debes detenerlo tú mismo si alguna vez recibe una llamada a onStartCommand().

Para obtener más información sobre el ciclo de vida de un servicio, consulta la sección Administración del ciclo de vida de un servicio, que aparece más adelante.

Cómo crear un servicio enlazado

Un servicio enlazado es un servicio que permite que los componentes de la aplicación se enlacen a él llamando a bindService() para crear una conexión a largo plazo. Generalmente, no permite que los componentes lo inicien llamando a startService().

Crea un servicio de enlace cuando desees interactuar con el servicio desde las actividades y otros componentes de tu aplicación o cuando quieras exponer algunas de las funciones de tu aplicación a otras aplicaciones a través de la comunicación entre procesos (IPC).

Para crear un servicio enlazado, implementa el método de devolución de llamada onBind() a fin de devolver un IBinder que defina la interfaz para la comunicación con el servicio. Luego, otros componentes de la aplicación pueden llamar a bindService() para recuperar la interfaz y comenzar a llamar a métodos en el servicio. El servicio está activo solo para servir al componente de la aplicación que está enlazado a él; por lo tanto, cuando no hay componentes enlazados al servicio, el sistema lo destruye. No es necesario que detengas un servicio enlazado de la forma en la que debes hacerlo cuando el servicio se inicia a través de onStartCommand().

Para crear un servicio enlazado, debes definir la interfaz que especifica cómo un cliente puede comunicarse con el servicio. Esta interfaz entre el servicio y un cliente debe ser una implementación de IBinder y es lo que tu servicio debe devolver desde el método de devolución de llamada onBind(). Después de que el cliente recibe el IBinder, puede comenzar a interactuar con el servicio a través de esa interfaz.

Se pueden enlazar varios clientes al servicio a la vez. Cuando un cliente termina de interactuar con el servicio, llama a unbindService() para desenlazarse. Cuando ya no hay clientes enlazados con el servicio, el sistema lo destruye.

Existen varias formas de implementar un servicio enlazado y la implementación es más complicada que la de un servicio iniciado. Por lo tanto, la explicación sobre los servicios enlazados aparece en otro documento sobre Servicios de enlace.

Enviar notificaciones al usuario

Cuando se está ejecutando, un servicio puede notificar al usuario acerca de los eventos utilizando notificaciones del sistema o notificaciones de la barra de estado.

Una notificación del sistema es un mensaje que aparece en la superficie de la ventana actual durante un momento y desaparece después. Una notificación de la barra de estado proporciona un ícono en la barra de estado con un mensaje, que el usuario puede seleccionar para realizar una acción (por ejemplo, iniciar una actividad).

Por lo general, una notificación de la barra de estado es la mejor técnica cuando se ha completado un trabajo en segundo plano (por ejemplo, cuando finaliza la descarga de un archivo) y el usuario ya puede utilizarlo. Cuando el usuario selecciona la notificación desde la vista expandida, la notificación puede iniciar una actividad (por ejemplo, mostrar el archivo descargado).

Consulta las guías para desarrolladores Notificaciones del sistema o Notificaciones de la barra de estado para obtener más información.

Ejecutar un servicio en primer plano

Un servicio en primer plano es un servicio del que el usuario está activamente al tanto y que no es candidato a que el sistema lo cierre cuando tiene poca memoria. Un servicio en primer plano debe proporcionar una notificación en la barra de estado, que aparece debajo del encabezado En curso. Esto significa que la notificación no se puede descartar, salvo que el servicio se detenga o se quite del primer plano.

Advertencia: Limita el uso de los servicios en primer plano en tu aplicación.

Debes usarlos solamente si la aplicación necesita realizar una tarea que el usuario notará incluso si no interactúa directamente con la aplicación. Por eso, los servicios en primer plano deben mostrar una notificación en la barra de estado con una prioridad de PRIORITY_LOW o superior. Esto ayuda a garantizar que el usuario esté al tanto de lo que hace tu aplicación. Si la acción tiene una importancia menor y prefieres usar una notificación de prioridad mínima, es recomendable que no uses un servicio, sino un trabajo programado.

Cada aplicación que ejecuta un servicio supone una carga adicional para el sistema y consume recursos. Si una aplicación intenta ocultar sus servicios mediante una notificación de prioridad baja, puede verse afectado el rendimiento de la aplicación con la que el usuario esté interactuando activamente. Por esto, si una aplicación intenta ejecutar un servicio con una notificación de prioridad mínima, el sistema menciona el comportamiento de la aplicación en la sección inferior del panel de notificaciones.

Por ejemplo, un reproductor de música que reproduce música desde un servicio se debe configurar para que se ejecute en primer plano, porque el usuario está explícitamente al tanto de su operación. La notificación en la barra de estado puede indicar la canción actual y permitir que el usuario lance una actividad para interactuar con el reproductor de música. De manera similar, una aplicación que permita a los usuarios controlar la distancia que corren necesitará un servicio en primer plano que pueda hacer un seguimiento de la ubicación del usuario.

Para solicitar que tu servicio se ejecute en primer plano, llama a startForeground(). Este método tiene dos parámetros: un valor entero que identifica de forma exclusiva la notificación y la Notification de la barra de estado. La prioridad de la notificación debe ser PRIORITY_LOW o superior. A continuación, te mostramos un ejemplo:

Kotlin

val pendingIntent: PendingIntent =
        Intent(this, ExampleActivity::class.java).let { notificationIntent ->
            PendingIntent.getActivity(this, 0, notificationIntent, 0)
        }

val notification: Notification = Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
        .setContentTitle(getText(R.string.notification_title))
        .setContentText(getText(R.string.notification_message))
        .setSmallIcon(R.drawable.icon)
        .setContentIntent(pendingIntent)
        .setTicker(getText(R.string.ticker_text))
        .build()

startForeground(ONGOING_NOTIFICATION_ID, notification)

Java

Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent =
        PendingIntent.getActivity(this, 0, notificationIntent, 0);

Notification notification =
          new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
    .setContentTitle(getText(R.string.notification_title))
    .setContentText(getText(R.string.notification_message))
    .setSmallIcon(R.drawable.icon)
    .setContentIntent(pendingIntent)
    .setTicker(getText(R.string.ticker_text))
    .build();

startForeground(ONGOING_NOTIFICATION_ID, notification);

Advertencia: El ID del valor entero que asignas a startForeground() no debe ser 0.

Para quitar el servicio del primer plano, llama al stopForeground(). Este método tiene un valor booleano que indica si también se debe quitar la notificación de la barra de estado. Este método no detiene el servicio. Sin embargo, si detienes el servicio mientras aún se está ejecutando en primer plano, también se quita la notificación.

Para obtener más información sobre las notificaciones, consulta la sección Creación de notificaciones de la barra de estado.

Administrar el ciclo de vida de un servicio

El ciclo de vida de un servicio es mucho más simple que el de una actividad. Sin embargo, es muy importante que prestes mucha atención a la forma en la que se crea y se destruye tu servicio, porque un servicio se puede ejecutar en segundo plano sin que el usuario esté al tanto.

El ciclo de vida del servicio (desde el momento en que se crea hasta que se destruye) puede seguir dos rutas de acceso diferentes:

  • Un servicio iniciado

    El servicio se crea cuando otro componente llama a startService(). El servicio se ejecuta de forma indefinida y se debe detener a sí mismo llamando a stopSelf(). Otro componente también puede detener el servicio llamando a stopService(). Una vez que se detiene el servicio, el sistema lo destruye.

  • Un servicio enlazado

    El servicio se crea cuando otro componente (un cliente) llama a bindService(). Luego, el cliente se comunica con el servicio a través de una interfaz IBinder. El cliente puede cerrar la conexión llamando a unbindService(). Se pueden enlazar varios clientes al mismo servicio y, cuando todos ellos se desenlazan, el sistema destruye el servicio (el servicio no necesita detenerse a sí mismo).

Estas dos rutas no son completamente independientes. Es decir, tú puedes establecer un enlace con un servicio que ya se inició con startService(). Por ejemplo, puedes iniciar un servicio de música en segundo plano llamando a startService() con una Intent que identifique la música que se debe reproducir. Luego, posiblemente cuando el usuario desee controlar el reproductor u obtener información sobre la canción actual, una actividad puede enlazarse con el servicio llamando a bindService(). En casos como este, stopService() o stopSelf() no detienen realmente el servicio hasta que todos los clientes se desenlazan.

Implementar devoluciones de llamada del ciclo de vida

Al igual que una actividad, un servicio tiene métodos de devolución de llamada del ciclo de vida que puedes implementar para controlar los cambios en el estado del servicio y realizar trabajos en los momentos adecuados. El siguiente servicio estructural demuestra cada uno de los métodos del ciclo de vida:

Kotlin

class ExampleService : Service() {
    private var startMode: Int = 0             // indicates how to behave if the service is killed
    private var binder: IBinder? = null        // interface for clients that bind
    private var allowRebind: Boolean = false   // indicates whether onRebind should be used

    override fun onCreate() {
        // The service is being created
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // The service is starting, due to a call to startService()
        return mStartMode
    }

    override fun onBind(intent: Intent): IBinder? {
        // A client is binding to the service with bindService()
        return mBinder
    }

    override fun onUnbind(intent: Intent): Boolean {
        // All clients have unbound with unbindService()
        return mAllowRebind
    }

    override fun onRebind(intent: Intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }

    override fun onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

Java

public class ExampleService extends Service {
    int startMode;       // indicates how to behave if the service is killed
    IBinder binder;      // interface for clients that bind
    boolean allowRebind; // indicates whether onRebind should be used

    @Override
    public void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return mStartMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return mBinder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return mAllowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

Nota: A diferencia de los métodos de devolución de llamada del ciclo de vida de una actividad, no debes llamar a la implementación de la superclase de estos métodos de devolución de llamada.

Figura 2: Ciclo de vida del servicio. El diagrama de la izquierda permite ver el ciclo de vida cuando el servicio se crea con startService() y el diagrama de la derecha permite ver el ciclo de vida cuando el servicio se crea con bindService().

En la figura 2, se ven los métodos de devolución de llamada habituales de un servicio. Si bien en la figura se separan los servicios que se crean mediante startService() de los que se crean mediante bindService(), recuerda que cualquier servicio, sin importar cómo se inicie, puede potencialmente permitir que se enlacen clientes a él. Por lo tanto, un servicio que inicialmente se inició con onStartCommand() (de parte de un cliente que llamó a startService()) puede recibir una llamada a onBind() (cuando un cliente llama a bindService()).

Al implementar estos métodos, puedes controlar estos dos bucles anidados del ciclo de vida del servicio:

  • El ciclo de vida completo de un servicio transcurre entre el momento en el que se llama a onCreate() y el momento en que se devuelve onDestroy(). Al igual que una actividad, un servicio realiza su configuración inicial en onCreate() y libera todos los recursos restantes en onDestroy(). Por ejemplo, un servicio de reproducción de música puede crear un subproceso en el que la música se reproduce en onCreate() y, luego, puede detener ese subproceso en onDestroy().

    Nota: Los métodos onCreate() y onDestroy() reciben llamadas para todos los servicios, independientemente de que se hayan creado a través de startService() o de bindService().

  • El ciclo de vida activo de un servicio comienza con una llamada a onStartCommand() o onBind(). A cada método se le entrega la Intent que se pasó a startService() o bindService().

    Si se inicia el servicio, el ciclo de vida activo finaliza al mismo tiempo que el ciclo de vida completo (el servicio sigue activo incluso después de que se devuelve onStartCommand()). Si se enlaza el servicio, el ciclo de vida activo finaliza cuando se devuelve onUnbind().

Nota: Si bien un servicio iniciado se detiene mediante una llamada a stopSelf() o stopService(), no hay una devolución de llamada respectiva para el servicio (no hay devolución de llamada de onStop()). Por lo tanto, a menos que el servicio esté enlazado a un cliente, el sistema lo destruirá cuando el servicio se detenga; onDestroy() es la única devolución de llamada recibida.

Para obtener más información sobre la creación de un servicio que proporciona enlaces, consulta el documento Servicios enlazados, que incluye más información sobre el método de devolución de llamada onRebind() en la sección Cómo administrar el ciclo de vida de un servicio enlazado.