AsyncTask: Tareas Asíncronas en Android

Este artículo ha sido creado con el fin de presentarte los beneficios del uso de hilos en tus aplicaciones Android. La idea es evaluar entre varias opciones la mejor alternativa para implementar concurrencia entre las tareas que se ejecutan.

Esta valoración mantendrá la sensación multitarea, evitará bloqueos y optimizará el hilo principal de tus proyectos.

El Manejo de Hilos en Android

Un hilo es una unidad de ejecución asociada a una aplicación. Es la estructura de la programación concurrente, la cual tiene como objetivo dar la percepción al usuario que el sistema que ejecuta realiza múltiples tareas a la vez.

Aunque los hilos se benefician de las tecnologías multinucleos y multiprocesamiento, no significa que una arquitectura simplista no se beneficie de la creación de hilos.

Cuando se construye una aplicación Android, todos los componentes y tareas son introducidos en el hilo principal o hilo de UI (UI Thread). Hasta el momento hemos trabajado de esta forma, ya que las operaciones que hemos realizado toman poco tiempo y no muestran problemas significativos de rendimiento visual en nuestros ejemplos.

Pero en tus proyectos reales no puedes pretender que todas las acciones que lleva a cabo tu aplicación sean simples y concisas.

En ocasiones hay instrucciones que toman unos segundos en terminarse, esta es una de las mayores causas de la terminación abrupta de las aplicaciones. Android está diseñado para ofrecerle al usuario la opción de terminar aquellas aplicaciones que demoran más de 5 segundos en responder a los eventos del usuario.

Cuando esto pasa, podemos ver el famoso Diálogo ANR (Application not respond).
Diálogo ANR de Android
¿Qué pasaría si intentas cargar una imagen jpg de 3MB desde un servidor externo vía HTTP en tu aplicación?, si la conexión es rápida, tal vez nada. Pero para conexiones lentas esto tomara algunos segundos. ¿Crees que se vería muy bien, que tu aplicación se dedique a cargar primero la imagen y luego actualice la interfaz de usuario?

¡En lo absoluto!, esto arruina la fluidez visual y estropea la estadía de nuestros usuarios, lo que en la mayoría de casos termina en la eliminación de tu aplicación. A nivel computacional este caso podría apreciarse de la siguiente forma:
Ejecución de tareas sin el uso de hilos en Android
La imagen ilustra la transición de las tareas que ocurren en el hilo principal de la aplicación. Si tu tarea toma algunos segundos se arruinaría la capacidad de respuesta, ya las tareas están en serie, es decir, hasta que una no acabe la otra no puede iniciar.

El camino correcto es renderizar la interfaz de la aplicación y al mismo tiempo ejecutar en segundo plano la otra actividad para continuar con la armonía de la aplicación y evitar paradas inesperadas. Es aquí donde entran los hilos, porque son los únicos que tienen la habilidad especial de permitir al programador generar concurrencia en sus aplicaciones y la sensación de multitareas ante el usuario.

Esta técnica es mostrada en el siguiente gráfico:
Uso de Hilos en Android
Se ha creado un nuevo hilo donde se ejecuta la tarea en el mismo intervalo de tiempo [t1, t2], pero esta vez el tiempo de ejecución de la tercera tarea UI se extendió debido a que se realizarán pequeños incrementos entre al segundo y tercera tarea. Aunque el tiempo empleado es el mismo, la respuesta ante el usuario simula una aplicación limpia.

Recuerda que existen dos tipos de procesamientos de tareas, Concurrencia y Paralelismo. La concurrencia se refiere a la existencia de múltiples tareas que se realizan simultáneamente compartiendo recursos de procesamiento.

El paralelismo es la ejecución de varias tareas al tiempo en distintas unidades de procesamiento, por lo que es mucho más rápido que la concurrencia.

Ejemplo de Hilos: Ordenar Números Con El Algoritmo Burbuja

Este tutorial no tendría gran valor si no encontrases un buen ejemplo explicativo. Por esta razón verás la construcción de una aplicación llamada AsyncLab.

Dicha aplicación tiene como fin mostrar algunos experimentos de ejecución del Algoritmo de Ordenamiento Burbuja Simple, con 4 diferentes caminos y así evaluar la mejor opción.

