PDM Avanzado AsyncTask

De MediaWiki
Saltar a: navegación, buscar

Introdución

Máis información en: http://developer.android.com/reference/android/os/AsyncTask.html


A clase AsyncTask vainos permitir facer o mesmo que un fío de execución (visto anteriormente) coa diferenza que dende un AsyncTask imos poder ter acceso ós compoñentes gráficos do fío principal.


AsyncTask

Ao crear unha clase que derive da clase AsyncTask, teremos acceso aos seguintes métodos:

  • onPreExecute(). Executarase antes do código principal da nosa tarefa.
  • doInBackground(). Código principal da nosa tarefa. Aquí é onde irá o código que poñemos no método run cando utilizamos un Thread.Non temos acceso ós elementos gráficos
  • onProgressUpdate(). Executarase cada vez que chamemos ó método publishProgress() dende o método doInBackground(). Dende aquí si podemos acceder aos elementos gráficos da Activity.
  • onPostExecute(). Executarase cando finalice a nosa tarefa (cando remate o método doInBackground().
  • onCancelled(). Executarase cando se cancele a execución da tarefa antes de súa finalización normal.

O que nos interesa é a relación entre doInBackground é onProgressUpdate.

Se poñemos dentro do código do método doInBackground a orde publishProgress(), automaticamente vai chamar ó método onProgressUpdate.

O bo de todo é que onProgressUpdate se executa no fío principal e polo tanto imos poder referenciar calquera elemento gráfico da interface, mentres que doInBackground se executa nun fío diferente.

O resto de métodos tamén se executan no fío principal e polo tanto podemos ter acceso a todos os elementos gráficos do fío principal.

Definición de parámetros en AsyncTask

O facer unha clase que derive de AsyncTask necesitamos definir tres parámetros:

  1. private class MiñaTarefa extends AsyncTask<Params, Progress, Result>{
  2.                
  3. };
  • Params: O tipo de dato que recibiremos como entrada no método doInBackground. Se non leva parámetros poñeremos Void (ningún). Podemos enviarlle datos cando dende a activity chamemos ó método execute da AsyncTask.

É dicir, cando eu cre un obxecto da clase MiñaTarefa e poña:

  1. MiñaTarefa tarefa = new MiñaTarefa();
  2. tarefa.execute(valor_enviar);

No método execute podo enviarlle valores en forma de parámetros. Estes valores chegarán ó método doInBackGround en forma de parámetros. Cabe sinalar que podemos enviar varios valores separados por comas ou ben en forma dun array de valores de tipo que sexa.

Se por exemplo, valor_enviar é un integer, teríamos que indicalo na clase MiñaTarefa:

  1. private class MiñaTarefa extends AsyncTask<Integer, Progress, Result>{
  2.                
  3. };

No exemplo que imos desenvolver, non enviamos parámetros e por tanto quedaría como Void.


  • Progress: O tipo de datos có que actualizaremos o progreso da tarefa. Cando chamamos o método publishProgress podemos enviarlle un parámetro e aquí indicamos o tipo de dato.

Num exemplo que imos facer enviaremos un contador que indicará por onde ter que debuxarse unha barra de progreso e polo tanto será un Integer. Como comentamos anteriormente ó chamar ó método publishProgress estamos chamando ó método onProgressUpdate que recibirá dito valor. Polo tanto teremos que definir un parámetro de tipo Integer nese método.

  1. private class MiñaTarefa extends AsyncTask<Void, Integer, Result>{
  2.  
  3.      @Override
  4.      protected Boolean doInBackground(Void... params) {
  5.         publishProgress(valor_integer);
  6.      }
  7.  
  8.      @Override
  9.      protected void onProgressUpdate(Integer... values) {
  10.           int progreso = values[0].intValue();
  11.      }
  12.  
  13.  
  14. };

Nota: Os puntos suspensivos ... indican que se envía ou se pode enviar un array. Por iso cando o recibimos accedemos ó elemento 0 do array.


  • Result: O tipo de dato que devolveremos cando finalice á nosa tarefa. Por exemplo, poderiamos indicar que cando finalice a tarefa esta enviará un Boolean. Ten que ser definido como valor de retorno no método doInBackGround e o seu valor será recibido polo método onPostExecute, en forma de parámetro:
  1. private class MiñaTarefa extends AsyncTask<Void, Integer, Boolean>{
  2.  
  3.   @Override
  4.   protected Boolean doInBackground(Void... params) {
  5.  
  6.         // CANDO FINALICE
  7.         return true;
  8.   }
  9.   @Override
  10.   protected void onPostExecute(Boolean result) {
  11.  
  12.   }
  13. };


Nota: Lembrar que indicamos tipos de datos: Void, Boolean, Integer.

Nota: Importar a clase AsyncTask premendo Ctrol+Shift+O.

Cancelando unha tarefa

Tanto dende a activity principal (a través do obxecto da clase MiñaTarefa) como dentro da propia AsyncTask podemos cancelar a tarefa.

Nese caso, dentro do método doInBackGround, o método isCancelled() devolverá true. Se cancelamos a tarefa, non se chamará ó método onPostExecute, se non a onCancelled.

Por exemplo:

        miñatarefa.cancel(true);

Nese caso, dentro do método doInBackGround, o método isCancelled() devolverá true. Se cancelamos a tarefa, non se chamará ao método onPostExecute, se non a onCancelled.

private class MiñaTarefa extends AsyncTask<Void, Integer, Boolean>{
       ............
       @Override
        protected Boolean doInBackground(Void... params) {
         
                if(isCancelled())
                        return true;
       }

       @Override
        protected void onCancelled() {
                    Toast.makeText(getApplicationContext(), "Tarefa cancelada!",
                        Toast.LENGTH_SHORT).show();
                }      
        };     

       ............
}

