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.
Como siempre, podéis descargar el código completo de la aplicación de ejemplo construida en este artículo.

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.