sabato 14 aprile 2012

AsynchTask in Background e ProgressDialog su Android

Quando si effettua una chiamata dal terminale Android a un server remoto è possibile che risposta arrivi dopo qualche secondo per i motivi più disparati. In questi casi si è soliti parlare di "Long Task". Occorre informare l'utente che dovrà attendere la fine dell'operazione in corso. La prima cosa che si cercherà di fare è quella di mostrare una finestra di dialogo con un messaggio ed eventualmente un'immagine animata o una progress bar che indichi all'utente ciò che sta succedendo. 





Dato che durante l'esecuzione di un Long Task la UI (user interface) risulta, per così dire, congelata in attesa che l'operazione finisca, non sarà possibile mostrare la progress bar o l'immagine animata che faccia intuire all'utente che vi è in corso un'operazione. 
Per ovviare a tale inconveniente occorre eseguire il Long Task all'interno di un Thread. In questo modo la UI risulterà indipendente dal Long Task in esecuzione all'interno del Thread, e sarà pertanto possibile mostrare messaggi e progress bar. Tuttavia la gestione per mostrare e rimuovere il messaggio con la progress bar, nonchè la notifica dell'esito dell'operazione richiede un lavoro di una certa complessità, in quanto si tratta di mettere in comunicazione due Thread che viaggiano indipendentemente l'uno dall'altro. Tra le API che Android mette a disposizione vi è  AsyncTask, al quale si possono passare parametri di input, gestisce il progress dell'operazione in corso ed è un grado di effettuare una callback al Thread chiamante per notificare la risposta del Long Task.
In questo post illustrerò con un semplice esempio come si può utilizzare AsyncTask. 
Per prima cosa occorre creare un progetto Android, che chiameremo LongTask, col seguente layout:



che si ottiene con il seguente main.xml:

    
    
    






L'activity principale è la classe LongTaskActivity, mentre il servizio la classe LongService, i cui sorgenti sono riportati di seguito.


file: LongTaskActivity.java:
package it.rino;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class LongTaskActivity extends Activity {
 
 public ProgressDialog pleaseWaitDialog = null;
 
 public void openPleaseWaitDialog() {
  if(pleaseWaitDialog == null) {
   pleaseWaitDialog = new ProgressDialog(LongTaskActivity.this);
   pleaseWaitDialog.setMessage("Please wait...");
   pleaseWaitDialog.setIndeterminate(true);
   pleaseWaitDialog.setCancelable(true);
   pleaseWaitDialog.show();
  }
 }
 
 public void closePleaseWaitDialog(){
  if(pleaseWaitDialog != null) {
   if(pleaseWaitDialog.isShowing())
    pleaseWaitDialog.hide();
   pleaseWaitDialog.dismiss(); 
   pleaseWaitDialog = null;
  }
 }
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button buttonReverseText = (Button)findViewById(R.id.buttonReverseText);
        buttonReverseText.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View arg0) {
    EditText editTextInput = (EditText)findViewById(R.id.editTextInput);  
    String input = editTextInput.getText().toString();
    (new LongService(LongTaskActivity.this, "handleLongServiceResponse")).execute(input);    
   }
  });
    }
    
    public void handleLongServiceResponse(String output) { 
     EditText editTextInput = (EditText)findViewById(R.id.editTextInput);
     editTextInput.setText(output);
    }
}


file: LongService.java
package it.rino;

import java.lang.reflect.Method;

import android.os.AsyncTask;

          // Params, Progress, Result
public class LongService extends AsyncTask {
 private LongTaskActivity activity;
 private String callbackMethod;
 
 @Override
 protected void onPreExecute() {
  super.onPreExecute();
  activity.openPleaseWaitDialog();
 }
 
 public LongService(LongTaskActivity activity, String callbackMethod) {
  this.activity = activity; 
  this.callbackMethod = callbackMethod;
 } 
 
 @Override
 protected String doInBackground(String... input) {
  String in = input[0];
  try {
   Thread.sleep(5000);
  } catch (InterruptedException e) {
  }
  String out = "";
  int size = in.length();
  for (int i = 0 ; i < size; i++) {
   out += in.charAt(size-1-i);
  }
  return out;
 }

 @Override
 protected void onProgressUpdate(Integer... values) {
  super.onProgressUpdate(values);
 }
 
 @Override
 protected void onPostExecute(String result) {
  super.onPostExecute(result);
  activity.closePleaseWaitDialog();
  
  // callback al metodo che gestisce la risposta del servizio
  try {
   Class classe = activity.getClass(); 
   Class[] argTypes = new Class[] { result.getClass() };
   Method metodo = classe.getDeclaredMethod(callbackMethod, argTypes);
   metodo.invoke(activity, result);
  } catch (Exception e) {
   e.printStackTrace();
  }

 }
 
 @Override
 protected void onCancelled() {
  super.onCancelled();
  activity.closePleaseWaitDialog();
 }
}



Nell'Activity principale sull'evento onClick() si esegue il Long Task passando come Stringa di input, al metodo execute, il contenuto dell'InputText. Viene anche passato il nome del metodo che dovrà essere chiamato dalla callback per restituire la risposta all'activity chiamante. Tale metodo dovrà essere pubblico.


(new LongService(LongTaskActivity.this, "handleLongServiceResponse")).execute(input);