Para desbloquear el link de descarga del código completo, sigue estas instrucciones:

En cuanto a diseño, AsyncLab consiste en una actividad Main que despliega un menú construido a través de una lista. Cada uno de los ítems representa un experimento aislado que muestra al usuario el comportamiento que se produce. Precisamente ese comportamiento es mostrado en una segunda actividad hija llamada ABTest, donde existe el botón sortButton para iniciar el ordenamiento de los números y cancelButton para cancelar la operación.

Experimento #1: Ordenar números sin Hilos en Java

Para este experimento se debe aclarar que cada ítem de la actividad Main es dirigido a la misma actividad ABTest, es decir, no se creó una actividad o fragmento para cada uno. Lo que se hizo fue establecer una condición dentro del método onClick() del botón “Ordenar”.

Veamos:

public void onClickSort(View v) {

        switch (position){
            case 0:
                // Experimento #1
                break;
            case 1:
                // Experimento #2
                break;
            case 2:
                // Experimento #3
                break;
            case 3:
                // Experimento #4
                break;

        }

    }

Un programador que desconozca la existencia de la programación concurrente y el uso de hilos en Java, abordaría una solución simplista para ordenar los números. Se le ocurría crear un procedimiento para representar el algoritmo burbuja y lo usaría directamente cuando el botón de ordenamiento sea pulsado. Luego mostraría un Toast para indicarle al usuario que la tarea se ha llevado a cabo.

 public void onClickSort(View v) {

        switch (position){
            case 0:
                bubbleSort(numbers);
       Toast.makeText(
                        getBaseContext(), 
                        "¡Números Ordenados!", 
                        Toast.LENGTH_LONG).show();
                break;
    ...
}

private void bubbleSort(int[] numbers) {

    int aux;

    for (int i = 0; i < numbers.length - 1; i++) {
        for (int j = 0; j < numbers.length -1; j++) {
            if (numbers[j] > numbers[j+1])
            {
                aux          = numbers[j];
                numbers[j]   = numbers[j+1];
                numbers[j+1] = aux;
            }
        }
    }

}

Este enfoque es completamente válido y funcional hasta cierto punto. Recuerda que el algoritmo de ordenamiento burbuja puede llegar a tener un orden de complejidad de O(N2) dependiendo de la dispersión de los números.

Esto significa que si en algún momento la cantidad de números requiere una cantidad de segundos considerable, el usuario debe esperar a que se termine la ejecución del algoritmo antes de poder interactuar de nuevo con la aplicación.
Bloqueo del hilo principal de una aplicación Android
Al ejecutar este experimento el botón “Ordenar” queda seleccionado y la interfaz se congela. Si das taps prolongadamente, para intentar que la aplicación responda, obtendrás un diálogo ANR.

Experimento #2: Usar Hilos en Java para ordenar los números

Si recuerdas la época en que veías tus clases de Java en el instituto, se usaba el paquete java.util.concurrent para acceder a la clase Thread que es la que representa un hilo. También has de recordar que un hilo ejecuta las instrucciones que se implementan el método run() de la interfaz Runnable. Y que para activar estas sentencias se ha de invocar al método start() para iniciar la ejecución.

Aplicando esta definición puedes crear un método que construya un hilo para añadir la ejecución del método bubbleSort():

public void onClickSort(View v) {

        switch (position){
          ...
            case 1:
                execWithThread();
                break;
    ...
}

public void execWithThread(){

    new Thread(
            new Runnable() {
                @Override
                public void run() {
                    bubbleSort(numbers);                    
                    Toast.makeText(
                        getBaseContext(), 
                        "¡Números Ordenados!", 
                        Toast.LENGTH_LONG).show();
                }
            }
    ).start();

}

Aunque el código anterior parece correcto, al iniciar este experimento obtendrás un error debido a que no es aceptada la creación de instancias de la clase Toast dentro de un Hilo.

La documentación de Android recomienda no acceder directamente a los objetos del hilo de UI desde los hilos creados manualmente. Advierten que pueden llegar a producirse anomalías debido a la ausencia de sincronización.

Para evitar acceder directamente sobre los elementos de la UI, puedes usar algunos de los siguientes métodos: Activity.runOnUiThread(), View.post(Runnable) y View.postDelayed(Runnable, long). Estos nos permitirán presentar la información necesaria que se ha procesada en UI Thread.

runOnUiThread() es ideal para presentar resultados en el UI Thread cuando se ejecutan sentencias generales asociadas a una actividad. El método post() se usa para relacionar un hilo al contenido de un View específico. postDelayed() realiza exactamente lo mismo que post(), solo que retrasa n milisegundos el inicio del hilo.
El ejemplo anterior quedaría asegurado con la siguiente definición:

public void execWithThread(){

    new Thread(
            new Runnable() {
                @Override
                public void run() {
                    bubbleSort(numbers);
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(
                                getBaseContext(), 
                                "¡Números Ordenados!", 
                                Toast.LENGTH_LONG).show();
                        }
                    });
                }
            }
    ).start();


}

