Android,
A Complete Course, From
Basics to Enterprise Edition
Android, A Complete Course, From Basics to Enterprise
Edition
Threads, AsyncTask et Handler.
Cet
article est extraie du livre « Android, A Complete Course »,
disponible sur Android2ee.com.
Les
exemples ou les programmes présents dans cet ouvrage sont fournis pour
illustrer les descriptions théoriques. Ce code est libre de toute utilisation
mais n'est pas distribuable.
La
distribution du code est reservée au site :
La
propriété intellectuelle du code appartient à :
L’utilisation
de ces codes est sous votre unique responsabilité et personne d’autre que vous
ne pourra être tenu responsable des préjudices ou dommages de quelques natures
que ce soit pouvant résulter de son utilisation.
Tous
les noms de produits ou marques cités dans cet ouvrage sont des marques déposés
par leurs propriétaires respectifs.
Publié par http://android2ee.com
Titre Original : Android, A Complete Course, From
Basics to Enterprise Edition. Édition Française.
ISBN : 979-10-90388-00-0
Copyright © 2011 by Mathias Séguy
Aucune
représentation ou reproduction, même partielle, autre que celles prévues à
l’article L. 122-5 2° et 3° a) du code de la propriété intellectuelle ne peut
être faite sans l’autorisation expresse de Mathias Seguy ou, le cas échéant,
sans le respect des modalités prévues à l’article L. 122-10 dudit code
Si une activité réagit en plus de 5 secondes, elle sera tuée par l’ActivityManager qui la considèrera comme morte. Les IHMs de l’activité sont dans une Thread qui lui est propre (comme en swing). Plusieurs façons d’interagir entre les threads en arrière plan et la thread d’IHM sont possibles.
Mais surtout garder toujours en tête qu’aucun traitement ne doit être effectué dans cette thread !
L’Handler est associé à l’activité qui le déclare et travaille au sein de la thread d’IHM. Ce qui signifie que tout traitement effectué par le Handler gèle l’IHM le temps qu’il soit effectué. Il faut donc considérer le Handler comme celui qui met à jour l’IHM, la thread qui appelle le Handler a à charge le traitement. Le Handler ne doit que mettre à jour l’IHM, tout autre comportement est une erreur de conception.
Une thread communique avec un Handler au moyen de message (ou de runnable mais ce ne sera pas ici expliqué). Pour cela :
· La thread récupère l’objet Message du pool du Handler par handler.obtainedMessage. Cette méthode peut être surchargée de manière à envoyer plus d’informations au Handler (en lui passant d’autres paramètres).
· La thread envoie le message au Handler en utilisant l’une des méthodes suivantes :
o sendMessage (envoie le message et le place à la fin de la queue)
o sendMessageAtFrontOfQueue (envoie le message et le place au début de la queue)
o sendMessageAtTime (envoie le message au moment donné en paramètre et le place à la fin de la queue)
o sendMessageDelayed (envoie le message après un temps d’attente passé en paramètre et le place à la fin de la queue)
· Le Handler doit surcharger sa méthode handleMessage pour répondre aux messages qui lui sont envoyés.
Thread
I.H.M Autre
Thread Activity sendMessage() Handler handleMessage() Thread sendMessage() SendMessage
Exemple :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ProgressBar android:id="@+id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
/**
* @author
mSeguy
* @goals
This class aims to show how to use handler and thread
*/
public class
HandlerTuto extends Activity {
/** * The progress bar to update */
ProgressBar bar;
/** * The progress bar increment stored in the
bundle of the handler's message */
private final String PROGRESS_BAR_INCREMENT="ProgreesBarIncrementId";
/** * The handler that manage the
communication from external threads to Gui's thread */
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
int progress=msg.getData().getInt(PROGRESS_BAR_INCREMENT);
//
here, just increment the progress bar
bar.incrementProgressBy(progress);
//
You can do quick stuff that update the Gui
}
};
/** * An atomic boolean to manage the external
thread's destruction */
AtomicBoolean isRunning = new
AtomicBoolean(false);
/** * An atomic boolean to manage the external
thread's pausing */
AtomicBoolean isPausing = new
AtomicBoolean(false);
//
Create the activity
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
//
Define the progress bar
bar = (ProgressBar) findViewById(R.id.progress);
bar.setMax(210);
}
/**
Called method when launching the activity */
public void onStart() {
super.onStart();
//
initialize the progress bar
bar.setProgress(0);
//
Thread definition, it could have been done in an external classes
Thread background = new Thread(new Runnable() {
/**
* The bundle
exchanged within the message between the tread and the handler
*/
Bundle messageBundle=new
Bundle();
/**
* The message
exchanged between this thread and the handler
*/
Message myMessage;
//
Overriden Run method
public void run() {
try {
// If isRunning is false, the run method will end
for (int i = 0; i < 20
&& isRunning.get();
i++) {
// while on Pause but still alive
while (isPausing.get()
&& (isRunning.get()))
{
// Make a pause
Thread.sleep(1000);
}
// Sleep one second
Thread.sleep(1000);
// Send the message to the handler (the
handler.obtainMessage is more
// efficient that creating a message from scratch)
//create a message, the best way is to use that
method:
myMessage=handler.obtainMessage();
//then, if you want add data to your message (you
could use arg0 and arg1 too)
messageBundle.putInt(PROGRESS_BAR_INCREMENT,
i);
myMessage.setData(messageBundle);
//then send the message
handler.sendMessage(myMessage);
}
} catch
(Throwable t) {
// just end the background thread
}
}
});
//Initialize
the threadSafe booleans
isRunning.set(true);
isPausing.set(false);
//And
launch the thread
background.start();
}
/************************************************************************************/
/** Managing life cycle *******************************************************************/ /**************************************************************************************/
//Method
called when activity stop
public void onStop() {
super.onStop();
//Update the boolean to kill the thread
isRunning.set(false);
}
/*
(non-Javadoc)
* @see
android.app.Activity#onPause()
*/
@Override
protected void onPause() {
super.onPause();
//Update
the boolean to pause the thread
isPausing.set(true);
}
/* (non-Javadoc)
*
@see android.app.Activity#onResume()
*/
@Override
protected void onResume() {
super.onResume();
//Update
the boolean to resume the thread
isPausing.set(false);
}
}
La classe Activity possède la méthode runOnUIThread pour lancer un traitement dans la thread de l’IHM.
Depuis la version 1.5 d’Android, l’AsyncTask permet une nouvelle façon d’effectuer des tâches en arrière plan.
Pour cela :
· Créer une sous-classe d’AsyncTask (elle peut être interne et privée à l’activité).
· Redéfinir une ou plusieurs de ces méthodes pour spécifier son travail.
· La lancer au moyen de sa méthode execute
La dérivation d’une classe AsyncTask n’est pas triviale, elle est générique et à paramètres variables. Pour la généricité elle attend 3 paramètres :
· Le type de l’information qui est nécessaire au traitement (dans l’exemple URL)
· Le type de l’information qui est passé à sa tache pour indiquer sa progression (dans l’exemple : Integer)
· Le type de l’information passé au code lorsque la tâche est finie (dans l’exemple Long).
Un exemple :
private class
DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded "
+ result + " bytes");
}
}
Les étapes d’AsynchTask :
doInBackground est la méthode qui s’exécute dans une autre thread. Elle reçoit un tableau d’objets lui permettant ainsi d’effectuer un traitement en série sur ces objets. Seule cette méthode est exécutée dans une thread à part, les autres méthodes s’exécutent dans la thread de l’IHM.
onPreExecute est appelée par la thread de l’IHM avant l’appel à doInBackground, elle permet de pré-initialiser les éléments de l’IHM.
onPostExecute est appelée lorsque la méthode doInBackground est terminée.
onProgressUpdate est appelée par la méthode publishProgress
à l’intérieur de la méthode doInBackground.
Other
Thread Thread
I.H.M. Activity MyAsyncTask extends AsyncTask doInBackground() onProgressUpdate() OnPostExecute() update Usual
communication update
Exemple :
/**
* @author
mathias seguy
* @goals
This class aims to show a simple usage of the AsyncTask
*/
public class
AsyncTuto extends Activity {
/** * The progress bar to update */
ProgressBar bar;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//
Define the progress bar
bar = (ProgressBar) findViewById(R.id.progress);
bar.setMax(210);
//
Launch the asynchTask
new MyAsyncTask().execute();
}
/**
* This class aims to show a
simple derivation of the AsyncTask
*/
class MyAsyncTask extends AsyncTask<Void, Integer, String> {
/** * A simple counter */
Integer count = 0;
//
override of the method doInBackground (the one which is running in a separate
thread)
@Override
protected String doInBackground(Void... unused) {
try {
while (count < 20) {
//increment the counter
count++;
// Make a pause
Thread.sleep(1000);
//talk to the onProgressUpdate method
publishProgress(count);
}
} catch (InterruptedException t) {
// just end the background thread
return ("The sleep operation failed");
}
return ("return
object when task is finished");
}
//
override of the onProgressUpdate method (runs in the GUI thread)
@Override
protected void onProgressUpdate(Integer... diff) {
bar.incrementProgressBy(diff[0]);
}
//
override of the onPostExecute method (runs in the GUI thread)
@Override
protected void onPostExecute(String message) {
Toast.makeText(getApplicationContext(),
message,
Toast.LENGTH_SHORT).show();
}
}
}
Ces deux méthodes ont pour but
d’effectuer des traitements liés aux IHMs dans des threads indépendantes de
celle de l’IHM. La question est de savoir laquelle choisir dans quelle
circonstance.
L’idée est que si vous faites un
traitement lourd, spécifique, qui n’a besoin que de donner son avancement et sa
fin (typiquement charger des données), le mieux est l’utilisation de
l’AsyncTask. A contrario, si vous avez besoin d’établir une communication avec
l’IHM, il est plus pertinent d’utiliser un Handler (l’exemple de la communication
Bluetooth entre deux appareils utilise ce pattern).