Tutoriel Android : tout comprendre sur les Threads, les Handlers, les AsyncTasks et les fuites mémoires

Image non disponible
Android2ee

Cet article explique comment mettre en place des Threads de background de manière appropriée sous Android. Il explique comment effectuer des traitements dans vos applications. Le discours se centre sur les activités ayant à effectuer des traitements, mais ces principes s'appliquent aussi bien aux services.

Pour réagir à ce tutoriel, un espace de dialogue vous est proposé sur le forum : 5 commentaires

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

1. Introduction

Cet article explique comment mettre en place des Threads de background de manière appropriée sous Android. Il explique comment effectuer des traitements dans vos applications. Le discours se centre sur les activités ayant à effectuer des traitements, mais ces principes s'appliquent aussi bien aux services.

La première partie de cet article explique le fonctionnement des Handlers et des AsyncTasks de manière générale. Elle montre comment déclarer une Thread de traitement et la faire communiquer avec votre Thread d'IHM.

La seconde partie se concentre sur la problématique des fuites mémoires qui apparaissent si la Thread n'est pas liée au cycle de vie de l'activité (du service). Et oui, je ne vais considérer les fuites mémoires que dans le cadre de la synchronisation du cycle de vie des Threads et des activités. Cet article ne traite pas des fuites mémoires dans leur généralité.

1-1. Les Threads

Il faut toujours garder en tête quand on fait de la programmation Android que 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). C'est cette Thread qui est chargée de l'interaction avec l'utilisateur. Ainsi pour effectuer un traitement, il faut lancer une autre Thread (dites de background, de traitement ou d'arrière-plan) qui effectue le traitement. Plusieurs façons d'interagir entre les Threads en arrière-plan et la Thread d'IHM sont possibles.

Mais surtout il faut garder toujours en tête qu'aucun traitement ne doit être effectué dans la Thread d'IHM !

Deux objets dédiés à ce pattern sont disponibles nativement sur Android, les Handlers et les AsyncTasks.

2. Les Handlers

Le 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 la charge du 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 messages (de l'objet Message pour être exact). 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 à la Thread (en lui passant d'autres paramètres au moyen d'un Bundle).
  • La Thread envoie le message au Handler en utilisant l'une des méthodes suivantes :
    • sendMessage (envoie le message et le place à la fin de la queue)
    • sendMessageAtFrontOfQueue (envoie le message et le place au début de la queue)
    • sendMessageAtTime (envoie le message au moment donné en paramètre et le place à la fin de la queue)
    • 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. Il a à charge de mettre à jour l'IHM en fonction de ces données.
Handler2.JPG


L'exemple suivant met à jour un composant de type ProgressBar :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
<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>

Le code java :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
/**
 * @author Android2ee
 * @goals Cette classe a pour but de montrer comment utiliser les Handlers et les Threads
 */
public class HandlerTuto extends Activity {
    /**     * La ProgressBar à mettre à jour     */
    ProgressBar bar;
    /**     * La clef dans le bundle pour incrémenter la progressBar     */
    private final String PROGRESS_BAR_INCREMENT="ProgreesBarIncrementId";
    /**     * Le Handler à charge de la communication entre la Thread de background et celle de l'IHM     */
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            int progress=msg.getData().getInt(PROGRESS_BAR_INCREMENT);
            // Incrémenter la ProgressBar, on est bien dans la Thread de l'IHM
            bar.incrementProgressBy(progress);
            // On peut faire toute action qui met à jour l'IHM
            
        }
    };
    /**     * L'AtomicBoolean qui gère la destruction de la Thread de background     */
    AtomicBoolean isRunning = new AtomicBoolean(false);
    /**     * L'AtomicBoolean qui gère la mise en pause de la Thread de background     */
    AtomicBoolean isPausing = new AtomicBoolean(false);

    // Création de l'activité
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.main);
        // Définition de la ProgressBar
        bar = (ProgressBar) findViewById(R.id.progress);
        bar.setMax(210);
    }

    /** Méthode du cycle de vie de l'activité (lancement de celle-ci) */
    public void onStart() {
        super.onStart();
        // initialisation de la ProgressBar
        bar.setProgress(0);
        // Définition de la Thread (peut être effectuée dans une classe externe ou interne)
        Thread background = new Thread(new Runnable() {
            /**
             * Le Bundle qui porte les données du Message et sera transmis au Handler
             */
            Bundle messageBundle=new Bundle();
            /**
             * Le message échangé entre la Thread et le Handler
             */
            Message myMessage;
            // Surcharge de la méthode run
            public void run() {
                try {
                    // Si isRunning est à false, la méthode run doit s'arrêter
                    for (int i = 0; i < 20 && isRunning.get(); i++) {
                        // Si l'activité est en pause mais pas morte
                        while (isPausing.get() && (isRunning.get())) {
                            // Faire une pause ou un truc qui soulage le CPU (dépend du traitement)
                            Thread.sleep(2000);
                        }
                        // Effectuer le traitement, pour l'exemple je dors une seconde
                        Thread.sleep(1000);
                        // Envoyer le message au Handler (la méthode handler.obtainMessage est plus efficace
                        // que créer un message à partir de rien, optimisation du pool de message du Handler)
                        //Instanciation du message (la bonne méthode):
                        myMessage=handler.obtainMessage();    
                        //Ajouter des données à transmettre au Handler via le Bundle
                        messageBundle.putInt(PROGRESS_BAR_INCREMENT, 1);
                       //Ajouter le Bundle au message
                        myMessage.setData(messageBundle);
                        //Envoyer le message
                        handler.sendMessage(myMessage);
                    }
                } catch (Throwable t) {
                    // gérer l'exception et arrêter le traitement
                }
            }
        });
        //Initialisation des AtomicBooleans
        isRunning.set(true);
        isPausing.set(false);
        //Lancement de la Thread
        background.start();
    }
    
 /************************************************************************************/
