0% encontró este documento útil (0 votos)
56 vistas10 páginas

Tareas en Segundo Plano en Android

El documento describe dos métodos para ejecutar tareas de larga duración en un hilo secundario en Android para evitar bloqueos en la interfaz de usuario: 1) Crear explícitamente un nuevo hilo, aunque esto dificulta actualizar la interfaz; 2) Usar la clase AsyncTask de Android, la cual divide la tarea en métodos que se ejecutan en diferentes hilos para facilitar la actualización de la interfaz de forma segura.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
56 vistas10 páginas

Tareas en Segundo Plano en Android

El documento describe dos métodos para ejecutar tareas de larga duración en un hilo secundario en Android para evitar bloqueos en la interfaz de usuario: 1) Crear explícitamente un nuevo hilo, aunque esto dificulta actualizar la interfaz; 2) Usar la clase AsyncTask de Android, la cual divide la tarea en métodos que se ejecutan en diferentes hilos para facilitar la actualización de la interfaz de forma segura.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd

Tareas en segundo Thread y

AsyncTask
Todos los componentes de una aplicación Android, tanto las actividades,
los servicios [sí, también los servicios], o los broadcast receivers se
ejecutan en el mismo hilo de ejecución, el llamado hilo principal, main
thread o GUI thread, que como éste último nombre indica también es el
hilo donde se ejecutan todas las operaciones que gestionan la interfaz
de usuario de la aplicación. Es por ello, que cualquier operación larga o
costosa que realicemos en este hilo va a bloquear la ejecución del resto
de componentes de la aplicación y por supuesto también la interfaz,
produciendo al usuario un efecto evidente de lentitud, bloqueo, o mal
funcionamiento en general, algo que deberíamos evitar a toda costa.
Incluso puede ser peor, dado que Android monitoriza las operaciones
realizadas en el hilo principal y detecta aquellas que superen los 5
segundos, en cuyo caso se muestra el famoso mensaje de “Application
Not Responding” (ANR) y el usuario debe decidir entre forzar el cierre de
la aplicación o esperar a que termine.

Obviamente, éstos son el tipo de errores que nadie quiere ver al utilizar
su aplicación, y en este artículo 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 artículo 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:

1. Crear nosotros mismos de forma explícita un nuevo hilo para


ejecutar nuestra tarea.
2. Utilizar la clase auxiliar AsyncTask proporcionada por Android.