Hacemos exactamente lo mismo pero esta vez asignamos ejecutamos el método makeText() del de runOnUiThread() junto a una nueva instancia Runnable que comunique las acciones al hilo principal. Internamente esta operación entra a un proceso de cola de peticiones, donde el main Thread gestionará el momento adecuado para iniciarla.

Experimento #3: Usar AsyncTasks en Android

Existen casos en los que se ejecutan varias instrucciones que deben presentar cambios en el hilo principal. Si se aplica el enfoque visto en la sección anterior el código para el envío de las ejecuciones tiende a ser muy largo, confuso y poco maleable a la hora de mantenimiento.

Por esta razón ha sido creada la interfaz AsyncTask, cuyo objetivo es liberar al programador del uso de hilos, la sincronización entre ellos y la presentación de resultados en el hilo primario. Esta clase unifica los aspectos relacionados que se realizarán en segundo plano y además gestiona de forma asíncrona la ejecución de las tareas.

Para implementarla debes extender una nueva clase con las características de AsyncTask e implementar los métodos correspondientes para la ejecución en segundo plano y la publicación de resultados en el UI Thread. Adaptemos el ejemplo anterior a esta filosofía:

private class SimpleTask extends AsyncTask<Void, Integer, Void> {

  /*
  Se hace visible el botón "Cancelar" y se desactiva
  el botón "Ordenar"
   */
  @Override
  protected void onPreExecute() {
      cancelButton.setVisibility(View.VISIBLE);
      sortButton.setEnabled(false);
  }

  /*
  Ejecución del ordenamiento y transmision de progreso
   */
  @Override
  protected Void doInBackground(Void... params) {
      int aux;

      for (int i = 0; i < numbers.length - 1; i++) {
          for (int j = 0; j < numbers.length -1; j++) {
              if (numbers[j] > numbers[j+1])
              {
                  aux          = numbers[j];
                  numbers[j]   = numbers[j+1];
                  numbers[j+1] = aux;
              }
          }
          // Notifica a onProgressUpdate() del progreso actual
          if(!isCancelled())
              publishProgress((int)(((i+1)/(float)(numbers.length-1))*100));
          else break;
      }
      return null;
  }

  /*
   Se informa en progressLabel que se canceló la tarea y
   se hace invisile el botón "Cancelar"
    */
  @Override
  protected void onCancelled() {
      super.onCancelled();
      progressLabel.setText("En la Espera");
      cancelButton.setVisibility(View.INVISIBLE);
      sortButton.setEnabled(true);
  }

  /*
  Impresión del progreso en tiempo real
    */
  @Override
  protected void onProgressUpdate(Integer... values) {
      super.onProgressUpdate(values);
      progressLabel.setText(values[0] + "%");
  }

  /*
  Se notifica que se completó el ordenamiento y se habilita
  de nuevo el botón "Ordenar"
   */
  @Override
  protected void onPostExecute(Void result) {
      super.onPostExecute(result);
      progressLabel.setText("Completado");
      sortButton.setEnabled(true);
  }

}

SimpleTask se extiende de AsyncTask que además de ser abstracta es genérica. Las tres variables de entrada que posee se refieren a los Parámetros, Unidades de Progreso y Resultados respectivamente.