/** Gestion du cycle de vie *******************************************************************/
 /**************************************************************************************/
    //Méthode appelée quand l'activité s'arrête 
    public void onStop() {
        super.onStop();
        //Mise-à-jour du booléen pour détruire la Thread de background
        isRunning.set(false);
    }

    /* (non-Javadoc)
     * @see android.app.Activity#onPause()
     */
    @Override
    protected void onPause() {
        super.onPause();
        // Mise-à-jour du booléen pour mettre en pause la Thread de background
        isPausing.set(true);
    }

    /* (non-Javadoc)
     * @see android.app.Activity#onResume()
     */
    @Override
    protected void onResume() {
        super.onResume();
        // Mise-à-jour du booléen pour relancer la Thread de background
        isPausing.set(false);
    }

}

Cet exemple utilise des objets de type AtomicBoolean, l'explication est donnée dans le paragraphe traitant des fuites mémoires.

2-1. Suis-je dans la Thread de l'IHM ou pas ?

La classe Activity possède la méthode runOnUIThread pour lancer un traitement dans la Thread de l'IHM.

3. La désynchronisation avec l'AsyncTask

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 trois 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 tâche 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 ).

Une dernière chose d'importance est que la classe AsyncTask (et donc vos classes filles) s'exécute dans deux Threads distinctes ; la Thread d'IHM et la Thread de traitement.


Un exemple (toujours celui qui met à jour un composant de type ProgressBar) :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
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.

AsyncTask.JPG

Exemple :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
/**
 * @author Android2ee
 * @goals Cette classe montre un usage simple du Pattern AsyncTask. Attention, elle génère des fuites mémoires
 *  si elle est utilisée telle quelle.
 */
public class AsyncTuto extends Activity {
    /**    * La ProgressBar à mettre à jour     */
    ProgressBar bar;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // Définition de la ProgressBar
        bar = (ProgressBar) findViewById(R.id.progress);
        bar.setMax(210);
        // Lancement de l'AsynchTask
        new MyAsyncTask().execute();
    }

    /**
     * Cette classe montre une simple dérivation de la classe AsyncTask
     */
    class MyAsyncTask extends AsyncTask<Void, Integer, String> {
        /**     * Un compteur */
        Integer count = 0;

        // Surcharge de la méthode doInBackground (Celle qui s'exécute dans une Thread à part)
        @Override
        protected String doInBackground(Void... unused) {
            try {
                while (count < 20) {
                    //incrémente le compteur
                    count++;
                    // Faire une pause
                    Thread.sleep(1000);
                    //Donne son avancement en appelant onProgressUpdate
                    publishProgress(count);
                }
            } catch (InterruptedException t) {
                // Gérer l'exception et terminer le traitement
                return ("The sleep operation failed");
            }
            return ("return object when task is finished");
        }

        // Surcharge de la méthode onProgressUpdate (s'exécute dans la Thread de l'IHM)
        @Override
        protected void onProgressUpdate(Integer... diff) {
             // Mettre à jour l'IHM
            bar.incrementProgressBy(diff[0]);
        }

        // Surcharge de la méthode onPostExecute (s'exécute dans la Thread de l'IHM)
        @Override
        protected void onPostExecute(String message) {
             // Mettre à jour l'IHM
            Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
        }
    }
}

4. AsyncTask ou Handler

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 quelles circonstances.


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 ( par exemple la communication Bluetooth entre deux appareils utilise ce pattern).


Ainsi, si vous avez un traitement qui correspond à effectuer la même action sur un ensemble d'éléments donnés et connus avant le début du traitement, l'utilisation de l'AsyncTask est préconisée. Sinon, il y a de grandes chances qu'il vous faille utiliser le pattern du Handler.