Caso práctico: AsyncTask e ProgressBar

Se non o temos creado antes, crearemos un novo paquete de nome: Fios como un subpaquete do teu paquete principal.



  • Dentro do paquete Fios crear unha nova 'Empty Activity' de nome: UD07_01_AsyncTask_1 de tipo Launcher e sen compatibilidade.
Modifica o arquivo AndroidManifiest.xml e engade unha label á activity como xa vimos na creación do proxecto base.



O obxectivo desta práctica e ver o funcionamento dun AsyncTask.

A práctica vai consistir en dous botóns cunha ProgressBar.

PDM Avanzada Threads 4.jpg

Ao premer sobre o botón 'Empezar' a barra de progreso se actualizará utilizando un AsyncTask. Ao premer sobre o botón 'Cancelar' o AsyncTask será cancelado e se informará ó usuario.


A ProgressBar como o seu nome di, é un view que amosa unha barra horizontal (ou se cambiamos o estilo, un círculo 'dando voltas') no que aparece unha segunda barra que vaise facendo máis grande 'cubrindo' a primeira e reflectindo un progreso dunha tarefa.


Creamos a activity

Código do layout xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.    xmlns:tools="http://schemas.android.com/tools"
  4.    android:layout_width="match_parent"
  5.    android:layout_height="match_parent"
  6.    tools:context=".Fios.UD07_01_AsyncTask_1" >
  7.  
  8.  
  9.     <Button
  10.        android:id="@+id/UD7_01_btnEmpezarAsync"
  11.        android:layout_width="wrap_content"
  12.        android:layout_height="wrap_content"
  13.        android:layout_alignParentTop="true"
  14.        android:layout_centerHorizontal="true"
  15.        android:text="EMPEZAR" />
  16.  
  17.     <ProgressBar
  18.        android:id="@+id/UD7_01_progressBarAsync"
  19.        style="?android:attr/progressBarStyleHorizontal"
  20.        android:layout_width="match_parent"
  21.        android:layout_height="wrap_content"
  22.        android:layout_centerHorizontal="true"
  23.        android:layout_centerVertical="true" />
  24.  
  25.     <Button
  26.        android:id="@+id/UD7_01_btnCancelarAsync"
  27.        android:layout_width="wrap_content"
  28.        android:layout_height="wrap_content"
  29.        android:layout_alignLeft="@+id/UD7_01_btnEmpezarAsync"
  30.        android:layout_below="@+id/UD7_01_btnEmpezarAsync"
  31.        android:layout_marginTop="18dp"
  32.        android:text="CANCELAR" />
  33.  
  34. </RelativeLayout>


  • Liña 19: Fixarse como neste caso o estilo da barra é a clásica de progreso, pero se cambiamos o seu valor por: style="?android:attr/progressBarStyleInverse" teremos un círculo que normalmente se utiliza para aquelas tarefas que non sabemos de antemano canto van durar:
PDM Avanzada ASynctask 1B.jpg




