Skip to content

3 février 2011 | Rédigé par Ju

15

Utiliser une ProgressDialog dans ses applications Android [Tutoriel Android n°22]

Le but de ce post est d’expliquer un peu comment utiliser une ProgressDialog dans son application. Il peut être parfois utile d’afficher durant de longues opérations une barre de progression renseignant l’utilisateur sur l’avancement. Android fourni un moyen via la ProgressDialog.

Cependant, qui dit longue opération dit souvent Thread. Or Android est connu pour bien différencier l’affichage du traitement (Modèle MVC) et seul le Thread propriétaire des objets graphiques (View) ne peut les modifier. En gros, lorsque votre Activity charge le layout, elle est la seule à pouvoir agir dessus.

C’est ce que je vais m’efforcer d’illustrer avec l’exemple ci-dessous.

Prenons l’exemple d’une application ayant a effectuer 2 opérations assez longues (doLongOperation1 et doLongOperation2). Ces opérations sont consécutives ; doLongOperation2 a besoin que doLongOperation1 soit terminée pour démarrer. Elles sont exécutées dans un second Thread qui sera lui, démarré depuis l’Activity.

Au moment où le second Thread est démarré, on obtient quelque chose du genre :

Soit en gros le code suivant:

private void compute() {
  mProgressDialog = ProgressDialog.show(this, "Please wait",
          "Long operation starts...", true);
 
  new Thread((new Runnable() {
      @Override
      public void run() {
          doLongOperation1();
          doLongOperation2();
      }
  })).start();
  // ...
}

Mais durant l’exécution des opérations, on peut imaginer que l’on ait besoin d’informer l’utilisateur sur le type d’opération en cours et donc mettre à jour le message de la ProgressDialog. Pour ce faire on pourrait utiliser la méthode setMessage de ProgressDialog.

Donc grosso-modo quelque chose comme ca :

private void compute() {
  mProgressDialog = ProgressDialog.show(this, "Please wait",
          "Long operation starts...", true);
 
  new Thread((new Runnable() {
      @Override
      public void run() {
          mProgressDialog.setMessage("Doing long operation 1...");
          doLongOperation1();
          mProgressDialog.setMessage("Doing long operation 2...");
          doLongOperation2();
      }
  })).start();
  // ...
}

Mais, on l’a vu plus tôt, seul le Thread propriétaire des objets graphiques ne peut les modifier. L’objet mProgressDialog, créé dans le Thread de l’Activity, ne peut être modifier par un Thread annexe, au risque de lancer une CalledFromWrongThreadException avec le message au combien explicite :

Only the original thread that created a view hierarchy can touch its views.

Pour réaliser notre opération, il faut donc que le second Thread soit capable d’informer le Thread de l’Activity. C’est ce que propose la classe Handler avec le principe des objets Message qu’elle véhicule. En gros un Handler, possède une queue (une file) de messages qu’il dépile un à un. Ces messages sont constitués d’un identifiant (int what) et peuvent contenir des données.
Il suffit de créer un objet de type Handler, et de l’utiliser pour préparer (appel à obtainMessage), envoyer (appel à sendMessage) et recevoir (override de handleMessage) les objet de type Message

Pour notre exemple, on définira 3 types de messages avec les 3 identifiants suivants :

  1. MSG_ERR: une erreur s’est produite, la ProgressDialog disparait et on informe l’utilisateur
  2. MSG_IND: une indication en cours de traitement à besoin d’être donnée à l’utilisateur (via la ProgressDialog)
  3. MSG_CNF: le traitement est terminé, on confirme à l’utilisateur que tout s’est bien passé (la ProgressDialog disparait).

Bref, dans le cas nominal, on obtient donc ce qui suit:

Soit le code suivant:

package com.jde.tutorial;
 
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
 
public class ProgressBarActivity extends Activity {
 
protected ProgressDialog mProgressDialog;
private Context mContext;
private ErrorStatus status;
 
public static final int MSG_ERR = 0;
public static final int MSG_CNF = 1;
public static final int MSG_IND = 2;
 
public static final String TAG = "ProgressBarActivity";
 
enum ErrorStatus {
    NO_ERROR, ERROR_1, ERROR_2
};
 
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    mContext = this;
 
    // registers the OnClickListener to our button
    ((Button) findViewById(R.id.btn))
            .setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    compute();
                }
            });
}
 