En conclusion: un traitement par lots s'effectuera avec une AsyncTask et un traitement nécessitant une communication dynamique entre la Thread de traitement et l'IHM utilisera le pattern du Handler.

5. Threads et fuites mémoires

La question qui se pose est « Que se passe-t-il lorsque l'activité suit son cycle de vie et est détruite puis recréée ? Typiquement une rotation de l'écran? Que devient la Thread de traitement ? »

Dans la plupart des cas, le développeur ne fait pas attention et l'ancienne Thread n'est pas détruite, une nouvelle Thread est mise en place. Le traitement peut rester cohérent (ou pas).

La même chose se produit avec les AsynchTask bien que seuls les Handlers soient cités dans la suite de l'article. Par contre, les tutoriels disponibles traitent tous les cas.

Le schéma mémoire qui se met en place est alors le suivant :

HandlerMemoryLeak2.JPG

On s'aperçoit que la destruction de l'activité ne détruit pas la Thread, celle-ci reste « en vie ». Pire, elle continue de pointer vers le Handler et l'activité, qui bien que considérés détruits par Android ne le sont pas (vous ne pouvez pas y accéder, ni afficher l'activité.). Le GarbageCollector détecte que ces ressources sont utilisées (en effet la Thread pointe sur ces espaces mémoires) et ne les collecte pas.

Pire encore, quand l'activité est recréée (typiquement un changement d'orientation), elle recrée aussi une nouvelle Thread et la lance.

On se retrouve au final (suite à un changement d'orientation par exemple) avec :

  • deux threads qui effectuent le même traitement mais n'en sont pas au même point,
  • Une Activity et un Handler actifs,
  • Une activité et un Handler fantômes, ni vraiment morts ni vraiment vivants.

Alors quelle est la parade ?

Avant de trouver la solution, il faut tout d'abord se poser les questions suivantes :

  • La Thread doit-elle être détruite lorsque l'activité se termine ?
  • La Thread doit-elle se terminer lorsque l'activité est détruite puis recréée ?
  • L'utilisateur a-t-il son mot à dire ?

Ces questions sont fondamentales pour le Design Pattern que vous allez implémenter pour votre traitement.

Clairement si la fin de l'activité ne termine pas la Thread, il est fort à parier que vous auriez du mettre un service entre l'activité et la Thread. Celui-ci ayant à charge le traitement est lancé par l'activité et continue indépendamment du cycle de vie de l'activité.

Dans les cas où le cycle de vie de votre activité doit être lié à celui de votre Thread, deux cas s'offrent à vous : l'utilisation de deux booléens « synchronized » appelés AtomicBoolean ou l'utilisation en plus de ces deux éléments de la méthode onRetainNonConfigurationInstance.

Deux projets Eclipse démontrant cette fuite mémoire sont disponibles ici :

L'un démontre cette fuite avec un Handler, l'autre la démontre avec un AsyncTask.

5-1. Utilisation des AtomicBooleans pour lier le cycle de vie de la Thread à celui de l'activité.

Les AtomicBooleans sont des objets de type Boolean qui sont thread safe. En effet les méthodes d'accès aux valeurs du booléens sont « synchronized ». Ainsi on accède à la valeur de ce type d'objet par appel à la méthode get() et on change sa valeur en utilisant la méthode set.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
public class HandlerTutoActivity extends Activity {
/*****************************************************************************************/
/** Gérer le Handler et la Thread *************************************************/
/*****************************************************************************************/
/** * Le Handler */
private Handler handler;
/** * Le AtomicBoolean pour lancer et stopper la Thread */
private AtomicBoolean isThreadRunnning = new AtomicBoolean();
/** * Le AtomicBoolean pour mettre en pause et relancer la Thread */
private AtomicBoolean isThreadPausing = new AtomicBoolean();
/** * La Thread */
Thread backgroundThread;

/****************************************************************************************/
/** Gérer l'activité ********************************************************************/
/***************************************************************************************/

/** Appelé à la création de l'activité. */
@Override
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	// Instancier la ProgressBar
	progressBar = (ProgressBar) findViewById(R.id.progressbar);
	progressBar.setMax(100);
	// définition du Handler
	handler = new Handler() {
		/* * (non-Javadoc) * 
		 * @see android.os.Handler#handleMessage(android.os.Message) */
		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			Log.d(TAG, "handle message called ");
			// s'assurer que la Thread est bien en mode Run avant de faire quelque chose
			if (isThreadRunnning.get()) {
				Log.w(TAG, "handle message calls updateProgress ");
				updateProgress();
			}
		}
	};
	// Définition de la Thread et liaison avec le Handler
	backgroundThread = new Thread(new Runnable() {
		/**
		 * Le message échangé entre la Thread et le Handler
		 */
		Message myMessage;

		/** (non-Javadoc) * * @see java.lang.Runnable#run()*/
		public void run() {
			try {
				while (isThreadRunnning.get()) {
					if (isThreadPausing.get()) {
						// Faire une pause cohérente avec le traitement, soulager le CPU
						Thread.sleep(2000);
					} else {
						// Faire un traitement 
						Thread.sleep(100);
						// Obtenir le Message
						myMessage = handler.obtainMessage();
						// envoyer le message au Hanlder
						handler.sendMessage(myMessage);
					}
				}
			} catch (Throwable t) {
				// Termine la Thread
			}
		}
	});
	// Initialiser le booléen isThreadRunning
	isThreadRunnning.set(true);
	//Lancer la Thread
	backgroundThread.start();
}