Código da clase UD7_01_AsyncTask
Obxectivo: Actualizar unha barra de progreso utilizando un AsyncTask.

  1. package es.cursoandroid.cifprodolfoucha.aprendiendo.Fios;
  2. import android.app.Activity;
  3. import android.os.AsyncTask;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.widget.Button;
  7. import android.widget.ProgressBar;
  8. import android.widget.Toast;
  9. import es.cursoandroid.cifprodolfoucha.aprendiendo.R;
  10.  
  11. public class UD07_01_AsyncTask_1 extends Activity {
  12.     private static final int TEMPO_FINAL = 10;
  13.     private ProgressBar barraProgreso;
  14.     private MiñaTarefa miñaTarefa;
  15.  
  16.  
  17.     private class MiñaTarefa extends AsyncTask<Void, Integer, Boolean> {
  18.  
  19.         @Override
  20.         protected Boolean doInBackground(Void... params) {
  21.             for (int i = 1; i <= TEMPO_FINAL; i++) {
  22.                 try {
  23.                     Thread.sleep(1000);
  24.                 } catch (InterruptedException e) {
  25.                     // TODO Auto-generated catch block
  26.                     e.printStackTrace();
  27.                 }
  28.                 publishProgress(i);
  29.  
  30.                 if (isCancelled())
  31.                     break;
  32.             }
  33.             return true;
  34.         }
  35.  
  36.         @Override
  37.         protected void onProgressUpdate(Integer... values) {
  38.             int progreso = values[0].intValue();
  39.             barraProgreso.setProgress(progreso);
  40.         }
  41.  
  42.         @Override
  43.         protected void onPreExecute() {
  44.             barraProgreso.setProgress(0);
  45.             barraProgreso.setMax(TEMPO_FINAL);
  46.         }
  47.  
  48.         @Override
  49.         protected void onPostExecute(Boolean result) {
  50.             if (result) {
  51.                 Toast.makeText(getApplicationContext(), "Tarefa finalizada!",
  52.                         Toast.LENGTH_SHORT).show();
  53.             }
  54.         }
  55.  
  56.         @Override
  57.         protected void onCancelled() {
  58.             Toast.makeText(getApplicationContext(), "Tarefa cancelada!",
  59.                     Toast.LENGTH_SHORT).show();
  60.         }
  61.     };
  62.  
  63.     private void xestionarEventos(){
  64.  
  65.         Button btnEmpezar = (Button)findViewById(R.id.UD7_01_btnEmpezarAsync);
  66.         btnEmpezar.setOnClickListener(new View.OnClickListener() {
  67.  
  68.             @Override
  69.             public void onClick(View v) {
  70.                 // TODO Auto-generated method stub
  71.  
  72.                 if ((miñaTarefa==null) || (miñaTarefa.getStatus()== AsyncTask.Status.FINISHED)){
  73.                     miñaTarefa = new MiñaTarefa();
  74.                     miñaTarefa.execute();
  75.                 }
  76.                 else {
  77.                     Toast.makeText(getApplicationContext(), "A tarefa non acabou!!!",
  78.                             Toast.LENGTH_SHORT).show();
  79.                 }
  80.             }
  81.         });
  82.  
  83.         Button btnCancelar = (Button)findViewById(R.id.UD7_01_btnCancelarAsync);
  84.         btnCancelar.setOnClickListener(new View.OnClickListener() {
  85.  
  86.             @Override
  87.             public void onClick(View v) {
  88.                 // TODO Auto-generated method stub
  89.                 if (miñaTarefa.getStatus()==AsyncTask.Status.RUNNING){
  90.                     miñaTarefa.cancel(true);
  91.                 }
  92.             }
  93.  
  94.         });
  95.  
  96.  
  97.     }
  98.  
  99.     @Override
  100.     protected void onCreate(Bundle savedInstanceState) {
  101.         super.onCreate(savedInstanceState);
  102.         setContentView(R.layout.activity_ud07_01__async_task_1);
  103.  
  104.         barraProgreso = (ProgressBar) findViewById(R.id.UD7_01_progressBarAsync);
  105.  
  106.         xestionarEventos();
  107.     }
  108. }
  • Liña 12: Definimos o 'tamaño' da barra de progreso.
  • Liña 13: Referencia á barra de progreso.
  • Liña 14: Definimos a tarefa (deriva de AsyncTask).
  • Liñas 17-61: Definimos a clase que deriva de AsyncTask e que vai a actualizar a barra de progreso.
  • Liña 69: Xestionamos o evento de Click sobre o botón de 'Comenzar'.
  • Liña 72: Para comenzar unha tarefa nova comprobamos que non fora instanciada ou ben rematara a anterior.
  • Liña 87: Xestionamos o evento de Click sobre o botón de 'Cancelar'.
  • Liña 89-90: En caso de que a tarefa estea en execución a cancelamos.

