jueves, 23 de mayo de 2013

Configurando CodeIgniter

Estoy aprendiendo a utilizar CodeIgniter para programar en PHP utilizando el patrón MVC (Modelo-Vista-Controlador). Este framework resulta muy cómodo y fácil de aprender a utilizarlo. Hasta el momento lo que voy a mostrar en este post es lo que más me costó resolver, porque tienen una documentación muy buena, clara, sencilla y con ejemplos de uso. Sin embargo, por defecto, en las URLs de los proyectos realizados con CodeIgniter aparece un index.php un poco indeseado, aunque se puede eliminar con el módulo mod_rewrite de Apache. Desafortunadamente, yo no conozco mucho sobre la administración de Apache (tendré que ponerme con eso también, en algún momento) y estuve probando las soluciones que encontraba por la web, aunque ninguna me funcionó salvo la esta. Por lo tanto, he decidido hacer una referencia a la misma para acordarme y no tener que recordar la página en el futuro.
El contenido del artículo que se puede ver en el enlace lo reproduzco a continuación:

1.- Creamos un archivo .htaccess con el siguiente contenido y lo guardamos en la raíz de nuestra aplicación

RewriteEngine On
RewriteBase /
#Removes access to the system folder by users.
#Additionally this will allow you to create a System.php controller,
#previously this would not have been possible.
#'system' can be replaced if you have renamed your system folder.
RewriteCond %{REQUEST_URI} ^system.*
RewriteRule ^(.*)$ /index.php/$1 [L]
#Checks to see if the user is attempting to access a valid file,
#such as an image or css document, if this isn't true it sends the
#request to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]
# If we don't have mod_rewrite installed, all 404's
# can be sent to index.php, and everything works as normal.
# Submitted by: ElliotHaughin
ErrorDocument 404 /index.php

2.- Cambiar en la segunda línea del archivo anterior / por la carpeta que aloja nuestro proyecto si este no está en la raíz del servidor web.
3.- En el archivo system/application/config/config.php  nos aseguramos de que la línea correspondiente a index_page aparezca de este modo

 $config['index_page'] = '';

4.- Yo como estoy trabajando con XAMPP sólo necesité reiniciar el servidor Apache. En otro caso puedes ver todos los detalles en el enlace en el que se basa esta información.

Estos cambios hay que hacerlos cada vez que creamos una nueva aplicación con el framework CodeIgniter (si queremos que las URL aparezcan lo más sencillas posible) por eso los documento aquí, para que no se me olviden, y también para ayudar a cualquiera que tenga el mismo problema.


martes, 14 de mayo de 2013

Cronómetro en Android

Una de las aplicaciones que podemos encontrar con facilidad en los móviles Android es el cronómetro. Además existen una gran variedad de ellos para descargar e instalar. Sin embargo, yo decidí hacer mi propia versión del mismo. Hace algún tiempo que hice una primera versión empleando la clase Handler para gestionar el hilo secundario. En esta ocasión, decidí utilizar la clase AsyncTask para dicha función, y además aproveché para hacer algunas mejoras.
A continuación podéis ver el código de mi aplicación, así como algunas imágenes de la misma en funcionamiento.

También podéis descargar el fichero para instalar a través de este enlace.

//// Actividad principal


package not.ebacelo.android.crono02;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.opengl.Visibility;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.*;

public class Crono02Activity extends Activity {
LinearLayout layTiempos, layTableHeader;
TextView tvCount, tvLaps;
Button btnStart, btnLap, btnStop, btnResume;
TareaCronometro tareaCronometro;

@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();

}

/**
 * Método que se ejecuta al crear la Actividad (se
 * debe consultar ciclo de vida de Actividad en caso
 * de dudas sobre este tema)
 */
@Override
public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.main);

//Definimos la orientación de la pantalla para
//que no se modifique. De otro modo debemos guardar
//el estado de la aplicación y restaurarla cada
//vez que se cambie la orientación.
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

//Cargamos las referencias a los controles de la UI
initialize();

//Deshabilitamos los botones Stop y Lap hasta que se
//pulse el botón Start.
btnStop.setEnabled(false);
btnLap.setEnabled(false);
//Ocultamos los títulos de la tabla de tiempos por vuelta
layTableHeader.setVisibility(View.INVISIBLE);

//Creamos una nueva instancia de nuestra tarea AsyncTask
tareaCronometro = new TareaCronometro();

//Qué hacemos cuando se pulsa el botón Start
btnStart.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {
Button btn = (Button) v;

if (btn.getText().toString().compareTo("Start") == 0) {
//Habilitamos los botones Stop y Lap
btnStop.setEnabled(true);
btnLap.setEnabled(true);

if (tareaCronometro.getStatus() != AsyncTask.Status.RUNNING) {
//Arrancamos nuestra tarea asíncrona
tareaCronometro.execute();
}
//Iniciamos el cronómetro
tareaCronometro.iniciar();

} else {
if (btn.getText().toString().compareTo("Resume") == 0) {
//Continuamos el cronómetro cuando ha sido parado
tareaCronometro.continuar();
}
}
}
});

