Los servicios nos acompañan en Android desde el primer día. Aún así, muchas de las aplicaciones que hacemos no los necesitan o los usan muy superficialmente. Por ello, para muchos desarrolladores se han convertido en unos desconocidos. Este post es el primero de una serie en la que describiremos con detalle, y con cierto espíritu revisionista, las posibilidades que nos ofrece la clase Service y sus asociados en el framework de la plataforma.

Un poco de Contexto

Service nació como uno de los componentes fundamentales de la interfaz de programación de aplicaciones (API) de Android. Por aquél entonces el término componente se usaba explícitamente para referirse a cuatro elementos del framework: Activity, Service, ContentProvider y BroadcastReceiver. Actualmente el uso de la palabra componente parece difuminarse en la documentación oficial, pero las dos características que hacen especiales a estos elementos siguen vigentes.

Por un lado, todos ellos son componentes gestionados: clases diseñadas para ser extendidas y cuyas instancias no deben ser usadas de cualquier manera en las aplicaciones, sino que deben adecuarse a un ciclo de vida predefinido y dirigido por el sistema. No creamos objetos de nuestras clases Activity, Service o ContentProvider, los arrancamos indirectamente utilizando Intents o ContentResolvers. No ejecutamos métodos en nuestros BroadcastReceiver, sino que los registramos y esperamos a que reciban llamadas en su método onReceive(…).

En segundo lugar, y de forma única, estas cuatro clases son las que introducen contexto en nuestro código. Literalmente, son las que permiten acceder a objetos de la clase Context, fundamentales para que las aplicaciones puedan acceder a recursos del dispositivo o servicios de sistema. Activity y Service son contextos ellos mismos, ya que extienden la clase Context. ContentProvider da acceso a un contexto inmutable con su método getContext(), y BroadcastReceiver lo recibe como parámetro de onReceive(…).

Debido a esto, los (antes llamados) componentes tienen un gran peso en la estructura de nuestras apps. Cada uno de ellos tiene características únicas que los dotan de un propósito diferenciado. Veamos cuál es el de los servicios.

Nacido para servir

Vayamos a la definición original en la guía de desarrollo para tener claro cuál es la misión de la clase Service en Android.

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.

La primera línea define claramente la misión de los servicios en Android: ejecutar operaciones de larga duración. Para ello, y en oposición directa a la misión de una Activity, un Service no proporciona una interfaz de usuario, o dicho de otro modo, no depende de una interfaz de usuario.

¿Qué operaciones de larga duración puede interesarnos realizar en un dispositivo móvil? Pensemos en una aplicación como ownCloud, que permite acceder, gestionar y compartir ficheros en nuestra nube privada. La app de ownCloud ejecuta tareas que requieren descargar o subir ficheros a un servidor ownCloud remoto. Típicamente, un usuario que quiera subir, por ejemplo, varios vídeos desde su móvil a su cuenta de ownCloud preferirá salir de la aplicación y seguir con su vida, antes que mirar pacientemente la barra de progreso con la app abierta mientras se completa la transferencia.

Afortunadamente, contamos con Service para ayudarnos a implementar este tipo de trabajos. La clase Activity no es apropiada para ello, ya que está concebida para proporcionar una interfaz de usuario a la que está íntimamente ligada. Una instancia de Activity, de forma general, debería interrumpir o pausar cualquier trabajo que ejecute en segundo plano cuando el usuario la retira de pantalla.

¿Y qué proporciona un Service que no tiene una Activity para facilitar nuestra vida como desarrolladores de tareas largas? La diferencia fundamental está en su ciclo de vida. Cuando queremos desarrollar un servicio, debemos escribir una clase que extienda a la clase Service o alguna de sus herederas. Al hacerlo, tendremos que sobrecargar algunos métodos específicos de Service que serán llamados en nuestra aplicación en respuesta a las peticiones de los componentes que actúen como clientes del servicio. En dichos métodos tendremos que iniciar la ejecución de las tareas a ejecutar. El framework se compromete, por medio de la especificación del ciclo de vido de Service, a no interrumpir la operación del servicio aunque los cliente que inicien operaciones en él se retiren, salvo que se den determinadas condiciones.

¿Es esto suficiente para desarrollar operaciones de largo duración? Desde luego es necesario, pero debemos ser cuidadosos con lo que no proporciona la clase Service. A pesar de que su misión es la ejecución de tareas en segundo plano, la gran mayoría de los métodos de entrada en los servicios son ejecutados por el framework en el hilo principal de la aplicación, y no en un hilo en segundo plano, como sería fácil asumir. Android deja bajo nuestra responsabilidad garantizar que las tareas se ejecutarán en otros hilos para no ocupar durante demasiado tiempo el hilo principal y que la app funcione correctamente.

Tres eran tres

Tras la definición, la guía de desarrollo describe tres tipos de servicios (1), aunque quizá es más exacto pensar en tres modos de ejecución de los mismos.

Los modos de ejecución clásicos son los de servicio iniciado y servicio enlazado. Los servicios iniciados (2) soportan la misión fundamental de la clase Service, sólo en este modo de funcionamiento se garantiza que el servicio permanecerá activo indefinidamente hasta que él mismo indique que ha terminado su trabajo o hasta que lo pare explícitamente otro componente (3).

Por otro lado, los servicios enlazados (4) ofrecen la posibilidad de que uno o varios componentes cliente obtengan una referencia a un objeto con el que interactuar de forma directa con el servicio, mediante una interfaz Java al uso, en lugar de con peticiones asíncronas lanzadas vía Intents. Los servicios enlazados no se ejecutan indefinidamente, sino que serán interrumpidos cuando todos los componentes cliente deshagan el enlace. Esto va en contra del interés del Service de ejecutar tareas de forma independiente de sus clientes, pero es fácil de solucionar ya que un mismo servicio puede ejecutarse en los modos iniciado y enlazado al mismo tiempo.

El tercer modo de funcionamiento es el de servicios programados (5). Fue incorporado con Android 5 (nivel de API 21), y Google recomienda expresamente su uso frente a los otros dos para todas las versiones de Android que lo soportan. Su uso supone un cambio drástico para las aplicaciones, ya que los servicios programados no se ejecutan inmediatamente después de las peticiones de sus clientes. Una petición de un trabajo programado supone que el cliente defina algunas condiciones que deben ser ciertas para que el trabajo sea realizado por el servicio. El framework garantiza que el servicio no intentará ejecutarse sin que esas condiciones se cumplan, pero no se compromete a que lo haga tan pronto como se cumplan.

Continuará

En próximas entregas de esta serie estudiaremos más detalles sobre cómo funcionan los tres modos de ejecución de un Service, cómo podemos sacar partido de cada uno y cómo pueden afectar a nuestras apps sus distintas restricciones.

Otros enlaces relacionados

Usa la librería Android Priority Job Queue para tus tareas en segundo plano

Conferencia de ownCloud 2016

(1) En la documentación original en inglés, la traducción al español no está actualizada y sólo incluye los dos modos clásicos.

(2) Started service.

(3) En condiciones de escasez de recursos el sistema puede decidir matar el proceso que ejecuta un servicio iniciado, pero se considera un caso de funcionamiento excepcional.

(4) Bound service.

(5) Scheduled service.