lunes, 6 de mayo de 2013

AsyncTask en Android

Últimamente he estado trabajando un poco en Android, aprendiendo cosas nuevas y también tratando de consolidar algunos conocimientos que no tenía del todo claros. El otro día estuve viendo un modo muy cómodo que nos ofrece Android para trabajar con hilos (para poder realizar más de una tarea "simultáneamente"). Android, aparte de las herramientas que ofrece Java para lidiar con estas tareas, nos ofrece la clase AsyncTask.
En esta entrada del blog voy a publicar el código de una pequeña aplicación que hice para ver si había comprendido el funcionamiento básico de este método. La aplicación simula la utilización de un bombo para obtener los números en un sorteo de la lotería Primitiva. Este no es el método más eficiente de realizar tal aplicación, pero su objetivo no era ese sino ayudarme a comprender mejor el funcionamiento de AsyncTask.
La interfaz de usuario se compone simplemente de un TextView y tres Button como se puede deducir del código que se muestra a continuación, así que me parece innecesario mostrarlo aquí.
Para ayudar a entender el funcionamiento de la aplicación, así como de la clase AsyncTask debes leer los comentarios que aparecen en el código. He intentado explicarlo con claridad para facilitar la comprensión, aunque recomiendo realizar un ejemplo para facilitar la comprensión.

package not.ebacelo.android.numerosprimitiva;

import java.util.Collections;
import java.util.Vector;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {
    //Definimos los elementos de la interfaz de usuario
    TextView tvNumerosSeleccionados;
    Button btnSeleccionarNumero;
    Button btnLimpiarNumeros;
    Button btnSalir;

    //Definimos un objeto de la clase descendiente de AsyncTask
    AsyncTaskGeneradorNumeros generador;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);

        //Enlazamos las variables con los controles de la interfaz de usuario
        tvNumerosSeleccionados = (TextView) findViewById(R.id.tvNumerosSeleccionados);
        btnSeleccionarNumero = (Button) findViewById(R.id.btnSeleccionar);
        btnLimpiarNumeros = (Button) findViewById(R.id.btnLimpiar);
        btnSalir = (Button) findViewById(R.id.btnSalir);

        //Creamos una nueva instancia de la clase que definimos a partir de AsyncTask
        //e iniciamos su ejecución
        generador = new AsyncTaskGeneradorNumeros();
        generador.execute();

        btnSeleccionarNumero.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                generador.seleccionarNumero();
            }
        });

        btnSalir.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Paramos y cancelamos la tarea y...
                generador.parar();
                generador.cancel(false);
               
                //Finalizamos la aplicación.
                finish();
            }
        });

        btnLimpiarNumeros.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Limpiamos el TextView en el que mostramos los números obtenidos
                tvNumerosSeleccionados.setText("");
                //Cancelamos la ejecución y...
                generador.parar();
                generador.cancel(true);
                //Arrancamos una nueva tarea del mismo tipo
                generador = new AsyncTaskGeneradorNumeros();
                generador.execute();
            }
        });
    }

    /*
     * Clase que hereda de AsyncTask. Debemos sobreescribir los métodos onCancelled(),
     * onPostExecute(Boolean result), onPreExecute(), onProgressUpdate(String... value) y
     * doInBackground(Void... params).
     * El contenido de doInBackground se ejecuta en un hilo distinto al hilo de ejecución de la
     * interfaz de usuario.
     * AsyncTask es una clase genérica y debemos indicar las clases que corresponden, en este caso
     * son <Void, String, Boolean>. Estas clases se corresponden con <Params, Values, Result> y deben
     * coincidir con la clase que se le pasa a doInBackground, a onProgressUpdate (el mismo que debe
     * pasarse a la función publishProgress, que es la que provoca la ejecución de onProgressUpdate),
     * y a onPostExecute (el mismo que debe devolver doInBackground).
     * El hecho de establecer correctamente estos tipos es fundamental ya que de otro modo
     * no funcionará, o se obtendrán resultados inesperados.
     * Es importante entender que todos los métodos se ejecutan en el hilo correspondiente a la
     * interfaz de usuario salvo doInBackground.
     */
    private class AsyncTaskGeneradorNumeros extends    AsyncTask<Void, String, Boolean> {
        Vector<Integer> listaNumerosSeleccionados;  //Lista para guardar los números seleccionados
        private boolean cancelado = false;    //Variable para parar la ejecución antes de finalizar
        private boolean seleccion = false;  //Variable que indica cuando hay que seleccionar un número
        private boolean numMaxAlcanzado = false; //Número máximo de números a generar (6 en la Primitiva)

        public boolean seleccionarNumero() {
            if (!numMaxAlcanzado) {
                Log.d("DEPURACION", "numMaxAlcanzado = true");
               
                //Al poner esta variable a true, se desencadena la selección de un nuevo
                //número en el método doInBackground
                this.seleccion = true;
                return true;
            } else
                return false;
        }
       
        public void parar() {
           
            this.cancelado = true;
        }

        @Override
        protected void onCancelled() {
            super.onCancelled();

            Log.d("DEPURACION", "Cancelado = true");
            cancelado = true;
        }

        @Override
        protected void onPostExecute(Boolean result) {
            super.onPostExecute(result);
            //Este método se ejecuta al finalizar la tarea que se realiza en
            //el método doInBackground. El tipo del parámetro de entrada result
            //debe coincidir con el valor que devuelve doInBackground y también
            //con el definido en tercer lugar en la definición de la clase
            //AsyncTaskGeneradorNumeros
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            // Inicializamos la lista de números
            listaNumerosSeleccionados = new Vector<Integer>();
           
            //Este método se ejecuta justo antes de la ejecución de doInBackground. Se
            //debería utilizar para inicializar el estado.
        }

        @Override
        protected void onProgressUpdate(String... values) {
            super.onProgressUpdate(values);

            // Actualizamos la lista de los números seleccionados
            tvNumerosSeleccionados.setText(values[0]);
        }

        @Override
        protected Boolean doInBackground(Void... params) {
           
            //En este método se realiza la tarea. El código contenido dentro de este
            //método se ejecutará dentro de un hilo distinto al de la interfaz de usuario
            while (!cancelado && !numMaxAlcanzado) {
                for (int i = 1; i < 50; i++) {
                    if (seleccion) {
                        if (!listaNumerosSeleccionados.contains(i)) {
                            listaNumerosSeleccionados.add(i);
                            if (listaNumerosSeleccionados.size() == 6)
                                numMaxAlcanzado = true;
                            String texto = "";
                            Collections.sort(listaNumerosSeleccionados);
                            for (Integer numero : listaNumerosSeleccionados)
                                texto += numero.toString() + "  ";
                           
                            //Al ejecutar publishProgress estamos haciendo que se ejecute el código
                            //contenido en el método onProgressUpdate recibiendo como parámetro el contenido
                            //de la variable texto en este caso. Este parámetro al igual que el de
                            //onProgressUpdate es de tipo String.
                            publishProgress(texto);
                            seleccion = false;
                        }
                    }
                }
            }
            //Devuelve un valor del tipo Boolean
            return true;
        }

    }
}

No hay comentarios:

Publicar un comentario