btnStop.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {
Button btn = (Button) v;

if (btn.getText().toString().compareTo("Stop") == 0) {
//Paramos el cronómetro (la tarea continua en
//ejecución).
tareaCronometro.parar();
} else {
if (btn.getText().toString().compareTo("Restore") == 0) {
//Reiniciamos el cronómetro
tareaCronometro.reiniciar();
//Deshabilitamos los botones Stop y Lap
btnStop.setEnabled(false);
btnLap.setEnabled(false);
//Ocultamos cabecera tabla tiempos
layTableHeader.setVisibility(View.INVISIBLE);
}
}
}
});

//Qué hacemos cuando se pulsa el botón Lap
btnLap.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {
//Registramos el tiempo actual
tareaCronometro.marcarTiempo();
layTableHeader.setVisibility(View.VISIBLE);
}
});

}

//Inicializamos las referencias a los controles de la
//interfaz de usuario de la Actividad.
private void initialize() {
tvCount = (TextView) findViewById(R.id.tvCount);
btnStart = (Button) findViewById(R.id.btnStart);
btnStop = (Button) findViewById(R.id.btnStop);
btnLap = (Button) findViewById(R.id.btnLap);
btnLap = (Button) findViewById(R.id.btnLap);
layTiempos = (LinearLayout) findViewById(R.id.layTiempos);
layTableHeader = (LinearLayout)findViewById(R.id.layTableHeader);

}

/**
 *
 * @param startTime
 * @param timeNow
 * @param timeStopped
 * @return String
 *
 * Este método nos devuelve en una cadena de texto con el
 * formato adecuado el tiempo transcurrido entre los
 * momentos indicados por los 2 primeros parámetros de
 * entrada. El tercer parámetro indica el tiempo a
 * descontar si el cronómetro estuvo parado durante el
 * intervalo definido por los dos parámetros anteriores.
 */
private String obtenerTiempoTranscurrido(long startTime, long timeNow,
long timeStopped) {
long diferencia, stopTime;
int hours, minutes, seconds, millis;
String strMillis;

diferencia = timeNow - startTime - timeStopped;
hours = (int) diferencia / 3600000;
diferencia = diferencia % 3600000;
minutes = (int) diferencia / 60000;
diferencia = diferencia % 60000;
seconds = (int) diferencia / 1000;
diferencia = diferencia % 1000;
millis = (int) diferencia;
try {
strMillis = String.format("%03d", millis).substring(0, 2);
millis = Integer.parseInt(strMillis);
} catch (IndexOutOfBoundsException e) {
Log.d("DEPURANDO", "IndexOutOfBoundsException catched");
}

String res = String.format("%d:%02d:%02d.%02d", hours, minutes,
seconds, millis);
return res;
}

/**
 *
 * @author ebacelo
 *
 * Clase interna derivada de AsyncTask que nos permite realizar una
 * tarea en un hilo secundario.
 */
class TareaCronometro extends AsyncTask<Void, String, Void> {
private boolean salir = false;
private boolean parado = true;
private boolean reiniciado = false;
private boolean lap = false;
private boolean iniciar = false;
private long timeStart = 0;
private long resumeTime = 0;
private long stopTime = 0;
private long timeStopped = 0;
private long timeNow = 0;
private long timeLastLap = 0;
private long timeLap = 0;
private int lap_num = 1;

public void iniciar() {
// e0
if (parado != false) {
parado = false;
timeStart = System.currentTimeMillis();
timeLastLap = timeStart;
timeStopped = 0;
}
}

public void parar() {
parado = true;
stopTime = System.currentTimeMillis();
// Ocultamos el botón Lap
// Cambiamos el texto de Stop a Restore, y el de Start a Resume
btnLap.setVisibility(View.INVISIBLE);
btnStart.setText("Resume");
btnStop.setText("Restore");
}

public void continuar() {
btnStart.setText("Start");
btnStop.setText("Stop");
btnLap.setVisibility(View.VISIBLE);
parado = false;
resumeTime = System.currentTimeMillis();
timeStopped += resumeTime - stopTime;
}

public void reiniciar() {
btnStart.setText("Start");
btnStop.setText("Stop");
btnLap.setVisibility(View.VISIBLE);
////
stopTime = System.currentTimeMillis();
timeStopped = 0;
parado = true;
iniciar = true;
reiniciado = true;

//Limpiamos los tiempos de vuelta
layTiempos.removeAllViews();
lap_num = 1;
}

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

salir = true;
finish();
}

//Tareas a realizar después de comenzar la tarea en el
//hilo secundario.
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
}

//Tareas a realizar antes de comenzar la tarea en el
//hilo secundario.
@Override
protected void onPreExecute() {
super.onPreExecute();
parado = true;
iniciar = false;
}