Il metodo pubblico handleLongServiceResponse, come detto sopra gestisce la risposta del Long Task rimpiazzando il testo iniziale con quello tornato da LongService all'interno dell'InputText.


    public void handleLongServiceResponse(String output) { 
     EditText editTextInput = (EditText)findViewById(R.id.editTextInput);
     editTextInput.setText(output);
    }
All'interno dell'activity principale sono presenti i metodi per far comparire e rimuovere il messaggio utente presente nella ProgressDialog:
 public ProgressDialog pleaseWaitDialog = null;
 
 public void openPleaseWaitDialog() {
  if(pleaseWaitDialog == null) {
   pleaseWaitDialog = new ProgressDialog(LongTaskActivity.this);
   pleaseWaitDialog.setMessage("Please wait...");
   pleaseWaitDialog.setIndeterminate(true);
   pleaseWaitDialog.setCancelable(true);
   pleaseWaitDialog.show();
  }
 }
 
 public void closePleaseWaitDialog(){
  if(pleaseWaitDialog != null) {
   if(pleaseWaitDialog.isShowing())
    pleaseWaitDialog.hide();
   pleaseWaitDialog.dismiss(); 
   pleaseWaitDialog = null;
  }
 }


Vediamo come viene implementato AsyncTask all'interno di LongService.
Per prima cosa occorre che LongService estenda AsyncTask e dichiari parametri di input, la gestione del Progress e il parametro di Result. Questo si ottiene nel modo seguente:


          // Params, Progress, Result
public class LongService extends AsyncTask {


In questo caso si è dichiarato di tipo String l'elenco dei parametri di input, per cui il metodo doInBackground() avrà la seguente forma:


  @Override
 protected String doInBackground(String... input) {
  String in = input[0];
  try {
   Thread.sleep(5000);
  } catch (InterruptedException e) {
  }
  String out = "";
  int size = in.length();
  for (int i = 0 ; i < size; i++) {
   out += in.charAt(size-1-i);
  }
  return out;
 }
Dove il parametro di input utilizzato è il primo presente nell'array, si simula il Long Task con una pausa di 5 secondi, si inverte il testo passato in input e lo si ritorna. A questo punto AsyncTask passa la stringa di risposta al metodo onPostExecute() che si occupa di effettuare la callback al chiamante invocando dinamicamente il metodo dichiarato sull'activity pricipale:


 @Override
 protected void onPostExecute(String result) {
  super.onPostExecute(result);
  activity.closePleaseWaitDialog();
  
  // callback al metodo che gestisce la risposta del servizio
  try {
   Class classe = activity.getClass(); 
   Class[] argTypes = new Class[] { result.getClass() };
   Method metodo = classe.getDeclaredMethod(callbackMethod, argTypes);
   metodo.invoke(activity, result);
  } catch (Exception e) {
   e.printStackTrace();
  }

 }


Come si può notare la prima operazione fatta dal metodo onPostExecute() è quella di chiudere la ProgressDialog che era stata aperta dal metodo onPreExecute() prima di richiamare doInBackground(), come si può verificare di seguito:


 @Override
 protected void onPreExecute() {
  super.onPreExecute();
  activity.openPleaseWaitDialog();
 }


A questo punto non resta che mostrare come si presenta il terminale subito dopo aver premuto il bottone "Reverse Text":




E come si presenta terminata l'esecuzione del Long Task e rimossa la ProgressDialog:




Se si cambia lo stile della progressbar in STYLE_HORIZONTAL è possibile mostrare il progress dell'operazione gestendo l'avanzamento della progressbar all'interno del metodo onProgressUpdate(Integer... values) della classe LongService.
Per fare ciò occorre aggiungere il metodo pleaseWaitDialogUpdate(int value) ed effettuare alcune modifiche al metodo openPleaseEaitDialog() nella classe LongTaskActivity:
 
 public void pleaseWaitDialogUpdate(int value) {
  pleaseWaitDialog.setProgress(value);
 }
 
 public void openPleaseWaitDialog() {
  if(pleaseWaitDialog == null) {
   pleaseWaitDialog = new ProgressDialog(LongTaskActivity.this);
   pleaseWaitDialog.setMessage("Please wait...");
   pleaseWaitDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
   pleaseWaitDialog.setMax(10);  
            pleaseWaitDialog.setCancelable(true);
   pleaseWaitDialog.show();
  }
 }


quindi occorre modificare la gestione dell'avanzamento del task nel metodo doInBackground(String... input)  della classe LongService:


 @Override
 protected String doInBackground(String... input) {
  String in = input[0];
  String out = "";
  int size = in.length();
  for (int i = 0 ; i < size; i++) {
   out += in.charAt(size-1-i);
   publishProgress((int)(10*(double)i/(double)size));
   try {
    Thread.sleep(100);
   } catch (InterruptedException e) {
   }
  }
  return out;
 }

 @Override
 protected void onProgressUpdate(Integer... values) {
  super.onProgressUpdate(values);
  activity.pleaseWaitDialogUpdate(values[0]);
 }


Il metodo publishProgress(Integer... values) viene richiamato durante l'esecuzione del task per notificare l'avanzamento al metodo onProgressUpdate(Integer... values) che, in questo caso, richiama il metodo dell'activity principale che aggiorna la progressbar pleaseWaitDialogUpdate(int value). L'effetto risultante è quello mostrato di seguito:



Nessun commento:

Posta un commento