La clase AsyncTask posee métodos te permitirán coordinar la ejecución de las tareas que deseas ubicar en segundo plano. Estos métodos tienen los siguientes propósitos:

  • onPreExecute(): En este método van todas aquellas instrucciones que se ejecutarán antes de iniciar la tarea en segundo plano. Normalmente es la inicialización de variables, objetos y la preparación de componentes de la interfaz.
  • doInBackground(Parámetros…): Recibe los parámetros de entrada para ejecutar las instrucciones especificas que irán en segundo plano, luego de que ha terminado onPreExecute(). Dentro de él podemos invocar un método auxiliar llamado publishProgress(), el cual transmitirá unidades de progreso al hilo principal. Estas unidades miden cuanto tiempo falta para terminar la tarea, de acuerdo a la velocidad y prioridad que se está ejecutando.
  • onProgressUpdate(Progreso…): Este método se ejecuta en el hilo de UI luego de que publishProgress() ha sido llamado. Su ejecución se prolongará lo necesario hasta que la tarea en segundo plano haya sido terminada. Recibe las unidades de progreso, así que podemos usar algún View para mostrarlas al usuario para que este sea consciente de la cantidad de tiempo que debe esperar.
  • onPostExecute(Resultados…): Aquí puedes publicar todos los resultados retornados por doInBackground() hacia el hilo principal.
  • onCancelled(): Ejecuta las instrucciones que desees que se realicen al cancelar la tarea asíncrona.

Comprendiendo estas propiedades, la clase SimpleTask queda fácil de asimilar. Si observas onPreExecute(), se comienza por hacer visible el botón cancelButton ( ya que solo aparece cuando la tarea asíncrona está en ejecución) y luego se desactiva el botón sortButton para evitar la ejecución la actividad un sinnúmero de ocasiones.

@Override
protected void onPreExecute() {
    cancelButton.setVisibility(View.VISIBLE);
    sortButton.setEnabled(false);
}

En el caso de doInBackground() se han puesto parámetros de tipo Void, ya que no se recibe valores de entrada y solo se ejecutarán las instrucciones del ordenamiento burbuja. Si eres buen observador, el método publishProgress() aparece al finalizar el primer bucle for. Esto con el fin de obtener una medida relativa en tiempo real del progreso actual. Matemáticas básicas!

El tipo de dato para las unidades de progreso es Integer, así se obtienen números enteros que muestren un porcentaje entre el intervalo [0, 100]. Dicha medida se muestra en un TextView llamado progressLabel.

@Override
protected void onProgressUpdate(Integer... values) {
    super.onProgressUpdate(values);
    progressLabel.setText(values[0] + "%");
}

En onPostExecute() se recibe un tipo Void como resultado, debido a que no se recibe retorno de doInBackground(). Aquí aprovecharás para restablecer sortButton y cancelButton a su estado inicial. También puedes avisar a través de progressLabel que se ha completado el trabajo.

@Override
protected void onPostExecute(Void result) {
    super.onPostExecute(result);
    progressLabel.setText("Completado");
    cancelButton.setVisibility(View.INVISIBLE);
    sortButton.setEnabled(true);
}

Ahora solo queda usar el método execute() para comenzar a ejecutar la tarea asíncrona en el método onClick():

