Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Android SDK

Concurrencia en Android con Service

by
Read Time:11 minsLanguages:

Spanish (Español) translation by Esther (you can also view the original English article)

En este tutorial exploraremos el componente Service y su superclase, el IntentService. Aprenderás cuándo y cómo utilizar este componente para crear grandes soluciones de concurrencia para operaciones de fondo de larga duración. También echaremos un vistazo rápido a IPC (Inter Process Communication), para aprender a comunicarnos con servicios que se ejecutan en diferentes procesos.

Para seguir este tutorial necesitarás entender algo de concurrencia en Android. Si no sabes mucho sobre el tema, tal vez quieras leer primero algunos de nuestros otros artículos sobre el tema.

1. El componente Service

El componente Service es una parte muy importante del marco de concurrencia de Android. Satisface la necesidad de realizar una operación de larga duración dentro de una aplicación, o suministra alguna funcionalidad para otras aplicaciones. En este tutorial nos concentraremos exclusivamente en la capacidad de tareas de larga duración de Service, y en cómo utilizar esta potencia para mejorar la concurrencia.

¿Qué es un servicio?

Un Service es un componente simple que es instanciado por el sistema para hacer un trabajo de larga duración que no depende necesariamente de la interacción del usuario. Puede ser independiente del ciclo de vida de la actividad y también puede ejecutarse en un proceso completamente diferente.

Antes de entrar en la discusión de lo que representa un Service, es importante subrayar que aunque los servicios se utilizan comúnmente para operaciones de fondo de larga duración y para ejecutar tareas en diferentes procesos, un Service no representa un Thread o un proceso. Solo se ejecutará en un hilo de fondo o en un proceso diferente si se le pide explícitamente que lo haga.

Un Service tiene dos características principales:

  • Una facilidad para que la aplicación informe al sistema sobre algo que quiere estar haciendo en segundo plano.
  • Una facilidad para que una aplicación exponga parte de su funcionalidad a otras aplicaciones.

Services y Threads

Hay mucha confusión sobre los services y los threads. Cuando se declara un Service, no contiene un Thread. De hecho, por defecto se ejecuta directamente en el thread principal y cualquier trabajo realizado en él puede potencialmente congelar una aplicación. (A menos que sea un IntentService, una subclase de Service que ya viene con un thread de trabajo configurado).

Entonces, ¿cómo ofrecen los services una solución de concurrencia? Bueno, un Service no contiene un thread por defecto, pero puede ser fácilmente configurado para trabajar con su propio thread o con un pool de threads. Veremos más sobre esto a continuación.

Sin tener en cuenta la falta de un thread incorporado, un Service es una excelente solución para los problemas de concurrencia en ciertas situaciones. Las principales razones para elegir un Service sobre otras soluciones de concurrencia como AsyncTask o el framework HaMeR son:

  • Un Service puede ser independiente de los ciclos de vida de las actividades.
  • Un Service es apropiado para ejecutar operaciones largas.
  • Los Services no dependen de la interacción del usuario.
  • Cuando se ejecuta en diferentes procesos, Android puede intentar mantener los servicios vivos incluso cuando el sistema tiene pocos recursos.
  • Un Service puede ser reiniciado para reanudar su trabajo.

Tipos de service

Hay dos tipos de Service, el iniciado y el vinculado.

Un servicio iniciado se lanza a través de Context.startService(). Generalmente realiza una sola operación y se ejecuta indefinidamente hasta que la operación termina, entonces se apaga. Normalmente, no devuelve ningún resultado a la interfaz de usuario.

El servicio vinculado se lanza a través de Context.bindService(), y permite una comunicación bidireccional entre el cliente y Service. También puede conectarse con múltiples clientes. Se autodestruye cuando no hay ningún cliente conectado a él.

Para elegir entre estos dos tipos, el Service debe implementar algunas llamadas de retorno: onStartCommand() para ejecutarse como un servicio iniciado, y onBind() para ejecutarse como un servicio vinculado. Un Service puede optar por implementar solo uno de esos tipos, pero también puede adoptar ambos al mismo tiempo sin ningún problema.

2. Implementación del service

Para utilizar un service, extiende la clase Service y anula sus métodos de callback, según el tipo de Service. Como se mencionó anteriormente, para los servicios iniciados se debe implementar el método onStartCommand() y para los servicios vinculados, el método onBind(). En realidad, el método onBind() debe declararse para cualquier tipo de service, pero puede devolver null para los servicios iniciados.

  • onStartCommand(): lanzado por Context.startService(). Se suele llamar desde una actividad. Una vez llamado, el service puede ejecutarse indefinidamente y depende de ti detenerlo, ya sea llamando a stopSelf() o a stopService().
  • onBind(): se llama cuando un componente quiere conectarse al service. Llamado en el sistema por Context.bindService(). Devuelve un IBinder que proporciona una interfaz para comunicarse con el cliente.

También es importante tener en cuenta el ciclo de vida del service. Los métodos onCreate() y onDestroy() deben ser implementados para inicializar y cerrar cualquier recurso u operación del service.

Declara un service en el manifiesto

El componente Service debe declararse en el manifiesto con el elemento <service>. En esta declaración también es posible, pero no obligatorio, establecer un proceso diferente para que el Service se ejecute.

2.2. Trabajar con los Services Started

Para iniciar un servicio iniciado hay que llamar al método Context.startService(). El Intent debe ser creado con el Context y la clase Service. Cualquier información o dato relevante también debe pasarse en esta Intent.