Caso Práctico: Utilizando un ProgressDialog. Deprecated API>=26

  • Nota:: Este tipo de diálogo está deprecated dende a versión Android 8 (Api 26).
Se recomenda facer uso dun ProgressBar, como vimos no exemplo anterior ou dunha Notificación.
Podemos por tanto facer uso dun Diálogo Personalizado como xa vimos na Wiki e poñer dentro do layout un ProgressBar.


No caso práctico anterior estamos a utilizar unha Activity para amosar un progreso.

Normalmente utilizaremos un ProgressDialog.


Un ProgressDialog. que como o seu propio nome di, é un tipo especial de diálogo no que se amosa unha barra de progreso (ou un texto). O rango da barra de progreso pode ir dende 0 a 10.000.

A idea é a de aproveitar o AsyncTask para amosar o diálogo actualizando o progreso no método onProgressUpdate, coma fixemos antes.

Podemos facelo de varias formas, unha delas é:

  • Crear un obxecto da clase ProgressDialog:
  1. public class MiñaActivity extends Activity {
  2.                 .................
  3.                 private ProgressDialog meuProgresoDialog;
  4.                 ...........
  5. }


  • Instanciamos dito obxecto no método onCreate:
  1.         @Override
  2.         protected void onCreate(Bundle savedInstanceState) {
  3.                 super.onCreate(savedInstanceState);
  4.                 setContentView(R.layout.activity_asysntask);
  5.                
  6.                
  7.                 meuProgresoDialog = new ProgressDialog(this);
  8.                 MiñaTarefa miñatarefa = new MiñaTarefa();
  9.                 miñatarefa.execute();
  10.         }


  • Modificamos a AsyncTask para inicializar o diálogo de progreso:
  1.         @Override
  2.         protected void onPreExecute() {
  3.                 meuProgresoDialog.setTitle("TAREFA");
  4.                 meuProgresoDialog.setMessage("ACTUALIZANDO");
  5.                 meuProgresoDialog.setCancelable(false);
  6.                 meuProgresoDialog.setIndeterminate(true);
  7.                 meuProgresoDialog.show();
  8.                        
  9.         }

Neste caso o diálogo de progreso non é 'cancelable', cando vai rematar tampouco o sabemos (non vai aparecer unha barra de progreso que indique inicio – fin).


  • Pechamos o ProgressDialog ó rematar a tarefa:
  1.         @Override
  2.         protected void onPostExecute(Boolean result) {
  3.                     if(result)
  4.                     {
  5.                         Toast.makeText(ActivityAsysntask.this, "Tarefa finalizada!",
  6.                             Toast.LENGTH_SHORT).show();
  7.                     }
  8.                     if (meuProgresoDialog!=null){
  9.                         meuProgresoDialog.dismiss();
  10.                     }
  11.         }


  • Por se acaso se cancela a tarefa tamén debemos pechar o diálogo:
  1.         @Override
  2.         protected void onCancelled() {
  3.            Toast.makeText(ActivityAsysntask.this, "Tarefa cancelada!",
  4.                 Toast.LENGTH_SHORT).show();
  5.        
  6.            if (meuProgresoDialog!=null){
  7.                   meuProgresoDialog.dismiss();
  8.            }
  9.         }


Existen outros métodos que podemos usar:

Por exemplo, no noso caso sabemos cando remata a tarefa que será cando o contador chegue a 10. Podemos modificar o diálogo indicándoo:

  1. meuProgresoDialog.setCancelable(false);
  2. meuProgresoDialog.setIndeterminate(false);
  3. meuProgresoDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
  4. meuProgresoDialog.setMax(TEMPO_FINAL);


Neste caso teremos que modificar a barra de progreso no método onProgressUpdate:

  1.                 @Override
  2.                 protected void onProgressUpdate(Integer... values) {
  3.                     int progreso = values[0].intValue();
  4.          
  5.                     meuProgresoDialog.setProgress(progreso);
  6.                 }

Obtendo este resultado:

PDM Avanzada Threads 5.jpg


Creamos a activity

  • Nome do proxecto: UD7_03_AsyncTask
  • Nome da clase: UD7_03_AsyncTask.java
  • O código do layout é o mesmo que no exercicio anterior.

