En el artículo anterior del Curso de Programación Android vimos cómo ejecutar tareas en segundo plano haciendo uso de hilos (Thread) y tareas asíncronas (AsyncTask). En este nuevo artículo nos vamos a centrar en una alternativa menos conocida, aunque tanto o más interesante, para conseguir el mismo objetivo: ejecutar una determinada tarea en un hilo independiente al hilo principal de la aplicación. Esta opción se llama IntentService, y no es más que un tipo particular de servicio Android que se preocupará por nosotros de la creación y gestión del nuevo hilo de ejecución y de detenerse a sí mismo una vez concluida su tarea asociada.
Como en el caso de las AsyncTask, la utilización de un IntentService va a ser tan sencilla como extender una nueva clase de IntentService e implementar su método onHandleIntent(). Este método recibe como parámetro un Intent, que podremos utilizar para pasar al servicio los datos de entrada necesarios.
A diferencia de las AsyncTask, un IntentService no proporciona métodos que se ejecuten en el hilo principal de la aplicación y que podamos aprovechar para «comunicarnos» con nuestra interfaz durante la ejecución. Éste es el motivo principal de que los IntentService sean una opción menos utilizada a la hora de ejecutar tareas que requieran cierta vinculación con la interfaz de la aplicación. Sin embargo tampoco es imposible su uso en este tipo de escenarios ya que podremos utilizar por ejemplo mensajes broadcast (y por supuesto su BroadcastReceiver asociado capaz de procesar los mensajes) para comunicar eventos al hilo principal, como por ejemplo la necesidad de actualizar controles de la interfaz o simplemente para comunicar la finalización de la tarea ejecutada. En este artículo veremos cómo implementar este método para conseguir una aplicación de ejemplo similar a la que construimos en el artículo anterior utilizando AsyncTask.
Empezaremos extendiendo una nueva clase derivada de IntentService, que para ser originales llamaremos MiIntentService. Lo primero que tendremos que hacer será implementar un constructor por defecto. Este constructor lo único que hará será llamar al constructor de la clase padre pasándole el nombre de nuestra nueva clase.
A continuación implementaremos el método onHandleIntent(). Como ya hemos indicado, este método será el que contenga el código de la tarea a ejecutar en segundo plano. Para simular una tarea de larga duración utilizaremos el mismo bucle que ya vimos en el artículo anterior con la novedad de que esta vez el número de iteraciones será variable, de forma que podamos experimentar con cómo pasar datos de entrada a través del Intent recibido como parámetro en onHandleIntent(). En nuestro caso de ejemplo pasaremos un sólo dato de entrada que indique el número de iteraciones. Por tanto, lo primero que haremos será obtener este dato a partir del Intent mediante el método getIntExtra(). Una vez conocemos el número de iteraciones, tan sólo nos queda ejecutar el bucle y comunicar el progreso tras cada iteración.
Como ya hemos comentado, para comunicar este progreso vamos a hacer uso de mensajes broadcast. Para enviar este tipo de mensajes necesitamos construir un Intent, asociarle un nombre de acción determinada que lo identifique mediante setAction(), e incluir los datos que necesitemos comunicar añadiendo tantos extras como sean necesarios mediante el método putExtra(). Los nombres de las acciones se suelen preceder con el paquete java de nuestra aplicación de forma que nos aseguremos que es un identificador único. En nuestro caso lo llamaremos «net.sgoliver.intent.action.PROGRESO» y lo definiremos como atributo estático de la clase para mayor comodidad, llamado ACTION_PROGRESO. Por su parte, los datos a comunicar en nuestro ejemplo será solo el nivel de progreso, por lo que sólo añadiremos un extra a nuestro intent con dicho dato. Por último enviaremos el mensaje llamando al método sendBroadcast() pasándole como parámetro el intent recién creado. Veamos cómo quedaría el código completo.
public class MiIntentService extends IntentService { public static final String ACTION_PROGRESO = "net.sgoliver.intent.action.PROGRESO"; public static final String ACTION_FIN = "net.sgoliver.intent.action.FIN"; public MiIntentService() { super("MiIntentService"); } @Override protected void onHandleIntent(Intent intent) { int iter = intent.getIntExtra("iteraciones", 0); for(int i=1; i<=iter; i++) { tareaLarga(); //Comunicamos el progreso Intent bcIntent = new Intent(); bcIntent.setAction(ACTION_PROGRESO); bcIntent.putExtra("progreso", i*10); sendBroadcast(bcIntent); } Intent bcIntent = new Intent(); bcIntent.setAction(ACTION_FIN); sendBroadcast(bcIntent); } private void tareaLarga() { try { Thread.sleep(1000); } catch(InterruptedException e) {} } }
Como podéis comprobar también he añadido un nuevo tipo de mensaje broadcast (ACTION_FIN), esta vez sin datos adicionales, para comunicar a la aplicación principal la finalización de la tarea en segundo plano.
Además de la implementación del servicio, recordemos que también tendremos que declararlo en el AndroidManifest.xml, dentro de la sección <application>:
<service android:name=".MiIntentService"></service>
Y con esto ya tendríamos implementado nuestro servicio. El siguiente paso será llamar al servicio para comenzar su ejecución. Esto lo haremos desde una actividad principal de ejemplo en la que tan sólo colocaremos una barra de progreso y un botón para lanzar el servicio. El código del botón para ejecutar el servicio será muy sencillo, tan sólo tendremos que crear un nuevo intent asociado a la clase MiIntentService, añadir los datos de entrada necesarios mediante putExtra() y ejecutar el servicio llamando a startService() pasando como parámetro el intent de entrada. Como ya dijimos, el único dato de entrada que pasaremos será el número de iteraciones a ejecutar.
btnEjecutar.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent msgIntent = new Intent(MainActivity.this, MiIntentService.class); msgIntent.putExtra("iteraciones", 10); startService(msgIntent); } });
Con esto ya podríamos ejecutar nuestra aplicación y lanzar la tarea, pero no podríamos ver el progreso de ésta ni saber cuándo ha terminado porque aún no hemos creado el BroadcastReceiver necesario para capturar los mensajes broadcast que envía el servicio durante su ejecución.
Para ello, como clase interna a nuestra actividad principal definiremos una nueva clase que extienda a BroadcastReceiver y que implemente su método onReceive() para gestionar los mensajes ACTION_PROGRESO y ACTION_FIN que definimos en nuestro IntentService. En el caso de recibirse ACTION_PROGRESO extraeremos el nivel de progreso del intent recibido y actualizaremos consecuentemente la barra de progreso mediante setProgress(). En caso de recibirse ACTION_FIN mostraremos un mensaje Toast informando de la finalización de la tarea.
public class ProgressReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if(intent.getAction().equals(MiIntentService.ACTION_PROGRESO)) { int prog = intent.getIntExtra("progreso", 0); pbarProgreso.setProgress(prog); } else if(intent.getAction().equals(MiIntentService.ACTION_FIN)) { Toast.makeText(MainActivity.this, "Tarea finalizada!", Toast.LENGTH_SHORT).show(); } } }
Pero aún no habríamos terminado dado, ya que aunque hayamos implementado nuestro BroadcastReceiver, éste no tendrá ningún efecto a menos que lo registremos con la aplicación y lo asociemos a los tipos de mensaje que deberá tratar (mediante un IntentFilter). Para hacer esto, al final del método onCreate() de nuestra actividad principal crearemos un IntentFilter al que asociaremos mediante addAction() los dos tipos de mensaje broadcast que queremos capturar, instanciaremos nuestro BroadcastReceiver y lo registraremos mediante registerReceiver(), al que pasaremos la instancia creada y el filtro de mensajes.
IntentFilter filter = new IntentFilter(); filter.addAction(MiIntentService.ACTION_PROGRESO); filter.addAction(MiIntentService.ACTION_FIN); ProgressReceiver rcv = new ProgressReceiver(); registerReceiver(rcv, filter);
Y con esto sí habríamos concluido nuestra aplicación de ejemplo. Si ejecutamos la aplicación en el emulador y pulsamos el botón de comenzar la tarea veremos cómo la barra de progreso comienza a avanzar hasta el final, momento en el que deberá aparecer el mensaje toast indicando la finalización de la tarea.
Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la página del curso en GitHub.
Curso de Programación Android en PDF
¿Te ha sido de utilidad el Curso de Programación Android? ¿Quieres colaborar de forma económica con el proyecto? Puedes contribuir con cualquier cantidad, unos céntimos, unos euros, cualquier aportación será bienvenida. Además, si tu aportación es superior a una pequeña cantidad simbólica recibirás como agradecimiento un documento con la última versión del curso disponible en formato PDF. Sea como sea, muchas gracias por colaborar!
Más información:
21 comentarios
En serio, ¡eres increíble haciendo todo esto!
Excelente tutorial, los IntentService quedan ejecutandose en segundo plano al cerrar la aplicacion?
estimado… deseo ponerme en contacto con usted para hacer unas consultas referente a su trabajo como programador.
El mail que figura en contacto me devuelve un Mail Failure !
Gracias
Excelente tutorial, no se puede explicar mejor las cosas.
Una pregunta, qué tipo de servicio utilizan aplicaciones como whatsapp que reciben los mensajes aunque esté la aplicación cerrada?
Muchas gracias.
Es verdaderamente increíble lo que haces, sólo con tu curso se puede desarrollar cualquier tipo de app, muchas gracias.
Siempre me ha resultado complejo el tema este de los servicios en segundo plano, y ahora que han cambiado el API me han generado algunas dudas, no se si alguien las sabrá:
– En el caso concreto del código que nos proporcionas, si no pasamos iteraciones, ¿el servicio se ejecuta hasta que se sale de la app? Teoricamente sí, pues el handler pide el hilo cada x tiempo y en este caso las iteraciones son una mera restricción, ¿no? (una bandera)
– A mí, con la API antigua me pasaba que se quedaban en segundo plano y habia que llamar al método stop del servicio, eso ha sido quitado?
Muchas gracias por todo
Excelente.
Tus artículos me estan solucionando muchos problemas para una práctica de la universidad que consiste en el desarrollo de una app sin que nos expliquen nada.
Gracias por todos tus maravillosos artículos.
Gracias amigo. Excelente. Es mucho más fácil entender 1 párrafo escrito por ti que un capitulo por otros.
[…] Tareas en segundo plano II: IntentService […]
Buenas,
Me gustaría saber si el IntentService sigue vivo si cerramos la app completamente, ya que he estado probando el Asynctask y se muere al cerrar completamente la app.
Me gustaría dejar mi app ejecutando su lógica aunque se cierre completamente, me podrías guiar un poco? Gracias
En primer lugar felicidades por tus post, me han ayudado mucho en mis andanzas por android.
Veras tengo un pequeño problemilla con la ejecucion en segundo plano ya que los uso para consultar una base de datos SQL y primero utilizo con AsyncTask un metodo que consulta la base de datos para obtener los nombres de usuario y comprobar que el nuevo usuario que se va a registrar no exista, posteriormente si no existe sera introducido en la base de datos (todo mediante SOAP y Web Services)
El problema radica en que necesito sincronizarlos para que primero se ejecute uno y despues el otro, si no cada hilo va por su cuenta y no consigo que funcione bien.
Hay alguna manera de conseguir esto con el AsyncTask? o deberia utilizas Threads y el Synchronize?
Gracias por adelantado. Un saludo
[…] que extienda de IntentService (para más información sobre los Intent Service puedes consultar el artículo dedicado a ellos) y como siempre implementaremos su evento onHandleIntent(). Aquí lo primero que haremos será […]
Excelente!!! forma de explicar y de gran ayuda.
buenas, primero que nada felicidades por tu trabajo, tus tutoriales me han sido de mucha ayuda para aprender android y entender su funcionamiento.
por lo que llevo aprendido me surgio un gran duda, en java uno utilizaba la clase thread o la interfaz runnable pare realizar estas tareas en segundo plano, aqui en android se utiliza AsyncTask y intentServices, en ambos casos e visto su implementacion en la clase que ejecuta tambien las acciones en la interfaz grafica de la aplicacion.
la cuestion que me he planteado, es posible utilizar estas misma implementaciones fuera de estas clase, yo por los momentos me encuentro programando una libreria y una de las cosas que realizo es una coneccion a un web services, pero no quiero colocar la implementacion en la interfaz si no en la libreria, se pueden utilizar esto mismo que has explicado tanto en este articulo como en el anterior, o debo irme por como se haria en java puro para resolver dicho conflicto. lo que mas se me acerca a mi planteamiento es la que se encuentra en este articulo, me gustaria saber tu opinion y consejo muchas gracias
Excelente tutorial…!! Pregunta…!! PODEMOS TENERLO VIA WEB..?? O QUE TAL SI PLANIFICAS UN CURSO ONLINE…!!
Saludos…. y gracias por lo que enseñas.
Enserio que buen blog apenas inicio y estoy buscando muchas cosas para mi proyecto final gracias por tomarte el tiempo de hacer un blog asi
Buenas tardes, me gusto mucho tu tutorial, una pregunta, yo ya cuento con un codigo que se esta ejecutando en el onHandleIntent, y quiero enviar datos de una actividad al intentService, pero por lo que comento antes, ya se encuentra ejecutando un codigo, hay alguna posibilidad que pueda obtener el nuevo intent de otra forma? gracias
Muy buenos articulos, estoy en proceso de estadias laborales y me ha servido mucho todos tus post gracias por facilitar los temas.
Buenas tardes, me gustaría saber como puedo hacer para que un servicio se mantenga en ejecución cuando la actividad sea destruida, es decir, que si yo cambio de activity o cierro la aplicación el servicio continue ejecutandose. Gracias de antemano
Buenas tardes,
Fantástica información! Me ha resultado muy útil para un proyecto que estoy realizando, he cogido el código entero y funciona perfectamente así que con tu permiso lo reeditare para acoplarlo a mi app (por ejemplo no necesito la progressbar).
He compartido el articulo en Twitter que seguro que le es útil a mucha gente. (@Alvaroivm)
Gracias por compartir contenido de tanto valor.
Un saludo!
Muchas gracias por tan excelentes articulos me han sido de gran ayuda para aclarar conceptos. Quiero consultar una situacion:
Tengo un servicio que consulta un websocket, el problema con este servicio es que aunque lo he configurado para que se inicie de nuevo si el sistema lo detiene por una falta de recursos, la operacion de consulta solo se hace una vez ai el servicio no haya sido cancelado o destruido. La idea es que este servicio este haciendo una especia de poollling al websocket para saber si tiene alguna información parsa la aplicacion.
Tampoco he podico hacer que el servicio me genere una notificación indicandole al usuario que le llegó algo de su interes.
He intentado crear el servicio sin uso de Google cloud message o Firebase cloud message ya que pretendo que mi servicio se inicie al reiniciar el telefono.
Agradecezco de antemano sus consejos sobre como abordar el tema y hacer que mi servicio realice la consulta al websocket de manera recurrente.
[…] http://www.sgoliver.net/blog/tareas-en-segundo-plano-en-android-ii-intentservice/ […]