En tu clase Service, el método que debe preocuparte es el onStartCommand(). Es en este método donde debes llamar a cualquier operación que quieras ejecutar en el servicio iniciado. Procesarás el Intent para capturar la información enviada por el cliente. El startId representa un ID único, creado automáticamente para esta solicitud específica y las flags también pueden contener información extra sobre ella.

La función onStartCommand() devuelve una constante int que controla el comportamiento:

  • Service.START_STICKY: El service se reinicia si se termina.
  • Service.START_NOT_STICKY: El service no se reinicia.
  • Service.START_REDELIVER_INTENT: El service se reinicia después de una caída y los intentos que se estén procesando se volverán a entregar.

Como se mencionó anteriormente, un servicio iniciado necesita ser detenido, de lo contrario se ejecutará indefinidamente. Esto puede ser hecho por el Service llamando stopSelf() en sí mismo o por un cliente llamando stopService() en él.

Vinculación a los services

Los componentes pueden crear conexiones con los services, estableciendo una comunicación bidireccional con ellos. El cliente debe llamar a Context.bindService(), pasando un Intent, una interfaz ServiceConnection y una flag como parámetros. Un Service puede estar vinculado a múltiples clientes y será destruido una vez que no tenga clientes conectados a él.

Es posible enviar objetos Message a los services. Para ello tendrás que crear un Messenger en el lado del cliente en una implementación de la interfaz ServiceConnection.onServiceConnected y utilizarlo para enviar objetos Message al Service.

También es posible pasar un Messenger de respuesta al Service para que el cliente reciba mensajes. Pero cuidado, porque es posible que el cliente ya no esté para recibir el mensaje del service. También puedes utilizar BroadcastReceiver o cualquier otra solución de difusión.

Es importante desvincularse del Service cuando el cliente está siendo destruido.

En el lado del Service, debes implementar el método Service.onBind(), proporcionando un IBinder proporcionado desde un Messenger. Esto retransmitirá un Handler de respuesta para manejar los objetos Message recibidos del cliente.

3 Concurrencia mediante services

Por último, es hora de hablar de cómo resolver los problemas de concurrencia utilizando services. Como se mencionó antes, un Service estándar no contiene ningún thread extra y se ejecutará en el Thread principal por defecto. Para superar este problema hay que añadir un Thread trabajador, un pool de threads o ejecutar el Service en un proceso diferente. También puedes usar una subclase de Service llamada IntentService que ya contiene un Thread.

Cómo hacer que un service se ejecute en un thread de trabajo

Para hacer que el service se ejecute en un thread en segundo plano puedes crear un thread extra y ejecutar el trabajo allí. Sin embargo, Android nos ofrece una mejor solución. Una forma de aprovechar al máximo el sistema es implementar el marco HaMeR dentro del Service, por ejemplo, mediante un bucle de un Thread con una cola de mensajes que pueda procesar mensajes indefinidamente.

Es importante entender que esta implementación procesará las tareas secuencialmente. Si necesitas recibir y procesar varias tareas al mismo tiempo, debes utilizar un pool de threads. El uso de grupos de threads está fuera del alcance de este tutorial y no hablaremos de ello hoy.

Para utilizar HaMeR debes proporcionar al Service un Looper, un Handler y un HandlerThread.

Si el framework HaMeR no te resulta familiar, lee nuestros tutoriales sobre HaMer para la concurrencia en Android.

El IntentService

Si no hay necesidad de que el Service se mantenga vivo durante mucho tiempo, podrías utilizar IntentService, una subclase de Service que está preparada para ejecutar tareas en threads de fondo. Internamente, IntentService es un Service con una implementación muy similar a la propuesta anteriormente.

Para usar esta clase, todo lo que tienes que hacer es extenderla e implementar onHandleIntent(), un método hook que será llamado cada vez que un cliente llame a startService() en este Service. Es importante que tengas en cuenta que el IntentService se detendrá en cuanto termines tu trabajo.

IPC (Comunicación entre procesos)

Un Service puede ejecutarse en un Process completamente diferente, independientemente de todas las tareas que estén ocurriendo en el proceso principal. Un proceso tiene su propia asignación de memoria, grupo de hilos y prioridades de procesamiento. Este enfoque puede ser realmente útil cuando necesitas trabajar independientemente del proceso principal.

La comunicación entre diferentes procesos se denomina IPC (Inter Process Communication). En un Service hay dos formas principales de hacer IPC: usando un Messenger o implementando una interfaz AIDL.

Hemos aprendido a enviar y recibir mensajes entre servicios. Todo lo que tienes que hacer es usar crear un Messenger usando la instancia IBinder recibida durante el proceso de conexión y usarlo para enviar un Messenger de respuesta de vuelta al Service.

La interfaz AIDL es una solución muy potente que permite realizar llamadas directas a los métodos del Service que se ejecutan en diferentes procesos y es apropiada para utilizarla cuando tu Service es realmente complejo. Sin embargo, AIDL es complicado de implementar y rara vez se utiliza, por lo que su uso no será discutido en este tutorial.

4. Conclusión

Los Services pueden ser simples o complejos. Depende de las necesidades de tu aplicación. He tratado de cubrir todo el terreno posible en este tutorial, sin embargo, me he centrado solo en el uso de los services con fines de concurrencia y hay más posibilidades para este componente. Si quieres estudiar más, echa un vistazo a la documentación y a las guías de Android.

¡Hasta pronto!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.