Mi idea inicial para este artículo 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.
Y para ir paso a paso, vamos a empezar por crear una aplicación de
ejemplo en cuya actividad principal colocaremos un control
ProgressBar (en mi caso llamado pbarProgreso) y un botón
(btnSinHilos) que ejecute una tarea de larga duración. Para simular
una operación de larga duración vamos a ayudarnos de un método
auxiliar que lo único que haga sea esperar 1 segundo, mediante una
llamada a Thread.sleep().

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
    public void onClick(View v) {
4         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

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.

Vemos cómo al intentar hacer cualquier acción sobre la aplicación


Android nos ha advertido con un mensaje de error que la aplicación no
responde debido a que se está ejecutando una operación de larga
duración en el hilo principal. El usuario debe elegir entre esperar a que
termine de ejecutarla o forzar el cierre de la aplicación. Pues bien, estos
son los efectos que vamos a intentar evitar. La opción más inmediata
que nos proporciona Android, al igual que otras plataformas, es crear
directamente hilos secundarios dentro de los cuales ejecutar nuestras
operaciones costosas. Esto lo conseguimos en Android instanciando un
objeto de la clase Thread. El constructor de la clase Thread recibe como
parámetro un nuevo objeto Runnable que debemos construir
implementando su método run(), dentro del cual vamos a realizar
nuestra tarea de larga duración. Hecho esto, debemos llamar al
método start() del objeto Threaddefinido para comenzar la ejecución
de la tarea en segundo plano.

1 new Thread(new Runnable() {


2     public void run() {
3         //Aquí ejecutamos nuestras tareas costosas
4     }
}).start();
5

Hasta aquí todo sencillo y relativamente limpio. Los problemas aparecen


cuando nos damos cuenta que desde este hilo secundario que hemos
creado no podemos hacer referencia directa a componentes que se
ejecuten en el hilo principal, entre ellos los controles que forman nuestra
interfaz de usuario, es decir, que desde el método run() no podríamos
ir actualizando directamente nuestra barra de progreso de la misma
forma que lo hacíamos antes. Para solucionar esto, Android proporciona
varias alternativas, entre ellas la utilización del método post() para
actuar sobre cada control de la interfaz, o la 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 new Thread(new Runnable() {


2     public void run() {
        pbarProgreso.post(new Runnable() {
3         public void run() {
4             pbarProgreso.setProgress(0);
5         }
6     });
7  
    for(int i=1; i<=10; i++) {
8         tareaLarga();
9         pbarProgreso.post(new Runnable() {
10             public void run() {
11                 pbarProgreso.incrementProgressBy(10);
12                 }
            });
13     }
14  
15     runOnUiThread(new Runnable() {
16         public void run() {
17             Toast.makeText(MainHilos.this, "Tarea finalizada!",
                Toast.LENGTH_SHORT).show();
18
        }
19     });
20     }
21 }).start();

Utilicemos este código dentro de un nuevo botón de nuestra aplicación


de ejemplo y vamos a probarlo en el emulador.

Ahora sí podemos ver el progreso de nuestra tarea reflejado en la barra


de progreso. La creación de un hilo secundario nos ha permitido
mantener el hilo principal libre de forma que nuestra interfaz de usuario
de actualiza sin problemas durante la ejecución de la tarea en segundo
plano. Sin embargo miremos de nuevo el código anterior. Complicado de
leer, ¿verdad? Y eso considerando que tan sólo estamos actualizando un
control de nuestra interfaz. Si el número de controles fuera mayor, o
necesitáramos una mayor interacción con la interfaz el código empezaría
a ser inmanejable, difícil de leer y mantener, y por tanto también más
propenso a errores. Pues bien, aquí es donde Android llega en nuestra
ayuda y nos ofrece la clase AsyncTask, que nos va a permitir realizar
esto mismo pero con la ventaja de no tener que utilizar artefactos del
tipo runOnUiThread() y de una forma mucho más organizada y legible.
La forma básica de utilizar la clase AsyncTaskconsiste en crear una
nueva clase que extienda de ella y sobrescribir varios de sus métodos
entre los que repartiremos la funcionalidad de nuestra tarea. Estos
métodos son los siguientes:

 onPreExecute(). Se ejecutará antes del código principal de


nuestra tarea. Se suele utilizar para preparar la ejecución de la
tarea, inicializar la interfaz, etc.
 doInBackground(). Contendrá el código principal de nuestra tarea.
 onProgressUpdate(). Se ejecutará cada vez que llamemos al
método publishProgress() desde el método doInBackground().
 onPostExecute(). Se ejecutará cuando finalice nuestra tarea, o
dicho de otra forma, tras la finalización del
método doInBackground().
 onCancelled(). Se ejecutará cuando se cancele la ejecución de la
tarea antes de su finalización normal.

Estos métodos tienen una particularidad esencial para nuestros


intereses. El método doInBackground() se ejecuta en un hilo
secundario (por tanto no podremos interactuar con la interfaz), pero sin
embargo todos los demás se ejecutan en el hilo principal, lo que quiere
decir que dentro de ellos podremos hacer referencia directa a nuestros
controles de usuario para actualizar la interfaz. Por su parte, dentro
de doInBackground() tendremos la posibilidad de
llamar periódicamente al método publishProgress() para que
automáticamente desde el método onProgressUpdate() se actualice la
interfaz si es necesario. Al extender una nueva clase
de AsyncTaskindicaremos tres parámetros de tipo:

1. El tipo de datos que recibiremos como entrada de la tarea en el


método doInBackground().
2. El tipo de datos con el que actualizaremos el progreso de la tarea,
y que recibiremos como parámetro del
método onProgressUpdate() y que a su vez tendremos que incluir
como parámetro del método publishProgress().
3. El tipo de datos que devolveremos como resultado de nuestra
tarea, que será el tipo de retorno del método doInBackground() y
el tipo del parámetro recibido en el método onPostExecute().
En nuestro caso de ejemplo, extenderemos de AsyncTask indicando los
tipos Void, Integer y Booleanrespectivamente, lo que se traducirá en
que:

 doInBackground() no recibirá ningún parámetro de entrada


(Void).
 publishProgress() y onProgressUpdate() recibirán como
parámetros datos de tipo entero (Integer).
 doInBackground() devolverá como retorno un dato de tipo
booleano y onPostExecute() también recibirá como parámetro un
dato del dicho tipo (Boolean).

Dicho esto, cómo repartiremos la funcionalidad de nuestra tarea entre


los distintos métodos. Pues sencillo, en onPreExecute() inicializaremos
la barra de progreso estableciendo su valor máximo y poniéndola a cero
para comenzar. En doInBackground() ejecutaremos nuestro bucle
habitual llamando a publishProgress()tras cada iteración indicando el
progreso actual. En onProgressUpdate() actualizaremos el estado de la
barra de progreso con el valor recibido como parámetro. y por último
en onPostExecute() mostraremos el mensaje Toast de finalización de la
tarea. Veamos el código completo:

1 private class MiTareaAsincrona extends AsyncTask<Void, Integer, Boolean> {


2  
    @Override
3     protected Boolean doInBackground(Void... params) {
4
 
5         for(int i=1; i<=10; i++) {
6             tareaLarga();
7  
8             publishProgress(i*10);
9  
10             if(isCancelled())
                break;
11         }
12  
13         return true;
14     }
15  
16     @Override
17     protected void onProgressUpdate(Integer... values) {
        int progreso = values[0].intValue();
18
 
19         pbarProgreso.setProgress(progreso);
20     }
21  
22     @Override
    protected void onPreExecute() {
23
        pbarProgreso.setMax(100);
24         pbarProgreso.setProgress(0);
25     }
26  
27     @Override
28     protected void onPostExecute(Boolean result) {
        if(result)
29             Toast.makeText(MainHilos.this, "Tarea finalizada!",
30                     Toast.LENGTH_SHORT).show();
31     }
32  
33     @Override
34     protected void onCancelled() {
        Toast.makeText(MainHilos.this, "Tarea cancelada!",
35                 Toast.LENGTH_SHORT).show();
36     }
37 }
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.

Mucho mejor que las alternativas anteriores, verdad? Pero vamos a


mostrar una opción más. Si queremos que el usuario pueda ver el
progreso de nuestra tarea en segundo plano, pero no queremos que
interactúe mientras tanto con la aplicación tenemos la opción de mostrar
la barra de progreso dentro de un diálogo. Android nos proporciona
directamente un componente de este tipo en forma de un tipo especial
de diálogo llamado ProgressDialog.
Configurar un cuadro de diálogo de este tipo es muy sencillo. Para ello
vamos a añadir un botón más a nuestra aplicación de ejemplo, donde
inicializaremos el diálogo y lanzaremos la tarea en segundo plano. Para
inicializar el diálogo comenzaremos por crear un nuevo
objeto ProgressDialog pasándole como parámetro el contexto actual.
Tras esto estableceremos su estilo: STYLE_HORIZONTAL para una barra
de progreso tradicional, o STYLE_SPINNER para un indicador de progreso
de tipo indeterminado.

ProgressDialog horizontal

ProgressDialog spinner

Lo siguiente será especificar el texto a mostrar en el diálogo, en nuestro


caso el mensaje “Procesando…“, y el valor máximo de nuestro progreso,
que lo mantendremos en 100. Por último indicaremos si deseamos que
el diálogo sea cancelable, es decir, que el usuario pueda cerrarlo
pulsando el botón Atrás del teléfono. Para nuestro ejemplo activaremos
esta propiedad para ver cómo podemos cancelar también nuestra tarea
en segundo plano cuando el usuario cierra el diálogo. Tras la
configuración del diálogo lanzaremos la AsyncTask del ejemplo anterior,
que tendremos que modificar ligeramente para adaptarla al nuevo
diálogo. Veamos el código por ahora:

1 btnAsyncDialog.setOnClickListener(new OnClickListener() {
2  
    @Override
3
    public void onClick(View v) {
4
 
5         pDialog = new ProgressDialog(MainHilos.this);
6         pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
7         pDialog.setMessage("Procesando...");
8         pDialog.setCancelable(true);
        pDialog.setMax(100);
9
10  
        tarea2 = new MiTareaAsincronaDialog();
11         tarea2.execute();
12     }
13 });
La AsyncTask será muy similar a la que ya implementamos. De hecho el
método doInBackground() no sufrirá cambios.

En onProgressUpdate() la única diferencia será que actualizaremos el


progreso llamando al método setProgress() del ProgressDialog en
vez de la ProgressBar.

El código de onPreExecute() sí tendrá algún cambio más.


Aprovecharemos este método para implementar el evento onCancel del
diálogo, dentro del cual cancelaremos también la tarea en segundo
plano llamando al método cancel(). Además, inicializaremos el
progreso del diálogo a 0 y lo mostraremos al usuario mediante el
método show().

Por último, en el método onPostExecute() además de mostrar el Toast


de finalización, tendremos que cerrar previamente el diálogo llamando a
su método dismiss().

Veamos el código completo de la AsyncTask modificada para usar el


nuevo ProgressDialog.

1 private class MiTareaAsincronaDialog extends AsyncTask<Void, Integer, Boolean> {


2  
        @Override
3
        protected Boolean doInBackground(Void... params) {
4
 
5             for(int i=1; i<=10; i++) {
6             tareaLarga();
7  
8             publishProgress(i*10);
9  
10             if(isCancelled())
                break;
11         }
12  
13             return true;
14         }
15  
16         @Override
17         protected void onProgressUpdate(Integer... values) {
            int progreso = values[0].intValue();
18
 
19
20             pDialog.setProgress(progreso);
        }
21
22  
        @Override
23         protected void onPreExecute() {
24  
25             pDialog.setOnCancelListener(new OnCancelListener() {
26             @Override
27             public void onCancel(DialogInterface dialog) {
                MiTareaAsincronaDialog.this.cancel(true);
28             }
29         });
30  
31             pDialog.setProgress(0);
32             pDialog.show();
33         }
34  
        @Override
35         protected void onPostExecute(Boolean result) {
36             if(result)
37             {
38                 pDialog.dismiss();
                Toast.makeText(MainHilos.this, "Tarea finalizada!",
39                     Toast.LENGTH_SHORT).show();
40             }
41         }
42  
43         @Override
44         protected void onCancelled() {
            Toast.makeText(MainHilos.this, "Tarea cancelada!",
45                 Toast.LENGTH_SHORT).show();
46         }
47     }

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.

También podría gustarte