10 - Tareas en Segundo Plano en Android
10 - Tareas en Segundo Plano en Android
10 - Tareas en Segundo Plano en Android
en Android
Desarrollo de Aplicaciones para
Dispositivos Móviles
Índice.
1
1. Tareas en segundo plano en
Android (I): Thread y AsyncTask
Obviamente, éstos son el tipo de errores que nadie quiere ver al utilizar
su aplicación, y en este capítulo y los siguientes vamos a ver varias
formas de evitarlo utilizando procesos en segundo plano para ejecutar
las operaciones de larga duración. En este primer capítulo de la serie
nos vamos a centrar en dos de las alternativas más directas a la hora
de ejecutar tareas en segundo plano en Android:
2
Mi idea inicial para este capítulo era obviar la primera opción, ya que
normalmente la segunda solución nos es más que suficiente, y además
es mas sencilla y más limpia de implementar. Sin embargo, si no
comentamos al menos de pasada la forma de crear “a mano” nuevos
hilos y los problemas que surgen, quizá no se viera demasiado claro
las ventajas que tiene utilizar las AsyncTask. Por tanto, finalmente voy
a pasar muy rápidamente por la primera opción para después
centrarnos un poco más en la segunda. Además, aprovechando el tema
de la ejecución de tareas en segundo plano, vamos a ver también cómo
utilizar un control (el ProgressBar) y un tipo de diálogo
(el ProgressDialog) que no vimos en los primeros temas del curso
dedicados a la interfaz de usuario.
1
private void tareaLarga()
2 {
3 try {
4 Thread.sleep(1000);
} catch(InterruptedException e) {}
5 }
6
Haremos que nuestro botón ejecute este método 10 veces, de forma
que nos quedará una ejecución de unos 10 segundos en total:
1
2 btnSinHilos.setOnClickListener(new OnClickListener() {
3 @Override
4 public void onClick(View v) {
pbarProgreso.setMax(100);
5 pbarProgreso.setProgress(0);
6
7 for(int i=1; i<=10; i++) {
8 tareaLarga();
9 pbarProgreso.incrementProgressBy(10);
}
10
11 Toast.makeText(MainHilos.this, "Tarea finalizada!",
12 Toast.LENGTH_SHORT).show();
13 }
});
14
15
3
Como veis, aquí todavía no estamos utilizando nada especial, por lo
que todo el código se ejecutará en el hilo principal de la aplicación. En
cuanto a la utilización del control ProgressBar vemos que es muy
sencilla y no requiere apenas configuración. En nuestro caso tan sólo
hemos establecido el valor máximo que alcanzará (el valor en el que
la barra de progreso estará rellena al máximo) mediante el
método setMax(100), posteriormente la hemos inicializado al valor
mínimo mediante una llamada a setProgress(0) de forma que
inicialmente aparezca completamente vacía, y por último en cada
iteración del bucle incrementamos su valor en 10 unidades llamando
a incrementProgressBy(10), de tal forma que tras la décima iteración
la barra llegue al valor máximo de 100 que establecimos antes.
Finalmente mostramos un mensaje Toast para informar de la
finalización de la tarea. Pues bien, ejecutemos la aplicación, pulsemos
el botón, y veamos qué ocurre.
4
llamada al método runOnUiThread() para “enviar” operaciones al hilo
principal desde el hilo secundario [Nota: Sí, vale, sé que no he
nombrado la opción de los Handler, pero no quería complicar más el
tema por el momento]. Ambas opciones requieren como parámetro un
nuevo objeto Runnable del que nuevamente habrá que implementar
su método run() donde se actúe sobre los elementos de la interfaz.
Por ver algún ejemplo, en nuestro caso hemos utilizado el
método post() para actuar sobre el control ProgressBar, y el
método runOnUiThread()para mostrar el mensaje toast.
1
2
3 new Thread(new Runnable() {
4 public void run() {
5 pbarProgreso.post(new Runnable() {
6 public void run() {
pbarProgreso.setProgress(0);
7 }
8 });
9
10 for(int i=1; i<=10; i++) {
tareaLarga();
11 pbarProgreso.post(new Runnable() {
12 public void run() {
13 pbarProgreso.incrementProgressBy(10);
14 }
});
15 }
16
17 runOnUiThread(new Runnable() {
18 public void run() {
Toast.makeText(MainHilos.this, "Tarea finalizada!",
19 Toast.LENGTH_SHORT).show();
20 }
21 });
}
22 }).start();
23
24
25
Utilicemos este código dentro de un nuevo botón de nuestra aplicación
de ejemplo y vamos a probarlo en el emulador.
6
método doInBackground() y el tipo del parámetro recibido en el
método onPostExecute().
7
27 pbarProgreso.setProgress(0);
28 }
29
30 @Override
31 protected void onPostExecute(Boolean result) {
32 if(result)
Toast.makeText(MainHilos.this, "Tarea finalizada!",
33
Toast.LENGTH_SHORT).show();
34 }
35
36 @Override
37 protected void onCancelled() {
38 Toast.makeText(MainHilos.this, "Tarea cancelada!",
39 Toast.LENGTH_SHORT).show();
40 }
41 }
42
43
Si observamos con detenimiento el código, la única novedad que
hemos introducido es la posibilidad de cancelar la tarea en medio de
su ejecución. Esto se realiza llamando al método cancel() de
nuestra AsyncTask (para lo cual añadiremos un botón más a nuestra
aplicación de ejemplo, además del nuevo que añadiremos para
comenzar la tarea). Dentro de la ejecución de nuestra tarea
en doInBackground() tendremos además que consultar
periodicamente el resultado del método isCancelled() que nos dirá
si el usuario ha cancelado la tarea (es decir, si se ha llamado al
método cancel()), en cuyo caso deberemos de terminar la ejecución
lo antes posible, en nuestro caso de ejemplo simplemente saldremos
del bucle con la instrucción break. Además, tendremos en cuenta que
en los casos que se cancela la tarea, tras el
método doInBackground() no se llamará a onPostExecute() sino al
método onCancelled(), dentro del cual podremos realizar cualquier
acción para confirma la cancelación de la tarea. En nuestro caso
mostraremos un mensaje Toast informando de ello.
ProgressDialog horizontal
ProgressDialog spinner
1
2 btnAsyncDialog.setOnClickListener(new OnClickListener() {
3
4 @Override
5 public void onClick(View v) {
6
pDialog = new ProgressDialog(MainHilos.this);
7 pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
8 pDialog.setMessage("Procesando...");
9 pDialog.setCancelable(true);
pDialog.setMax(100);
10
11 tarea2 = new MiTareaAsincronaDialog();
12 tarea2.execute();
13 }
});
14
15
La AsyncTask será muy similar a la que ya implementamos. De hecho
el método doInBackground() no sufrirá cambios.
9
En onProgressUpdate() la única diferencia será que actualizaremos el
progreso llamando al método setProgress() del ProgressDialog en
vez de la ProgressBar.
10
36
37 @Override
38 protected void onPostExecute(Boolean result) {
39 if(result)
40 {
41 pDialog.dismiss();
Toast.makeText(MainHilos.this, "Tarea finalizada!",
42
Toast.LENGTH_SHORT).show();
43 }
44 }
45
46 @Override
47 protected void onCancelled() {
48 Toast.makeText(MainHilos.this, "Tarea cancelada!",
49 Toast.LENGTH_SHORT).show();
50 }
51 }
52
53
54
Si ahora ejecutamos nuestro proyecto y pulsamos sobre el último
botón incluido veremos cómo el diálogo aparece por encima de nuestra
actividad mostrando el progreso de la tarea asíncrona. Tras finalizar,
el diálogo desaparece y se muestra el mensaje toast de finalización. Si
en cambio, se pulsa el botón Atrás del dispositivo antes de que la tarea
termine el diálogo se cerrará y se mostrará el mensaje de cancelación.
11
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 IntentServicesean 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 capítulo veremos cómo implementar este método
para conseguir una aplicación de ejemplo similar a la que construimos
en el capítulo anterior utilizando AsyncTask.
12
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.
1
2
3
4 public class MiIntentService extends IntentService {
5
public static final String ACTION_PROGRESO =
6 "net.sgoliver.intent.action.PROGRESO";
7 public static final String ACTION_FIN =
8 "net.sgoliver.intent.action.FIN";
9
public MiIntentService() {
10
super("MiIntentService");
11 }
12
13 @Override
14 protected void onHandleIntent(Intent intent)
{
15 int iter = intent.getIntExtra("iteraciones", 0);
16
17 for(int i=1; i<=iter; i++) {
18 tareaLarga();
19
//Comunicamos el progreso
20 Intent bcIntent = new Intent();
21 bcIntent.setAction(ACTION_PROGRESO);
22 bcIntent.putExtra("progreso", i*10);
23 sendBroadcast(bcIntent);
}
24
25 Intent bcIntent = new Intent();
26 bcIntent.setAction(ACTION_FIN);
27 sendBroadcast(bcIntent);
}
28
29 private void tareaLarga()
30 {
31 try {
32 Thread.sleep(1000);
} catch(InterruptedException e) {}
33 }
34 }
35
36
37
13
38
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.
1 <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.
1 btnEjecutar.setOnClickListener(new OnClickListener() {
2
3 @Override
4 public void onClick(View v) {
5 Intent msgIntent = new Intent(MainActivity.this, MiIntentService.class);
6 msgIntent.putExtra("iteraciones", 10);
7 startService(msgIntent);
8 }
9 });
14
recibirse ACTION_FIN mostraremos un mensaje Toast informando de la
finalización de la tarea.
3. Referencias.
15
http://www.sgoliver.net/blog/
16