public void onClickSort(View v) {
        switch (position){
             ...
            case 2:
                execWithAsyncTask();
                break;
                ...
}

public void execWithAsyncTask(){
        simpleTask= new SimpleTask();
        simpleTask.execute();
}

Las Tareas Asíncronas deben ser creadas, cargadas y ejecutadas dentro del UI Thread para su correcto funcionamiento.
Progreso de una Tarea Asíncrona en Android

Cancelar una tarea Asíncrona

Puedes detener la ejecución de una tarea asíncrona usando el método cancel(). Este invoca el método onCancelled(), en vez de doInBackground(), por lo que se descartarán los resultados que estén por entregarse al hilo principal. Si necesitas saber el momento exacto en que terminó la tarea, puedes comprobar el valor arrojado por el método isCancelled()(que retorna en true si ya se ha cancelado):

En AsyncLab la tarea asíncrona se cancela en el método onClick() con cancelButton.

public void onClickCancel(View v){
    simpleTask.cancel(true);
}

Como viste en la definición de SimpleTask, onCancelled() se sobrescribe para que ya no muestre unidades de progreso en progressLabel y para restablecer los estados de los botones:

@Override
protected void onCancelled() {
    super.onCancelled();
    progressLabel.setText("En la Espera");
    cancelButton.setVisibility(View.INVISIBLE);
    sortButton.setEnabled(true);
}

Complementariamente se coordina la detención del ordenamiento burbuja con un break si en algún momento la actividad se ha cancelado:

if(!isCancelled())
    publishProgress((int)(((i+1)/(float)(numbers.length-1))*100));
else break;

Experimento #4: Retener una AsyncTask ante la Rotación de la Pantalla

Hasta el momento nuestra tarea asíncrona ejecuta el ordenamiento de los números de forma perfecta, pero… ¿Qué pasará si rotas la pantalla mientras se está ordenando?

¿Lo has probado?… desafortunadamente la tarea asíncrona es alterada y la ejecución de onProgressUpdate() es anulada. Esto se debe a que cuando hay un cambio de configuración en tus aplicaciones (rotación de la pantalla, cambio de idioma, cambio de teclado) las actividades llaman a su método onDestroy() y luego a su método onCreate() para actualizar su layout y recursos.

Lo que quiere decir que comienza un nuevo ciclo de vida para la actividad y por ende el hilo principal toma otro rumbo.

Lee también Ciclo de Vida de una Actividad en Android

Para solucionar este pequeño inconveniente se usará el método setRetainInstace(), el cual permite retener las características de un fragmento ante un cambio de configuración. En consecuencia crearemos un fragmento personalizado, donde se añada una instancia de la tarea asíncrona ProgressBarTask con el fin de que el fragmento la proteja ante el cambio de configuración.

Lee También Fragmentos en una Aplicación Android

Cabe añadir que para el progreso de la tarea usaremos una ProgressBar en vez de progressLabel, pero básicamente el comportamiento es igual a SimpleTask.

public class HiddenFragment extends Fragment {

    /*
    Interfaz para la comunicación con la actividad ABTest.
     */
    static interface TaskCallbacks {
        void onPreExecute();
        void onProgressUpdate(int progress);
        void onCancelled();
        void onPostExecute();
    }

    private TaskCallbacks mCallbacks;
    ProgressBarTask progressBarTask;

    public HiddenFragment() {}

    @Override
    public void onAttach(Activity activity){
        super.onAttach(activity);
        //Obtener la instancia de ABTest
        mCallbacks = (TaskCallbacks) activity;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Retener el fragmento creado
        setRetainInstance(true);

        //Una vez creado el fragmento se inicia la tarea asincrona
        progressBarTask = new ProgressBarTask();
        progressBarTask.execute();
    }

    @Override
    public void onDetach(){
        super.onDetach();
        mCallbacks = null;
    }


    public class ProgressBarTask extends AsyncTask<Void, Integer, Long> {

        @Override
        protected void onPreExecute() {
            if (mCallbacks != null) {
                mCallbacks.onPreExecute();
            }
        }

        @Override
        protected Long doInBackground(Void... params) {
            long t0 = System.currentTimeMillis();

            int aux;
            int numbers[] = ABTest.numbers;

            for (int i = 0; i < numbers.length - 1; i++) {
                for (int j = 0; j < numbers.length -1; j++) {
                    if (numbers[j] > numbers[j+1])
                    {
                        aux          = numbers[j];
                        numbers[j]   = numbers[j+1];
                        numbers[j+1] = aux;
                    }
                }
                if(!isCancelled())
                    publishProgress((int)(((i+1)/(float)(numbers.length-1))*100));
                else break;

            }

            return t0;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {

            if (mCallbacks != null) {
                mCallbacks.onProgressUpdate(values[0]);
            }
        }

        @Override
        protected void onPostExecute(Long aLong) {
            if (mCallbacks != null) {
                mCallbacks.onPostExecute();
            }
        }


    }

}

Se le ha llamado HiddenFragment porque no posee interfaz de usuario (por eso su método onCreateView() no está sobrescrito). Su estructura está formada por una interfaz llamada TaskCallbacks que le permitirá comunicarse con la actividad ABTest, donde se han añadido 4 métodos de comunicación para sobrescribir respectivamente los métodos callback de la clase ProgressBarTask.

Recuerda que para que la comunicación se dé, es necesario obtener la instancia de la actividad ABTest en el método onAttach(). Una vez realizado esto, es posible comenzar a llamar todas las implementaciones de los métodos de la interfaz que han sido definidos en la actividad. Es de vital importancia que ejecutes la tarea en el método onCreate() del fragmento para arraigarla con setRetainInstance().

Al implementar la interfaz TaskCallbacks en ABTest es necesario sobrescribir los métodos de la siguiente forma:

@Override
public void onPreExecute() {
    progressBar.setVisibility(View.VISIBLE);
    cancelButton.setVisibility(View.VISIBLE);
    sortButton.setEnabled(false);
}

@Override
public void onProgressUpdate(int progress) {
    progressBar.setProgress(progress);
    progressLabel.setText(progress+"%");
}

@Override
public void onCancelled() {
    progressBar.setVisibility(View.INVISIBLE);
    cancelButton.setVisibility(View.INVISIBLE);
    progressLabel.setText("En la Espera");
    sortButton.setEnabled(true);
}

@Override
public void onPostExecute() {
    progressBar.setVisibility(View.INVISIBLE);
    cancelButton.setVisibility(View.INVISIBLE);
    progressLabel.setText("Completado");
    sortButton.setEnabled(true);

}

Básicamente se oculta la ProgressBar al igual que se hacía con la progressLabel y luego configuras los botones, para que aparezcan en la situación adecuada. También se usa el método setProgress() para actualizar el estado de realización de la tarea.

A continuación crea el fragmento justo cuando sea presionado sortButton para que la tarea se inicie.

private void execWithProgresBar() {
        FragmentManager fg = getFragmentManager();
        fragment = new HiddenFragment();
        FragmentTransaction transaction = fg.beginTransaction();
        transaction.add(fragment, HIDDEN_FRAGMENT_TAG);
        transaction.commit();
    }

Finalmente asegura que los botones y la barra se restablezcan correctamente en el método onCreate() de ABTest cuando surja la rotación de la pantalla:

fragment = (HiddenFragment)getFragmentManager().
                findFragmentByTag(HIDDEN_FRAGMENT_TAG);

if(position==3 && fragment!=null) {
    if(<strong>fragment.progressBarTask.getStatus()== AsyncTask.Status.RUNNING</strong>){
        progressBar.setVisibility(View.VISIBLE);
        cancelButton.setVisibility(View.VISIBLE);
        sortButton.setEnabled(false);
    }
}

En castellano, el código anterior se traduce a “Si se ha elegido la opción 3 y el fragmento ya ha sido creado, entonces compruebe si la tarea está en ejecución. Si es así, entonces mantener visible la barra de progreso, mantener visible el botón de cancelar y además conservar el estado de inactividad de sortButton”.

Supongo que ya deduces que getStatus() obtiene el estado actual de la tarea y que RUNNING es un tipo enumerado que representa el estado de ejecución.

Ahora prueba el experimento y verás como al cambiar a landscape, la ProgressBar y la tarea asíncrona siguen intactas.
Aplicación Android con una ProgressBar en Landscape

Conclusiones

No hace falta justificar ampliamente el porqué es mejor usar la clase AsyncTask para gestionar hilos y trabajos en segundo plano. Este mecanismo permite optimizar tanto el flujo de tus aplicaciones como el orden de codificación de las Actividades.

Así que la próxima vez que debas poner en ejecución una operación que requiere algunos segundos considerables, no dudes en acudir a las tareas asíncronas.

Icono de la Aplicación AsyncLab cortesía de IconFinder

  • José

    Excelente info. Quería preguntarte si hay una cantidad límite de tareas asíncronas en ejecución o eso depende de la capacidad de cada equipo?

  • José

    Excelente info. Quería preguntarte si hay una cantidad límite de tareas asíncronas en ejecución o eso depende de la capacidad de cada equipo?

  • Jesuso Marfil

    hola, tengo una duda, realice una aplicacion con conexion a base de datos remota, y las consultas las hago en hilos, el detalle esta en que la aplicacion funciona perfecto en android 4.1 a android 4.4 epero al intentar ejecutarlo en android 5. se detiene la aplicacion. hay algun cambio para la programacion de hilos en android 5?

    • Hola Jesuso. ¿Que causa te arroja del logcat? ¿Podrías subir un screenshot aquí al comentario?

  • Pedro Joya

    Viendo tu magnífico tutorial y su código, me ha entrado la siguiente duda: ¿habría algún problema de memory leak por el hecho de que la clase interna ProgressBarTask no sea static y mantenga por tanto una referencia a la actividad a través de la variable mCallbacks? ¿o al poner mCallbacks = null en el onDetach del fragmento ya se soluciona este problema?

    Muchas gracias por tu blog, es una maravilla. Me encanta que expliques los temas con tanto detalle.

    • Hola Pedro. Si claro, la referencia es limpiada en onDetach() como la documentación lo dice

  • Paulo Felipe Prado Gärtner

    Buena explicación, muchas gracias. Tengo una duda, tengo una aplicación donde debo cargar varios spinner con diferentes valores que vienen de un servicio web. Cada llamado al servicio se realiza con una tarea asincrona, pero cómo puedo hacer para tener un único progress dialog mientras se cargan todos los spinner?

  • Alex Yengle Cosamalon

    Te entendí mas que a cualquier otro tutorial que haya visto. Solo que hay algo que no has explicado: 1: este hilo se mantiene activo así cierre la aplicacion o apague el celular? y 2: si se diera el caso que yo coloco un hilo en mi actividad principal, es decir, en la primera pantalla o de bienvenida como quieras llamarlo, cómo hago para que ese hilo no se vuelva a abrir cuando abro nuevamente la aplicacion? Gracias de antemano y espero puedas responder pronto (Y)

  • Daniel

    Hola, disculpa tengo una duda es que tengo un EditText donde hago una consulta a una base de datos con lo que se ingresa y lo que quiero hacer es que se ejecute el Asynctask cuando se oculte el teclado.¿como puedo detectar cuando el teclado virtual se ha ocultado?

  • Raul Eduardo Perez

    Como puedo obtener el codigo completo, luego de unirme a sus redes sociales?

    • El código lo puedes descargar del link que aparece al compartir el artículo con una cajita que está al inicio del titulo “Ejemplo de hilos”

  • Jaime Tellez

    Hola, felicidades por tu trabajo. tengo una consulta, espero me puedan ayudar. uso un Asynctask para hacer una consulta a una base de datos por medio de un webService usando la libreria Volley. el asunto es que en doInBackground que es donde se hace la consulta, el problema es que me llama a onPostExecute antes de haber terminado la consulta. este es mi Asynctask.

    class ActualizarDatos extends AsyncTask {

    @Override
    protected void onPreExecute() {
    super.onPreExecute();
    pDialog = new ProgressDialog(Ajustes.this);
    pDialog.setMessage(“Actualizando…”);
    pDialog.setIndeterminate(false);
    pDialog.setCancelable(true);
    pDialog.show();

    }

    @Override
    protected Boolean doInBackground(String… params) {

    final String nombreUsuario = names;
    final String TelefonoUsuario = tels;
    final String PassUsuario = passwords;
    final String EmailUsuario = emails;

    HashMap map = new HashMap();

    map.put(“email”, EmailUsuario);
    map.put(“password”, PassUsuario);
    map.put(“name”, nombreUsuario);
    map.put(“tel”, TelefonoUsuario);

    JSONObject jobject = new JSONObject(map);

    Log.d(TAG, jobject.toString());

    VolleySingleton.getInstance(Ajustes.this).addToRequestQueue(
    new JsonObjectRequest(
    Request.Method.POST,
    Constantes.UPDATE_USUARIOS,
    jobject,
    new Response.Listener() {

    @Override
    public void onResponse(JSONObject response) {
    try {
    String estado = response.getString(“estado”);
    switch (estado) {
    case “1”:
    update = true;
    Log.d(“Successful!”, response.toString());

    break;
    case “2”:
    update = false;
    String mensaje2 = response.getString(“mensaje”);
    Toast.makeText(
    Ajustes.this,
    mensaje2,
    Toast.LENGTH_LONG).show();
    break;

    }

    } catch (JSONException e) {
    e.printStackTrace();
    }
    }
    },
    new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
    Log.d(TAG, “Error Volley: ” + error.toString());
    Toast.makeText(Ajustes.this, “Respuesta incorrecta, verifica tu acceso a internet”, Toast.LENGTH_LONG).show();
    }
    }

    ) {
    @Override
    public Map getHeaders() {
    Map headers = new HashMap();
    headers.put(“Content-Type”, “application/json; charset=utf-8”);
    headers.put(“Accept”, “application/json”);
    return headers;
    }

    @Override
    public String getBodyContentType() {
    return “application/json; charset=utf-8” + getParamsEncoding();
    }
    }
    );

    return update;

    }

    @Override
    protected void onPostExecute(Boolean result ) {
    super.onPostExecute(result);
    pDialog.dismiss();
    if(result){
    switch (TAG_Vactualizado){
    case “usuario”:
    datos.setNameUsuario(names);
    AlertDialog.Builder dr = new AlertDialog.Builder(Ajustes.this);
    dr.setTitle(“Nombre Actualizado”);
    dr.setMessage(“Tu nombre se ha actualizado correctamente. “);
    dr.setCancelable(false);
    dr.setPositiveButton(“Aceptar”, new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
    new CargaDatos().execute();

    }
    });

    dr.show();
    break;
    case “telefono”:
    AlertDialog.Builder dt = new AlertDialog.Builder(Ajustes.this);
    dt.setTitle(“Teléfono Actualizado”);
    dt.setMessage(“Tu número de teléfono se ha actualizado correctamente. “);
    dt.setCancelable(false);
    dt.setPositiveButton(“Aceptar”, new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
    new CargaDatos().execute();
    }
    });

    dt.show();
    break;
    case “contra”:
    AlertDialog.Builder dc = new AlertDialog.Builder(Ajustes.this);
    dc.setTitle(“Contraseña cambiada”);
    dc.setMessage(“Tu contraseña se ha actualizado correctamente. “);
    dc.setCancelable(false);
    dc.setPositiveButton(“Aceptar”, new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
    new CargaDatos().execute();

    }
    });

    dc.show();
    break;
    }
    }else{
    AlertDialog.Builder de = new AlertDialog.Builder(Ajustes.this);
    de.setTitle(“Error”);
    de.setMessage(“Tus Datos no se han podido actualizar, intenta nuevamente.”);
    de.setCancelable(false);
    de.setPositiveButton(“Aceptar”, new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
    new CargaDatos().execute();

    }
    });

    de.show();
    }
    }

    }

    al verlo se darán cuenta que como no espera a que termine la consulta, me afecta en el resultado, ya que update sera falso siempre. alguna solución? muchas gracias!

    • No te comprendo compañero. onPreExecute() se supone que se llama antes de el doinbackground(). Si quieres mostrar un progreso de la consulta debes usar onProgressUpdate().

      • Jaime Tellez

        Gracias por tu atenta respuesta, jejej ya logré solucionarlo, resulta que Volley, al crear un request para hacer la consulta, lo hace en un hilo diferente al principal de forma automatica jejeje así que no necesitava el asynkTask jejej y ese era el problema. De igual manera muchisimas gracias.

  • Leo

    Muy buena pagina explica mucho mejor que las demás que e leído (incluyendo la que estan en ingles) ahora una pregunta si necesito que se ejecute un procedimiento mas largo digamos un envio de foto puedo llamar el procedimiento o tengo que hacer el procedimiento dentro de doInBackground(Void… params)

    • Hola Leo.

      La carga va en el doInBackground(). Solo debes cambiar su retorno a Bitmap. Luego lo decodificas con el método BitmapFactory.decode() dentro de onPostExecute().

  • Luis

    muy buen articulo =) desde que vi este blog por primera vez lo sigo pues he aprendido. una pregunta soy nuevo en este mundo del desarrollo como puedo ver las tareas que estan en cola del asynctask.

    en mi asynctask paso los siguientes parametros

    new DownloadFileFromURL().execute(carpeta, urlPage, numPage);

    donde carpeta es un nombre de carpeta donde descargare unos archivos el problema que tengo es que tengo un boton de descarga el cual ejecuta el asynctask desde un webview como puedo hacerle para que si se presiona varias veces ese botón indique que ese elemento ya esta siendo descargado y no me genere mas tareas en cola?

    • James Revelo

      Que tal Luis?

      Usa el método getStatus() de AsyncTask.

      Luego comprueba si su estado es AsyncTask.Status.RUNNING, si es así, pues ya decides que hacer.

      Saludos!