Bienvenidos sean a este post, como vimos hasta ahora los hilos son los encargados de ejecutar nuestras tareas pero existe la posibilidad de que una de estas tareas puede bloquear la interfaz del usuario pero para evitar esto se utiliza AsyncTask, esta forma de trabajo conocida como Tarea asincronica permite la creacion de un hilo secundario para derivar procesos complejos y luego derivados a nuestro hilo principal o interfaz del usuario, su estructura es similar a esta:
class MiTarea extends AsyncTask< Parametros, Progreso, Resultado >{
@Override protected void onPreExecute(){
.... instrucciones ...
}
@Override protected Resultado doInBackground(Parametros... p){
... instrucciones ...
}
@Override protected void onProgressUpdate(Progreso... prog){
... instrucciones ...
}
@Override protected void onPostExecute(Resultado resultado){
... instrucciones ...
}
}
Esta es la estructura de como definir una tarea asimcronica, Los tres primeros atributos de la clase (Parametros, Progreso, Resultado) deben ser reemplazados con clases mas acordes a los tipos de datos para trabajar, ahora veamos los cuatros metodos mostrados anteriormente:
- onPreExecute, se utiliza para ejecutar las trabajos previos a la ejecucion de la tarea
- doInBackground(Parametros…), se llama despues del metodo anterior, es el mas importante porque justamente es el encargado de ejecutar la tarea por medio de un hilo secundario
- onProgressUpdate(Progreso…), este se utiliza para mostrar el progreso de la tarea del usuario, como se ejecuta en el hilo de la interfaz podremos interactuar con ella
- onPostExecute(Resultado), es la encargada de mostrar en la interfaz del usuario el resultado de la tarea.
Para entenderlo un poco mejor vamos a modificar el ejemplo creado en el post de Threads de la siguiente forma, vamos a nuestra clase MainActivity y vamos a agregar la siguiente clase:
class MiTarea extends AsyncTask< Integer, Void, Integer >{
@Override
protected Integer doInBackground(Integer... n){
return factorial(n[0]);
}
@Override
protected void onPostExxcute(Integer res){
salida.append(res + "\n");
}
}
Veamos esta clase, como dijimos antes los tres parametros de la misma se deben reemplazar por los tipos de datos necesarios para nuestra tarea asincronica, luego utilizaremos el metodo doInBackground() para ejecutar el metodo factorial() y poder realizar el calculo en segundo plano, y el siguiente metodo es el encargado de mostrarlo en pantalla, ahora iremos al metodo calcularOperacion() y reemplazaremos estas dos lineas:
MiThread hilo = new MiThread(n); hilo.start();
Por estas dos nuevas lineas:
MiTarea tarea = new MiTarea(); tarea.execute(n);
Observen como modificamos el metodo start() por execute() y en este metodo enviamos el valor del elemento entrada, en este caso por n. Si lo prueban observaran como ahora nuestra app sigue funcionando de la misma forma, para observar el progreso de la misma haremos algunas modificaciones en nuestra clase MiTarea, reemplacen el codigo de MiTarea por el siguiente:
class MiTarea extends AsyncTask< Integer, Integer, Integer >{
private ProgressDialog progreso;
@Override
protected void onPreExecute(){
progreso = new ProgressDialog(MainActivity.this);
progreso.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progreso.setMessage("Calculando...");
progreso.setCancelable(false);
progreso.setMax(100);
progreso.setProgress(0);
progreso.show();
}
protected Integer doInBackground(Integer... n){
int res = 1;
for(int i = 1; i <=n[0]; i++){
res *= i;
SystemClock.sleep(1000);
publishProgress(i*100/n[0]);
}
return res;
}
protected void onProgressUpdate(Integer... porc){
progreso.setProgress(porc[0]);
}
protected void onPostExecute(Integer res){
progreso.dismiss();
salida.append(res + "\n");
}
}
En este caso modificamos nuestra clase de la siguiente forma, primero creamos un objeto de DialogProgress llamado progreso para mostrar una ventana de progreso, luego definimos el metodo de onPreExecute() para generar esta ventana, con sus mensajes y todo lo pertinente, luego tendremos el metodo doInBackground() que a diferencia del caso anterior no utilizaremos a factorial() sino que utilizaremos los mismos procedimientos para hacer el calculo pero con la diferencia de que agregaremos la siguiente linea:
publishProgress(i*100/n[0]);
Esta linea es la encargada de generar el progreso de nuestro bucle pero a su vez cuando es utilizada llamara al siguiente metodo llamada onProgressUpdate() el cual sera el encargado de mostrar el valor generado por esa linea en nuestro cuadro de dialogo, y por ultimo cuando haya terminado utilizara el metodo onPostExecute() para cerrar la ventana de progreso y luego asignara el valor resultante a salida, aca les muestro un ejemplo de como queda el cuadro de dialogo