private void compute() {
    mProgressDialog = ProgressDialog.show(this, "Please wait",
            "Long operation starts...", true);
 
    // useful code, variables declarations...
    new Thread((new Runnable() {
        @Override
        public void run() {
            Message msg = null;
            String progressBarData = "Doing long operation 1...";
 
            // populates the message
            msg = mHandler.obtainMessage(MSG_IND, (Object) progressBarData);
 
            // sends the message to our handler
            mHandler.sendMessage(msg);
 
            // starts the first long operation
            status = doLongOperation1();
 
            if (ErrorStatus.NO_ERROR != status) {
                Log.e(TAG, "error while parsing the file status:" + status);
 
                // error management, creates an error message
                msg = mHandler.obtainMessage(MSG_ERR,
                        "error while parsing the file status:" + status);
                // sends the message to our handler
                mHandler.sendMessage(msg);
            } else {
                progressBarData = "Doing long operation 2...";
                mProgressDialog.setMessage(progressBarData);
 
                // populates the message
                msg = mHandler.obtainMessage(MSG_IND,
                        (Object) progressBarData);
 
                // sends the message to our handler
                mHandler.sendMessage(msg);
 
                // starts the second long operation
                status = doLongOperation2();
 
                if (ErrorStatus.NO_ERROR != status) {
                    Log.e(TAG, "error while computing the path status:"
                            + status);
                    // error management,creates an error message
                    msg = mHandler.obtainMessage(MSG_ERR,
                            "error while computing the path status:"
                                    + status);
                    // sends the message to our handler
                    mHandler.sendMessage(msg);
                } else {
                    msg = mHandler.obtainMessage(MSG_CNF,
                            "Parsing and computing ended successfully !");
                    // sends the message to our handler
                    mHandler.sendMessage(msg);
                }
            }
        }
    })).start();
    // ...
}
 
/** fake operation for testing purpose */
protected ErrorStatus doLongOperation2() {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
    }
    return ErrorStatus.NO_ERROR;
}
 
/** fake operation for testing purpose */
protected ErrorStatus doLongOperation1() {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
    }
    return ErrorStatus.NO_ERROR;
}
 
final Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {
        String text2display = null;
        switch (msg.what) {
        case MSG_IND:
            if (mProgressDialog.isShowing()) {
                mProgressDialog.setMessage(((String) msg.obj));
            }
            break;
        case MSG_ERR:
            text2display = (String) msg.obj;
            Toast.makeText(mContext, "Error: " + text2display,
                    Toast.LENGTH_LONG).show();
            if (mProgressDialog.isShowing()) {
                mProgressDialog.dismiss();
            }
            break;
        case MSG_CNF:
            text2display = (String) msg.obj;
            Toast.makeText(mContext, "Info: " + text2display,
                    Toast.LENGTH_LONG).show();
            if (mProgressDialog.isShowing()) {
                mProgressDialog.dismiss();
            }
            break;
        default: // should never happen
            break;
        }
    }
};
 
}

C’est à travers la méthode surchargée handleMessage que le message est dépilé. On teste ensuite le type de message (what) et on applique le traitement désiré.
Note: dans la méthode handleMessage, on se trouve à nouveau dans le Thread de l’Activity, c’est donc pour ça que l’opération est possible.

Voila ce que vous devriez avoir :

En espérant que ceci vous aura aidé.

Les sources du projet, c’est ici

Découvrez d'autre articles de la catégorie Tutoriels Android

Encore un peu de lecture :