//Este método se ejecutará en el hilo de la interfaz de usuario
//cada vez que desde el método doInBackground() se invoque el
//método publishProgress(). De este modo podemos transmitir
//información entre los dos hilos de ejecución de forma
//sencilla.
@Override
protected void onProgressUpdate(String... values) {

super.onProgressUpdate(values);
// Si no esta parado actualizamos el tiempo en la pantalla
// (obtenerTiempoTranscurrido)

//Mostramos el tiempo transcurrido en la pantalla del
//cronómetro.
tvCount.setText(values[0]);

if (lap) {
// Añadimos en la lista de vueltas el tiempo que va a ser
// publicado
TextView tvTiempoVuelta = new TextView(layTiempos.getContext());
tvTiempoVuelta.setBackgroundColor(Color.argb(45, 55, 120, 55));

if (lap_num % 2 == 0)
tvTiempoVuelta.setTextColor(Color.argb(100, 33, 230, 230));
else
tvTiempoVuelta.setTextColor(Color.argb(100, 230, 230, 33));

tvTiempoVuelta.setTextSize(20);
tvTiempoVuelta.setPadding(0, 1, 0, 1);

//Calcular el tiempo de la vuelta con los datos recibidos
timeNow = Long.parseLong(values[1]);
timeLap = timeNow - timeLastLap;
tvTiempoVuelta.setText(fijarLongCadena(String.valueOf(lap_num),2) + " - " +
values[0] + "   ---   " + String.valueOf(obtenerTiempoTranscurrido(0, timeLap, 0)));

//Añadimos el nuevo tiempo de vuelta al layout del interfaz
//de usuario para visualizarlo.
layTiempos.addView(tvTiempoVuelta, 0);
timeLastLap = timeNow;
lap_num++;
lap = false;
}
}


//El contenido de este método son las tareas que se
//realizarán en el hilo secundario (sólo estas, todos
//los demás métodos se ejecutan en el hilo de la
//interfaz de usuario).
@Override
protected Void doInBackground(Void... arg0) {

while (!salir) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!parado) {
timeNow = System.currentTimeMillis();
String values[] = new String[2];
if (reiniciado) {
timeStart = timeNow;
reiniciado = false;
}
values[0] = obtenerTiempoTranscurrido(timeStart,
timeNow, timeStopped);
values[1] = String.valueOf(timeNow);

publishProgress(values);
}

if (iniciar) {
timeStopped = 0;
timeLastLap = 0;
timeNow = timeStart;
String values[] = new String[2];
values[0] = obtenerTiempoTranscurrido(timeStart,
timeNow, timeStopped);
values[1] = String.valueOf(timeNow);
publishProgress(values);
iniciar = false;
parado = true;
}

}
return null;
}

//Permite indicar al hilo secundario que hay que marcar un tiempo
public void marcarTiempo() {
lap = true;

return;
}

public String fijarLongCadena(String cadena, int longitud) {
String resultado = "";
String relleno = "";

if (cadena.length() < longitud) {
for (int i=0; i< longitud - cadena.length(); i++) {
relleno = relleno.concat("  ");
}
resultado = relleno.concat(cadena);
Log.d("DEPURANDO", "Cadena1: |" + resultado + "|");
return resultado;
}

Log.d("DEPURANDO", "Cadena2: |" + cadena + "|");
return cadena;
}
}

}

//// Layout de la actividad



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dip"
        android:gravity="center" >

        <TextView
            android:id="@+id/tvCount"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dip"
            android:layout_marginTop="5dip"
            android:text="0:00:00.00"
            android:textColor="#ff22ee22"
            android:textSize="44sp" />
    </LinearLayout>


    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btnStart"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            android:padding="10dip"
            android:text="Start"
            android:textSize="24sp" />

        <Button
            android:id="@+id/btnStop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dip"
            android:layout_marginTop="5dip"
            android:layout_weight="0.5"
            android:padding="10dip"
            android:text="Stop"
            android:textSize="24sp" />

        <Button
            android:id="@+id/btnLap"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dip"
            android:layout_marginLeft="5dip"
            android:layout_marginTop="5dip"
            android:layout_weight="0.5"
            android:padding="10dip"
            android:text="Lap"
            android:textSize="24sp" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/layTableHeader"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#304930" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="2dp"
            android:text="Lap"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textColor="#eeeeee" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="14dp"
            android:text="Absolute time"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textColor="#eeeeee" />

        <TextView
            android:id="@+id/textView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="36dp"
            android:text="Lap time"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textColor="#eeeeee" />
     
    </LinearLayout>
 
    <ScrollView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <LinearLayout
            android:id="@+id/layTiempos"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:orientation="vertical"
            android:padding="4dip" >
        </LinearLayout>
    </ScrollView>

</LinearLayout>

//// AndroidManifest.xml



<manifest android:versioncode="1" android:versionname="1.0" package="not.ebacelo.android.crono02" xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-sdk android:minsdkversion="3">

    <application android:icon="@drawable/chrono2" android:label="@string/app_name">
        <activity android:configchanges="orientation|keyboardHidden|keyboard" android:label="@string/app_name" android:name=".Crono02Activity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN">

                <category android:name="android.intent.category.LAUNCHER">
            </category></action></intent-filter>
        </activity>
    </application>

</uses-sdk></manifest>

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;
        }

    }
}