Código da clase UD7_03_AsyncTask
Obxectivo: Amosar o uso dun ProgressDialog xunto cun AsyncTask

  1. import android.app.Activity;
  2. import android.app.ProgressDialog;
  3. import android.os.AsyncTask;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.view.View.OnClickListener;
  7. import android.widget.Button;
  8. import android.widget.Toast;
  9.  
  10. public class UD7_03_AsyncTask extends Activity {
  11.  
  12.         private static final int TEMPO_FINAL = 10;
  13.         private MiñaTarefa miñaTarefa;
  14.  
  15.         private ProgressDialog meuProgresoDialog;
  16.        
  17.  
  18.         private class MiñaTarefa extends AsyncTask<Void, Integer, Boolean> {
  19.  
  20.         @Override
  21.         protected void onPreExecute() {
  22.  
  23.  
  24.                 meuProgresoDialog.setTitle("TAREFA");
  25.                 meuProgresoDialog.setMessage("ACTUALIZANDO");
  26.                 meuProgresoDialog.setCancelable(false);
  27.                 meuProgresoDialog.setIndeterminate(false);
  28.                 meuProgresoDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
  29.                 meuProgresoDialog.setMax(TEMPO_FINAL);
  30.                 meuProgresoDialog.show();
  31.                
  32.         }
  33.  
  34.                 @Override
  35.                 protected Boolean doInBackground(Void... params) {
  36.                         for (int i = 1; i <= TEMPO_FINAL; i++) {
  37.                                 try {
  38.                                         Thread.sleep(1000);
  39.                                 } catch (InterruptedException e) {
  40.                                         // TODO Auto-generated catch block
  41.                                         e.printStackTrace();
  42.                                 }
  43.                                 publishProgress(i);
  44.  
  45.                                 if (isCancelled())
  46.                                         break;
  47.                         }
  48.                         return true;
  49.                 }
  50.  
  51.                 @Override
  52.                 protected void onProgressUpdate(Integer... values) {
  53.                         int progreso = values[0].intValue();
  54.             meuProgresoDialog.setProgress(progreso);
  55.         }
  56.  
  57.  
  58.                 @Override
  59.                 protected void onPostExecute(Boolean result) {
  60.                         if (result) {
  61.                                 Toast.makeText(getApplicationContext(), "Tarefa finalizada!",
  62.                                                 Toast.LENGTH_SHORT).show();
  63.                         }
  64.             if (meuProgresoDialog!=null){
  65.                 meuProgresoDialog.dismiss();
  66.             }
  67.                 }
  68.  
  69.                 @Override
  70.                 protected void onCancelled() {
  71.                         Toast.makeText(getApplicationContext(), "Tarefa cancelada!",
  72.                                         Toast.LENGTH_SHORT).show();
  73.  
  74.                         if (meuProgresoDialog!=null){
  75.                           meuProgresoDialog.dismiss();
  76.                 }
  77.                 }
  78.         };
  79.  
  80.         private void xestionarEventos(){
  81.                
  82.                 Button btnEmpezar = (Button)findViewById(R.id.UD7_02_btnEmpezarAsync);
  83.                 btnEmpezar.setOnClickListener(new OnClickListener() {
  84.                        
  85.                         @Override
  86.                         public void onClick(View v) {
  87.                                 // TODO Auto-generated method stub
  88.                        
  89.                                 if ((miñaTarefa==null) || (miñaTarefa.getStatus()==AsyncTask.Status.FINISHED)){
  90.                                         miñaTarefa = new MiñaTarefa();
  91.                                         miñaTarefa.execute();
  92.                                 }
  93.                                 else {
  94.                                         Toast.makeText(getApplicationContext(), "A tarefa non acabou!!!",
  95.                                                         Toast.LENGTH_SHORT).show();
  96.                                 }
  97.                         }
  98.                 });
  99.                
  100.                 Button btnCancelar = (Button)findViewById(R.id.UD7_02_btnCancelarAsync);
  101.                 btnCancelar.setOnClickListener(new OnClickListener() {
  102.  
  103.                         @Override
  104.                         public void onClick(View v) {
  105.                                 // TODO Auto-generated method stub
  106.                                 if (miñaTarefa.getStatus()==AsyncTask.Status.RUNNING){
  107.                                         miñaTarefa.cancel(true);
  108.                                 }
  109.                         }
  110.                        
  111.                 });
  112.  
  113.                
  114.         }
  115.        
  116.         @Override
  117.         protected void onCreate(Bundle savedInstanceState) {
  118.                 super.onCreate(savedInstanceState);
  119.                 setContentView(R.layout.activity_ud7_03__async_task);
  120.  
  121.                 meuProgresoDialog = new ProgressDialog(this);
  122.                 xestionarEventos();
  123.  
  124.         }
  125. }





-- Ángel D. Fernández González e Carlos Carrión Álvarez -- (2014).