15 Commentaires Poster un commentaire
  1. Cédric
    3 fév 2011

    Personnelement j’étais passé par une Asynctask pour mettre à jour ma progressDialog

  2. 3 fév 2011

    Oui, comme le dit Cedric, l’AsyncTask (spécifique à Android et non à java) permet de simplifier le code (et la vie !) du développeur en évitant d’utiliser le système de Thread/Handler pour modifier l’interface utilisateur en passant par l’UIThread. :)

    Le code est moins lourd et en plus de ça il y a des petites fonctions pratiques pour observer l’évolution de la tâche qui tourne en background (début, progression, fin).

  3. Ju
    8 fév 2011

    Oui, en effet l’utilisation de l’AsyncTask aurait été plus simple :) . Cependant, il faudra tout de même utiliser le mécanisme de Handler si on veut pouvoir modifier le contenu (Message) de la ProgressDialog durant son affichage.

  4. J0hnD0e
    25 mar 2011

    @Ju : Faux ! On peut aisément se passer d’un handler.

    Je m’explique : On hérite de AsyncTask (disons MyTask). Dans le constructeur de MyTask, on passe un objet Activity.
    Dans la méthode onPreExecute() de MyTask, qui est exécutée par l’UIThread, on crée la ProgressDialog (dialog est un attribut de MyTask et à sa création, on a besoin d’un contexte : l’objet Activity).

    Dans la méthode doInBackground, on met à jour les infos via publishProgress(…).

    Dans onProgressUpdate(…), on met à jour la ProgressDialog.

    Et on peut même ajouter à tout cela la mise en place d’une callback qui est renvoyée dans la méthode onPostExecute(…) en cas de succès et qui permet de renvoyer divers objets crées ou infos récupérées dans le traitement (liste d’images,…)

    Je peux filer un exemple si besoin..

    https://twitter.com/j0hnd0e42

  5. hazem
    9 avr 2011

    bon jour,
    Je debute android, apres terminer mon application il me reste de programmer un progress bar saffiche lors passage d’une activity a une autre le probleme est que le passage d’une activity a une autre est trop long alors je veut faire un progress bar lors de chargement de l’activity pour patienter l’utilisateur
    Merci d’avence

  6. Mc Flurry
    13 avr 2011

    @J0hnD0e
    Salut, je suis interressé par ta solution si elle permet d’alléger le code pourquoi faire autrement ! Tu aurais un exemple sous la main ?
    Merci.

  7. J0hnD0e
    13 avr 2011

    J’ai rédigé un petit pdf pour expliquer : http://db.tt/3vfr2W4

    En espérant que ce soit suffisamment clair.

    Si Axon veut le reprendre et en faire un VRAI tuto mis à disposition sur le site, qu’il ne se gène pas.. :-)

  8. J0hnD0e
    13 avr 2011

    Et voici l’archive du projet : http://db.tt/dLpfBAi

  9. sebastien
    20 avr 2011

    Merci à Axon pour son tuto et à J0hnD0e de nous avoir fait un petit pdf complémentaire! Ce site est vraiment d’une grande utilité. Au plaisir de vous lire.

  10. Mc Flurry
    5 mai 2011

    @ J0hnD0e
    J’ai regardé ton tuto, il est vraiment bon car simple et précis. Bravo !

    @ Axon
    Ton site est vraiment une bonne source d’information, continue !

  11. Florian
    24 août 2011

    Salut,

    Cela fait 8heures que je cherche une réponse à mon problème… J’ai parcouru tout le web sans succès……

    Si tu pouvais m’expliquer comment faire pour :

    Afficher un ProgressDialog le temps que je récupère la position GPS du téléphone (Location manager)…..

    Une fois que j’ai les coordonnées je lance une autre activity avec les coordonnées récupérées….

    Mon gros souci est que dans le Thread je ne peux pas utiliser onLocationChanged et tout autre listener…….

    Merci pour ton aide..!

  12. J0hnD0e
    26 août 2011

    package com.tuto;

    import android.app.Activity;
    import android.app.ProgressDialog;
    import android.content.Context;
    import android.location.Location;
    import android.location.LocationListener;
    import android.location.LocationManager;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;

    public class Gps_projActivity extends Activity implements OnClickListener, LocationListener {
    private ProgressDialog dialog;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    Button b = (Button)findViewById(R.id.button1);
    b.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
    dialog = ProgressDialog.show(this, « GPS », « recup infos »);
    LocationManager objgps = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
    //*************ecouteur ou listener*********************

    objgps.requestLocationUpdates(
    LocationManager.GPS_PROVIDER,
    0,
    0,
    this);
    }

    @Override
    public void onLocationChanged(Location location) {
    dialog.dismiss();
    Log.d(« GPS », »gps lat: »+location.getLatitude());
    }

    @Override
    public void onProviderDisabled(String provider) {
    // TODO Auto-generated method stub

    }

    @Override
    public void onProviderEnabled(String provider) {
    // TODO Auto-generated method stub

    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
    // TODO Auto-generated method stub

    }
    }

    Dans ce genre là ?

  13. darkendorf
    29 sept 2011

    Merci pour ce tuto complet, parfaitement expliqué et qui me fait gagner un temps précieux !!

    un petit détail : tu as laissé trainer un petit ‘mProgressDialog.setMessage()’ dans ta dernière implémentation ^^

  14. fantastic@
    18 jan 2012

    Votre site est un trésor

Trackbacks & Pingbacks

  1. Les tweets qui mentionnent Utiliser une ProgressDialog dans ses applications Android [Tutoriel Android n°22] | Tuto Mobile -- Topsy.com

Une question, une suggestion, une opinion? Partagez ce que vous pensez, laissez un commentaire.

(obligatoire)
(obligatoire)

Note: Votre adresse email ne sera jamais publiée.

Suivez les réponses aux commentaires