I. Présentation▲
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) peut les modifier. En gros, lorsque votre Activity charge le layout, elle est la seule à pouvoir agir dessus. C'est ce que nous verrons dans la partie Fonctionnement.
II. Fonctionnement▲
II-A. Cas Simple▲
Prenons l'exemple d'une application ayant à effectuer deux opérations assez longues consécutives (doLongOperation1 et doLongOperation2). Elles sont exécutées dans un nouveau Thread, en plus du Thread de l'Activity, qui sera lui, démarré depuis l'Activity.
Au moment où ce second Thread est démarré, on obtient quelque chose du genre :
Ce qui correspond à un code comme celui-ci :
private
void
compute
(
) {
mProgressDialog =
ProgressDialog.show
(
this
, "Please wait"
,
"Long operation starts..."
, true
);
new
Thread
((
new
Runnable
(
) {
@Override
public
void
run
(
) {
doLongOperation1
(
);
doLongOperation2
(
);
}
}
)).start
(
);
// ...
}
II-B. Afficher un Message▲
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.
Ce qui correspond à un code comme celui-ci :
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, comme nous l'avons indiqué précédemment, seul le Thread propriétaire des objets graphiques peut les modifier. L'objet mProgressDialog, créé dans le Thread de l'Activity, ne peut être modifié 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 objets de type Message.
Pour notre exemple, on définira trois types de messages avec les trois 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 a 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 :
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 doLongOperation1
(
) {
try
{
Thread.sleep
(
3000
);
}
catch
(
InterruptedException e) {
}
return
ErrorStatus.NO_ERROR;
}
/** fake operation for testing purpose */
protected
ErrorStatus doLongOperation2
(
) {
try
{
Thread.sleep
(
5000
);
}
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.
Voilà ce que vous devriez avoir :
III. Remerciements▲
Je tiens à remercier tout particulièrement Feanorinhttp://www.developpez.net/forums/u35388/feanorin/ qui a mis ce tutoriel au format Developpez.com.
Merci également à JF Jousseaumehttp://www.developpez.net/forums/u195853/sepia/ d'avoir pris le temps de le relire et de le corriger.