/* * (non-Javadoc) * * @see android.app.Activity#onDestroy() */
protected void onDestroy() {
	// Tuer la Thread
	isThreadRunnning.set(false);
	super.onDestroy();
}

/* * (non-Javadoc) *  * @see android.app.Activity#onPause() */
protected void onPause() {
    //Mettre la Thread en pause
    isThreadPausing.set(true);
    super.onPause();
}

/* * (non-Javadoc) *  * @see android.app.Activity#onResume() */
protected void onResume() {
	// Relancer la Thread
	isThreadPausing.set(false);
	super.onResume();
}

Ce pattern (qui est le même que celui présenté en début d'article) permet d'obtenir le schéma mémoire suivant lors du passage de l'activité par les méthodes onDestroy, onCreate :

HanlderMemoryWithAtomic2.JPG

Ce schéma mémoire a le mérite d'être sans fuite mémoire, a contrario, il réinitialise le traitement de la Thread (en fait il en créé un nouvelle) à chaque redémarrage de l'activité. Il pourrait être utile de sauvegarder l'état d'avancement de la Thread dans la méthode onDestroy puis de restaurer cet état dans la méthode onCreate.

Cette solution ne permet pas de pallier au problème du changement de configuration de l'appareil qui reconstruit l'activité immédiatement après sa destruction. Pour cela il faut utiliser la méthode onRetainNonConfigurationInstance.

5-2. Utilisation de la méthode onRetainNonConfigurationInstance

Pour garder un caractère compréhensible, ce paragraphe explique l'utilisation de cette méthode. Mais si vous utilisez le code décrit ci-dessous directement vous ne gérez plus la fuite mémoire générée par la destruction de l'activité. Il faut inclure dans la solution de ce paragraphe, l'utilisation des AtomicBoolean du paragraphe précédent qui permettent de n'avoir jamais de fuite mémoire.

Mais tout d'abord « Qu'es aquò la méthode OnRetainNonConfigurationInstance ? » comme dirait mon grand-père.

De manière générale les méthodes Object getLastNonConfigurationInstance() et Object onRetainNonConfigurationInstance() permettent de passer un objet entre deux instances d'une activité lorsque celle-ci est détruite pour être immédiatement recréée. Il faut donc toujours tester la valeur de retour (null ou pas) et être cohérent avec ce retour.

On renvoie l'objet (ou la liste d'objets) avec la méthode onRetainNonConfiguartionInstance et on le récupère avec getLastNonConfigurationInstance.

Nous utiliserons ces méthodes pour passer en paramètre la Thread ; ce qui donne :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
/** Appelée à la première creation de l'activité */ 
@Override 
public void  onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //Faire quelque chose 
    backgroundThread = (Thread) getLastNonConfigurationInstance();
    //Tester si la Thread est nulle ou pas et adapter le traitement en fonction
}
@Override 
public Object onRetainNonConfigurationInstance() {
    //Renvoyer la Thread 
    return backgroundThread;
}

Ce DesignPattern permet d'obtenir le schéma mémoire suivant :

HanlderMemoryWithOnRetain2.JPG

Remarquez que ce schéma mémoire a le bon goût lors de la destruction puis recréation (immédiate) de l'activité de conserver la même Thread de Traitement et d'éviter les fuites mémoires. Il est à noter que sans les AtomicBooleans lorsque l'activité s'arrête, vous générez une fuite mémoire, il faut donc impérativement mixer les deux approches.

5. Conclusion

Suite à cet article vous êtes censés être capable d'effectuer des traitements dans vos applications qui ne gèlent pas vos IHMs et qui ne génèrent pas de fuite mémoire. J'espère que vous vous en souviendrez dans vos projets ou quand vous expliquerez à vos stagiaires comment coder proprement. En attendant, bonne continuation à vous.

6. Remerciements

J'adresse ici tous mes remerciements à Feanorin pour son implication, son aide et sa sympathie et à Erielle pour l'excellence de ses corrections orthographiques.

Je remercie les correcteurs techniques MrDuChnok, David55 et Feanorin pour la pertinence de leurs remarques.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2016 Mathias Seguy. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.