Como pueden ver ahora podemos ver cuanto tarda en procesar la tarea de nuestro hilo, para dejarlo un poco mas practico vamos a agregar la opcion de cancelar la accion, para ello haremos algunas modificaciones sobre la clase MiTarea, agregaremos las siguientes lineas:
progreso.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
MiTarea.this.cancel(true);
}
});
Como pueden lo que hicimos en estas lineas fue primero habilitar un boton, el cual va a ser el boton de Cancel, luego agregaremos un evento listener para el click y en caso de ser ejecutado seteamos a cancel como verdad, ahora agregaremos una modificacion mas en la siguiente linea:
for(int i = 1; i <=n[0]; i++){
Por esta linea:
for(int i = 1; i <=n[0] && !isCancelled(); i++){
Esta modificacion lo unico que hace es efectuar el bucle siempre que i sea menor a n[0] y el estado sea distinto de cancelado, por ultimo agregaremos el siguiente metodo dentro de la clase AsyncTask:
protected void onCancelled(){ salida.append("Cancelado\n"); }
Esta reemplazara el mensaje de salida por Cancelado siempre y cuando el progreso sea cancelado, antes de probar nuestra app veamos como quedo finalmente el codigo fuente de nuestra clase MainActivity:
package org.example.hilos;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends Activity {
private EditText entrada;
private TextView salida;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
entrada = (EditText) findViewById(R.id.entrada);
salida = (TextView) findViewById(R.id.salida);
}
class MiThread extends Thread {
private int n, res;
public MiThread(int n){
this.n = n;
}
@Override
public void run(){
res = factorial(n);
runOnUiThread(new Runnable() {
@Override
public void run() {
salida.append(res + "\n");
}
});
}
}
public void calcularOperacion(View view){
int n = Integer.parseInt(entrada.getText().toString());
salida.append(n + " ! = ");
MiTarea tarea = new MiTarea();
tarea.execute(n);
}
public int factorial(int n){
int res = 1;
for(int i=1; i<=n; i++){
res *=i;
SystemClock.sleep(1000);
}
return res;
}
class MiTarea extends AsyncTask< Integer, Integer, Integer >{
private ProgressDialog progreso;
@Override
protected void onPreExecute(){
progreso = new ProgressDialog(MainActivity.this);
progreso.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progreso.setMessage("Calculando...");
progreso.setCancelable(false);
progreso.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
MiTarea.this.cancel(true);
}
});
progreso.setMax(100);
progreso.setProgress(0);
progreso.show();
}
protected Integer doInBackground(Integer... n){
int res = 1;
for(int i = 1; i <=n[0] && !isCancelled(); i++){
res *= i;
SystemClock.sleep(1000);
publishProgress(i*100/n[0]);
}
return res;
}
protected void onProgressUpdate(Integer... porc){
progreso.setProgress(porc[0]);
}
@Override
protected void onCancelled(){
salida.append("Cancelado\n");
}
protected void onPostExecute(Integer res){
progreso.dismiss();
salida.append(res + "\n");
}
}
}
Ahora podemos probar nuestra app y se vera de la siguiente forma:
Como pueden ver tenemos ahora una hermosa ventana la cual nos va indicando como se esta procesando el calculo pero a su vez con una opcion de interrumpir (cancelar) el calculo y este nos informa porque no se pudo hacer el mismo, ahora pasemos a hablar un poco sobre el ultimo metodo llamado get() en AsyncTask.
Este metodo es ideal para cuando necesitamos el resultado de la tarea para continuar con el programa, su forma de trabajar es esperar a que termine la tarea para devolver el resultado obtenido, puede sonar muy util pero este metodo tiende a bloquearse hasta que se termina la tarea, y esto es justamente por lo que utilizamos AsyncTask. Pero este metodo tiene una forma de sobrecarga que nos permite establecer el tiempo que estara bloqueado, por ejemplo esto seria muy bueno para cuando necesitamos que el usuario no tenga acceso al hilo de la interfaz del usuario como podria ser una ventana de ingreso de usuario, por eso como dijimos este metodo es interesante para utilizarlo cuando el bloqueo del usuario al hilo de la interfaz es necesario o no nos afecta en nuestra utilidad.
En resumen, hoy hemos aprendido que es y para que sirve AsyncTask, como nos da la posibilidad de crear hilos secundarios para no bloquear a nuestro hilo principal, como podemos tener distintas formas de poder mejorar la experiencia para el usuario y por sobre todo como mejorar la performance de nuestras apps con este tipo de uso, espero les haya sido util sigueme en Twitter, Facebook o Google+ para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Tambien podes donar
Es para mantenimiento del sitio, gracias!
$1.50