Tutoriel pour gérer les processus d'arrière-plan sous Android avec des gestionnaires, des tâches asynchrones et des chargeurs

Fils d'exécution Android, gestionnaires AsyncTask

Lars Vogel

Traducteur

Mishulyna

Ce tutoriel décrit l'utilisation des fils d'exécution(1), des gestionnaires(2) et des tâches asynchrones(3) dans votre application. Il explique également comment gérer le cycle de vie de l'application et les fils d'exécution. Il est basé sur Eclipse 4.2, Java 1.6 et Android 4.2.

Nous remercions Lars Vogel qui nous a aimablement autorisé à traduire et héberger cet article.

N'hésitez pas à commenter cet article ! Commentez

- Titre : Tutoriel pour gérer les processus d'arrière-plan sous Android avec des gestionnaires, des tâches asynchrones et des chargeurs

- Auteur : Lars Vogel

- Traducteur : Mishulyna

- Parution : 12 juin 2013

- Public visé :

- Licence : Le contenu de cet article est rédigé par Lars Vogel et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Sommaire

- 1. Le fil d'exécution de l'interface utilisateur sous Android

- 1-1. Le fil d'exécution principal

- 1-2. Pourquoi utiliser l'accès concurrentiel ?

- 2. Les notions de base Android

- 3. L'utilisation des fils d'exécution Java sous Android

- 3-1. Utiliser le threading Java sous Android

- 3-2. Les inconvénients de l'utilisation des fils d'exécution Java sous Android

- 4. Les éléments de concurrence dans Android

- 5. La classe Handler

- 5-1. Le but de la classe Handler

- 5-2. Création et réutilisation des instances de gestionnaires

- 5-3. Exemple

- 6. La classe AsynkTask

- 6-1. Le but de la classe AsyncTask

- 6-2. L'utilisation de la classe AsyncTask

- 6-3. L'exécution de plusieurs AsyncTask en parallèle

- 6-4. Les inconvénients de l'utilisation des tâches asynchrones

- 6-5. Tâche asynchrone : exemple

- 7. Les processus d'arrière-plan et la gestion des cycles de vie

- 7-1. Conserver l'état pendant les changements de configuration

- 7-2. Utiliser l'objet application pour stocker des objets

- 8. Fragments et processus d'arrière-plan

- 8-1. Conserver une instance lors des changements de configuration

- 8-2. Fragments sans entête

- 9. En savoir plus sur les fragments

- 10. Chargeur

- 10-1. Le but de la classe Loader

- 10-2. Implémenter un Chargeur

- 10-3. Base de données SQLite et CursorLoader

- 11. Exercice : Chargeur personnalisé pour préférences

- 11-1. Implémentation

- 11-2. Test

- 12. L'utilisation des services

- 13. En apprendre plus sur les services

- 14. Exercice : le cycle de vie de l'activité et les fils d'exécution

- 15. Optimisations de mémoire

- 15-1. L'utilisation des caches

- 15-2. L'utilisation des structures de mémoire efficaces

- 15-3. Nettoyer l'utilisation mémoire

- 16. Le mode strict

- 17. Retour d'information par boîte de dialogue

- 18. Liens et littérature

- 18-1. Code source

- 18-2. Ressources concernant l'accès concurrentiel

- 19. Remerciements

1 - Le fil d'exécution de l'interface utilisateur sous Android

1-1 - Le fil d'exécution principal

Android modifie l'interface utilisateur et gère les événements de saisie dans un fil d'exécution d'interface utilisateur unique. Celui-ci est appelé également le fil d'exécution principal.

Android recueille tous les événements dans une file d'attente et traite une instance de la classe Looper.

1-2 - Pourquoi utiliser l'accès concurrentiel ?

Si le programmeur n'utilise pas d'éléments de concurrence, tout le code d'une application Android s'exécute dans le fil d'exécution principal et les instructions sont exécutées l'une après l'autre.

Si vous effectuez une opération de longue durée, par exemple l'accès aux données à partir d'Internet, l'application est bloquée jusqu'à ce que l'opération correspondante ait pris fin.

Pour assurer une bonne expérience utilisateur, toutes les opérations potentiellement lentes d'une application Android devraient être exécutées de manière asynchrone, par exemple, par des éléments de concurrence du langage Java ou du framework Android. Cela comprend toutes les opérations potentiellement lentes, comme l'accès réseau ou l'accès aux fichiers et aux bases de données et les calculs complexes.

Android impose aux applications un temps de réaction au pire des cas. Si une activité ne réagit pas à la saisie de l'utilisateur dans les cinq secondes, le système Android affiche une boîte de dialogue « L'application ne répond pas »(4). Dans cette dernière, l'utilisateur peut choisir d'arrêter l'application.

2 - Les notions de base Android

Ce qui suit implique que vous avez déjà les bases du développement Android. Veuillez consulter le tutoriel de développement Android pour en apprendre les bases. Lisez également les tutoriels de développement Android pour plus d'informations à ce sujet.

3 - L'utilisation des fils d'exécution Java sous Android

3-1 - Utiliser le threading Java sous Android

Android prend en charge l'utilisation de la classe Thread pour effectuer des traitements asynchrones.

Android fournit également le paquetage java.util.concurrent pour exécuter quelque chose en arrière-plan, par exemple, en utilisant les classes ThreadPools et Executor.

Si vous avez besoin de mettre à jour l'interface utilisateur à partir d'un nouveau fil d'exécution, vous devez synchroniser avec le fil d'exécution de l'interface utilisateur.

3-2 - Les inconvénients de l'utilisation des fils d'exécution Java sous Android

Si vous utilisez des fils d'exécution Java, vous devrez tenir compte, dans votre code, des exigences suivantes :

- synchronisation avec le fil d'exécution principal si vous postez des résultats en retour sur l'interface utilisateur ;

- aucune valeur par défaut pour annuler le fil d'exécution ;

- aucun fil d'exécution mis en commun par défaut ;

- pas de mécanisme par défaut pour le traitement des changements de configuration Android.

4 - Les éléments de concurrence dans Android

Par rapport à la norme Java, Android fournit des éléments supplémentaires pour gérer la concurrence. Vous pouvez utiliser la classe android.os.Handler ou les classes AsyncTask. Des approches plus sophistiquées sont basées sur la classe Loader, les fragments conservés et les services.

5 - La classe Handler

5-1 - Le but de la classe Handler

La classe Handler peut être utilisée pour s'enregistrer à un fil d'exécution. Elle fournit un canal simple pour envoyer des données à ce fil d'exécution.

Un objet Handler s'enregistre avec le fil dans lequel il est créé. Par exemple, si vous créez une nouvelle instance de la classe Handler dans la méthode onCreate() de votre activité, l'objet Handler qui en résulte peut être utilisé pour publier des données sur le fil d'exécution principal.

Les données affichées par la classe Handler peuvent être des instances de la classe Message ou de la classe Runnable.

Un gestionnaire est particulièrement utile si vous voulez publier des données sur le fil d'exécution principal à plusieurs reprises.

5-2 - Création et réutilisation des instances de gestionnaires

Pour utiliser un gestionnaire, vous devez créer une classe dérivée et y redéfinir la méthode handleMessage() pour traiter les messages.

Votre fil d'exécution peut envoyer des messages à l'objet Handler par la méthode sendMessage(Message) ou par la méthode sendEmptyMessage().

Pour traiter un Runnable, vous pouvez utiliser la méthode post().

Pour éviter la création d'objets, vous pouvez réutiliser l'objet Handler existant de votre activité.

// Réutilisez le gestionnaire existant si vous n'en avez pas
// à redéfinir le traitement des messages
handler = getWindow().getDecorView().getHandler();

La classe View vous permet de poster des objets de type Runnable par la méthode post().

5-3 - Exemple

Le code suivant illustre l'utilisation d'un gestionnaire par une vue.

Supposons que votre activité utilise le schéma suivant :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<ProgressBar
android:id="@+id/progressBar1" style="android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="false"
android:max="10"
android:padding="4dip" >
</ProgressBar>

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" >
</TextView>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="startProgress"
android:text="Start Progress" >
</Button>

</LinearLayout>

Avec le code suivant, la ProgressBar est mise à jour dès que l'utilisateur appuie sur le Bouton :

package de.vogella.android.handler;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;

public class ProgressTestActivity extends Activity {
private ProgressBar progress;
private TextView text;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
progress = (ProgressBar) findViewById(R.id.progressBar1);
text = (TextView) findViewById(R.id.textView1);
}

public void startProgress(View view) {
// faites une opération longue
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
final int value = i;
doFakeWork();
progress.post(new Runnable() {
@Override
public void run() {
text.setText("Updating");
progress.setProgress(value);
}
});
}
}
};
new Thread(runnable).start();
}

// Simuler une opération chronophage
private void doFakeWork() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

6 - La classe AsynkTask

6-1 - Le but de la classe AsyncTask

La classe AsyncTask encapsule la création d'un processus d'arrière-plan et la synchronisation avec le fil d'exécution principal. Elle prend également en compte l'état d'avancement des tâches en cours d'exécution.

6-2 - L'utilisation de la classe AsyncTask

Pour utiliser AsyncTask, vous devez créer une classe dérivée. AsyncTask utilise les génériques et les varargs. Les paramètres sont AsyncTask<TypeOfVarArgParams, ProgressValue, ResultValue>.

Une AsyncTask est lancée par la méthode execute().

La méthode execute() appelle les méthodes doInBackground() et onPostExecute().

TypeOfVarArgParams est fourni en entrée avec la méthode doInBackground(), ProgressValue est utilisé pour les informations de progression et ResultValue doit être retourné par doInBackground() et passé en paramètre à onPostExecute().

La méthode doInBackground() contient le code de l'instruction qui devrait être accomplie dans un fil d'exécution d'arrière-plan. Cette méthode s'exécute automatiquement dans un fil séparé.
La méthode onPostExecute() se resynchronise avec le fil d'exécution de l'interface utilisateur et lui permet d'être mis à jour. Cette méthode est appelée par le framework une fois que la méthode doInBackground() prend fin.

6-3 - L'exécution de plusieurs AsyncTask en parallèle

Android exécute des tâches AsyncTask avant Android 1.6 et encore à partir d'Android 3.0 dans la séquence par défaut.

Vous pouvez demander à Android de les exécuter en parallèle par l'utilisation de la méthode executeOnExecutor(), en spécifiant AsyncTask.THREAD_POOL_EXECUTOR comme premier paramètre.

L'extrait de code ci-après démontre cela :

// ImageLoader étend AsyncTask
ImageLoader imageLoader = new ImageLoader(imageView);

// Exécution in parallèle
imageLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "http://url.com/image.png");

6-4 - Les inconvénients de l'utilisation des tâches asynchrones

La tâche asynchrone ne gère pas automatiquement les modifications de configuration, c'est-à-dire si l'activité est recréée, le programmeur doit gérer cela dans son code.

Une solution commune à ce problème est de déclarer la tâche asynchrone dans un fragment conservé sans entête.

6-5 - Tâche asynchrone : exemple

Le code suivant montre comment utiliser la classe AsyncTask pour télécharger le contenu d'une page Web.

Créez un nouveau projet Android appelé de.vogella.android.asynctask avec une activité appelée ReadWebpageAsyncTask. Ajoutez l'autorisation android.permission.INTERNET à votre fichier AndroidManifest.xml.

Créez la mise en page suivante :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<Button
android:id="@+id/readWebpage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="Load Webpage" >
</Button>

<TextView
android:id="@+id/TextView01"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Placeholder" >
</TextView>

</LinearLayout>

Modifiez votre activité comme suit :

package de.vogella.android.asynctask;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import de.vogella.android.asyntask.R;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class ReadWebpageAsyncTask extends Activity {
private TextView textView;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textView = (TextView) findViewById(R.id.TextView01);
}

private class DownloadWebPageTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... urls) {
String response = "";
for (String url : urls) {
DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
try {
HttpResponse execute = client.execute(httpGet);
InputStream content = execute.getEntity().getContent();

BufferedReader buffer = new BufferedReader(new InputStreamReader(content));
String s = "";
while ((s = buffer.readLine()) != null) {
response += s;
}

} catch (Exception e) {
e.printStackTrace();
}
}
return response;
}

@Override
protected void onPostExecute(String result) {
textView.setText(result);
}
}

public void onClick(View view) {
DownloadWebPageTask task = new DownloadWebPageTask();
task.execute(new String[] { "http://www.vogella.com" });

}
}

Si vous exécutez votre application et appuyez sur le bouton, le contenu de la page Web définie est lu en arrière-plan. Une fois ce processus terminé, votre TextView est actualisé.

7 - Les processus d'arrière-plan et la gestion des cycles de vie

7-1 - Conserver l'état pendant les changements de configuration

Un défi de l'utilisation des fils d'exécution est de prendre en compte le cycle de vie de l'application. Le système Android peut tuer votre activité ou déclencher un changement de configuration qui le redémarrera.

Vous devez également gérer les boîtes de dialogue ouvertes, car ces dernières sont toujours reliées à l'activité qui les a créées. Dans le cas où l'activité redémarre et vous accédez à un dialogue existant, vous recevez une exception Vue non attachée au gestionnaire de fenêtre(5).

Pour enregistrer un objet, vous pouvez utiliser la méthode onRetainNonConfigurationInstance(). Cette méthode vous permet d'enregistrer un objet pour le cas où l'activité sera bientôt relancée.

Pour récupérer cet objet, vous pouvez utiliser la méthode getLastNonConfigurationInstance(). Ainsi, vous pouvez enregistrer un objet, par exemple un fil d'exécution, même si l'activité est redémarrée.

getLastNonConfigurationInstance() retourne « null » si l'activité est lancée pour une première fois ou si elle a été terminée par la méthode finish().

onRetainNonConfigurationInstance() est obsolète depuis API 13, il est recommandé d'utiliser les fragments et la méthode setRetainInstance() afin de conserver les données sur les changements de configuration.

7-2 - Utiliser l'objet application pour stocker des objets

Si plus d'un objet doit être stocké à travers des activités et des changements de configuration, vous pouvez implémenter une classe Application pour votre application Android.

Pour utiliser votre classe application, assignez le nom de la classe à l'attribut android:name de votre application.

<application android:icon="@drawable/icon" android:label="@string/app_name"
android:name="MyApplicationClass">
<activity android:name=".ThreadsLifecycleActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

La classe application est créée automatiquement par l'environnement d'exécution Android et est disponible à moins que l'ensemble du processus de l'application soit terminé.

Cette classe peut être utilisée pour accéder à des objets qui devraient être des activités transversales ou disponibles pour l'ensemble du cycle de vie de l'application. Dans la méthode onCreate(), vous pouvez créer des objets et les rendre disponibles via les champs « public » ou des méthodes getter.

La méthode OnTerminate() de la classe application est utilisée seulement pour tester. Si Android met fin au processus dans lequel l'application s'exécute, toutes les ressources allouées sont libérées automatiquement.

Vous pouvez accéder à l'Application par la méthode getApplication() dans votre activité.

8 - Fragments et processus d'arrière-plan

8-1 - Conserver une instance lors des changements de configuration

Vous pouvez utiliser des fragments sans interface utilisateur et les conserver entre les changements de configuration par un appel à leur méthode setRetainInstance().

De cette façon, votre Thread ou AsyncTask est conservé lors des changements de configuration. Cela vous permet d'effectuer un traitement de fond sans tenir compte explicitement du cycle de vie de votre activité.

8-2 - Fragments sans entête

Si vous effectuez un traitement de fond, vous pouvez attacher dynamiquement un fragment sans entête à votre application et appeler setRetainInstance() à true. Ce fragment est conservé lors des changements de configuration et vous pouvez y effectuer le traitement asynchrone.

9 - En savoir plus sur les fragments

Vous pouvez utiliser ce tutoriel sur les fragments (Tutoriel Developpez « vogella - Utiliser les fragments sous Android ») pour apprendre à utiliser les fragments.

10 - Chargeur

10-1 - Le but de la classe Loader

La classe Loader permet de charger des données de manière asynchrone dans une activité ou un fragment. Elle peut contrôler la source des données et fournir de nouveaux résultats au moment du changement de contenu. Elle persiste également les données entre les changements de configuration.

Si le résultat est récupéré par le chargeur après la déconnexion de l'objet de son parent (activité ou fragment), il peut mettre les données en cache.

Les chargeurs ont été introduits dans Android 3.0 et font partie de la couche de compatibilité pour les versions Android supérieures à 1.6.

10-2 - Implémenter un Chargeur

Vous pouvez utiliser la classe abstraite AsyncTaskLoader comme base pour vos propres implémentations du chargeur.

Le LoaderManager d'une activité ou d'un fragment gère une ou plusieurs instances de chargeur. La création d'un chargeur se fait par l'appel de méthode suivant :

# démarrer un nouveau chargeur ou se reconnecter à un chargeur existant
getLoaderManager().initLoader(0, null, this);

Le premier paramètre est un identifiant unique qui peut être utilisé par la classe de rappel pour identifier le Chargeur plus tard. Le deuxième paramètre est un ensemble qui peut être passé à la classe de rappel pour plus d'informations.

Le troisième paramètre de initLoader() est la classe qui est appelée une fois que l'initialisation a été lancée (classe de rappel). Cette classe doit implémenter l'interface LoaderManager.LoaderCallbacks. C'est une bonne pratique qu'une activité ou le fragment qui utilise un chargeur implémente l'interface LoaderManager.LoaderCallbacks.

Le chargeur n'est pas directement créé par l'appel de méthode getLoaderManager().InitLoader(), mais il doit être créé par la classe de rappel dans la méthode onCreateLoader().

Une fois que le chargeur a terminé la lecture asynchrone de données, la méthode onLoadFinished() de la classe de rappel est appelée. Ici, vous pouvez mettre à jour votre interface utilisateur.

10-3 - Base de données SQLite et CursorLoader

Android fournit une implémentation de chargeur par défaut, la classe CursorLoader, pour gérer les connexions à la base de données SQLite.

Pour un ContentProvider basé sur une base de données SQLite, vous pouvez généralement utiliser la classe CursorLoader. Ce chargeur de curseur interroge la base de données dans un fil d'exécution d'arrière-plan afin que l'application ne soit pas bloquée.

La classe CursorLoader est la remplaçante des curseurs d'activité gérés (Activity-managed) qui sont maintenant obsolètes.

Si le Curseur devient invalide, la méthode onLoaderReset() est appelée sur la classe de rappel.

11 - Exercice : Chargeur personnalisé pour préférences

11-1 - Implémentation

Dans le code suivant, vous créez une implémentation personnalisée de chargeur pour gérer les préférences. À chaque chargement, la valeur de la préférence est augmentée.

Créez un projet appelé com.vogella.android.loader.preferences avec une activité appelée MainActivity.

Créez la classe suivante comme implémentation personnalisée de AsyncTaskLoader pour la gestion des préférences partagées :

package com.vogella.android.loader.preferences;

import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;

public class SharedPreferencesLoader extends AsyncTaskLoader<SharedPreferences>
implements SharedPreferences.OnSharedPreferenceChangeListener {
private SharedPreferences prefs = null;

public static void persist(final SharedPreferences.Editor editor) {
editor.apply();
}

public SharedPreferencesLoader(Context context) {
super(context);
}

// Charge les données de façon asynchrone
@Override
public SharedPreferences loadInBackground() {
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
prefs.registerOnSharedPreferenceChangeListener(this);
return (prefs);
}

@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// notifier le chargeur que le contenu a changé
onContentChanged();
}

/**
* starts the loading of the data
* once result is ready the onLoadFinished method is called
* in the main thread. It loader was started earlier the result
* is return directly

* method must be called from main thread.
*/
@Override
protected void onStartLoading() {
if (prefs != null) {
deliverResult(prefs);
}

if (takeContentChanged() || prefs == null) {
forceLoad();
}
}
}

L'exemple de code suivant illustre l'utilisation de ce chargeur dans une activité :

package com.vogella.android.loader.preferences;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.Loader;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity implements
LoaderManager.LoaderCallbacks<SharedPreferences> {
private static final String KEY = "prefs";
private TextView textView;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.prefs);
getLoaderManager().initLoader(0, null, this);

}

@Override
public Loader<SharedPreferences> onCreateLoader(int id, Bundle args) {
return (new SharedPreferencesLoader(this));
}

@SuppressLint("CommitPrefEdits")
@Override
public void onLoadFinished(Loader<SharedPreferences> loader,
SharedPreferences prefs) {
int value = prefs.getInt(KEY, 0);
value += 1;
textView.setText(String.valueOf(value));
// actualiser la valeur
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(KEY, value);
SharedPreferencesLoader.persist(editor);
}

@Override
public void onLoaderReset(Loader<SharedPreferences> loader) {
// NON utilisé
}
}

11-2 - Test

LoaderManager appelle automatiquement onLoadFinished() dans votre activité après un changement de configuration. Exécutez l'application et assurez-vous que la valeur stockée dans les préférences partagées est augmentée à chaque changement de configuration.

12 - L'utilisation des services

Vous pouvez également utiliser les services Android pour effectuer des tâches de fond. Voir le tutoriel les services sous Android pour plus de détails.

13 - En apprendre plus sur les services

Vous pouvez utiliser le tutoriel les services sous Android pour apprendre à utiliser les services.

14 - Exercice : le cycle de vie de l'activité et les fils d'exécution

L'exemple suivant téléchargera une image à partir d'Internet dans un fil d'exécution et affichera une boîte de dialogue jusqu'à ce que le téléchargement soit terminé. Nous veillerons à ce que le fil d'exécution soit conservé, même si l'activité redémarre et que la boîte de dialogue s'affiche et se ferme correctement.

Pour cet exemple, créez un nouveau projet Android appelé de.vogella.android.threadslifecycle avec l'activité appelée ThreadsLifecycleActivity. Ajoutez également l'autorisation d'utiliser l'Internet à votre fichier AndroidManifest.xml.

Votre fichier AndroidManifest.xml devrait ressembler à ce qui suit :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.vogella.android.threadslifecycle"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk android:minSdkVersion="10" />

<uses-permission android:name="android.permission.INTERNET" >
</uses-permission>

<application
android:icon="@drawable/icon"
android:label="@string/app_name" >
<activity
android:name=".ThreadsLifecycleActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

Changez la mise en page main.xml comme suit :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="downloadPicture"
android:text="Click to start download" >
</Button>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="resetPicture"
android:text="Reset Picture" >
</Button>
</LinearLayout>

<ImageView
android:id="@+id/imageView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/icon" >
</ImageView>

</LinearLayout>

Maintenant, adaptez votre activité. Dans cette dernière, le fil d'exécution est enregistré et la boîte de dialogue fermée si l'activité est détruite.

package de.vogella.android.threadslifecycle;

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.ImageView;

public class ThreadsLifecycleActivity extends Activity {
// Statique pour que le fil accède au dernier attribut
private static ProgressDialog dialog;
private static Bitmap downloadBitmap;
private static Handler handler;
private ImageView imageView;
private Thread downloadThread;

/** Appelée quand l'activité est créée. */

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// créer un gestionnaire pour actualiser l'IU
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
imageView.setImageBitmap(downloadBitmap);
dialog.dismiss();
}

};
// obtenir la dernière imageView après le redémarrage de l'application
imageView = (ImageView) findViewById(R.id.imageView1);
Context context = imageView.getContext();
System.out.println(context);
// Avons-nous déjà téléchargé l'image ?
if (downloadBitmap != null) {
imageView.setImageBitmap(downloadBitmap);
}
// vérifier si le thread est déjà en cours d'exécution
downloadThread = (Thread) getLastNonConfigurationInstance();
if (downloadThread != null && downloadThread.isAlive()) {
dialog = ProgressDialog.show(this, "Download", "downloading");
}
}

public void resetPicture(View view) {
if (downloadBitmap != null) {
downloadBitmap = null;
}
imageView.setImageResource(R.drawable.icon);
}

public void downloadPicture(View view) {
dialog = ProgressDialog.show(this, "Download", "downloading");
downloadThread = new MyThread();
downloadThread.start();
}

// conserver le thread
@Override
public Object onRetainNonConfigurationInstance() {
return downloadThread;
}

// dissimuler la boîte de dialogue si l'activité est détruite
@Override
protected void onDestroy() {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
dialog = null;
}
super.onDestroy();
}

// Méthode utilitaire pour télécharger l'image depuis Internet
static private Bitmap downloadBitmap(String url) throws IOException {
HttpUriRequest request = new HttpGet(url);
HttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(request);

StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode == 200) {
HttpEntity entity = response.getEntity();
byte[] bytes = EntityUtils.toByteArray(entity);

Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0,
bytes.length);
return bitmap;
} else {
throw new IOException("Download failed, HTTP response code "
+ statusCode + " - " + statusLine.getReasonPhrase());
}
}

static public class MyThread extends Thread {
@Override
public void run() {
try {
// Simuler une connexion lente
try {
new Thread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
downloadBitmap = downloadBitmap("http://www.devoxx.com/download/attachments/4751369/DV11");
// Mettre à jour l'interface utilisateur
handler.sendEmptyMessage(0);
} catch (IOException e) {
e.printStackTrace();
} finally {

}
}
}
}

Exécutez votre application et appuyez sur le bouton pour démarrer un téléchargement. Vous pouvez tester le comportement correct du cycle de vie en changeant l'orientation dans l'émulateur via le raccourci Ctrl + F11.

Il faut aussi noter que Thread est une classe interne statique. Il est important d'utiliser une classe interne statique pour votre processus d'arrière-plan, car sinon la classe interne contiendrait une référence vers la classe dans laquelle elle a été créée. Comme le fil d'exécution est passé à la nouvelle instance de votre activité, cela créerait une fuite de mémoire si l'ancienne activité était encore référencée par le Thread.

15 - Optimisations de mémoire

15-1 - L'utilisation des caches

Une autre façon d'éviter une mauvaise performance est de mettre en cache les objets coûteux. Par exemple, si vous téléchargez des images à partir d'Internet pour les afficher dans un ListView, vous devez les garder dans un cache pour éviter de les télécharger plusieurs fois.

Un cache « Moins Utilisé Récemment »(6) garde des traces de l'usage de son contenu. Il a une taille donnée et si cette taille est dépassée, il supprime les éléments qui n'ont pas été utilisés depuis longtemps. Ce comportement est illustré dans l'image ci-dessous :

L'appel get() pour un objet déplace celui-ci au sommet du cache

La plate-forme Android fournit la classe LruCache à partir de l'API 12 (ou dans la bibliothèque support-v4). La classe LruCache fournit une implémentation de cache Least Recently Used.

L'exemple de code suivant illustre une implémentation possible de la classe LruCache pour la mise en cache des images :

public class ImageCache extends LruCache<String, Bitmap> {

public ImageCache(int maxSize) {
super(maxSize);
}

@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}

@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
oldValue.recycle();

Pour déterminer la taille initiale de la mémoire cache, vous pouvez utiliser MemoryClass et utiliser une fraction de la quantité totale de mémoire disponible comme le montre le code suivant :

int memClass = ((ActivityManager)activity.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
int cacheSize = 1024 * 1024 * memClass / 8;
LruCache cache = new LruCache<String, Bitmap>(cacheSize);

15-2 - L'utilisation des structures de mémoire efficaces

Android fournit des structures de données qui sont plus efficaces pour le mappage de valeurs vers d'autres objets. Utilisez ces objets si possible, ils évitent la création des objets comme dans le cas de l'utilisation de HashMap. La création d'objets peut être coûteuse et doit être évitée afin de réduire le nombre de fois où le garbage collector doit s'exécuter.

Le tableau 1 donne des exemples pour SparseArrays.

Tableau 1. Des structures de mémoire efficaces

Structure de mémoire

Description

SparseArray<E>

Effectue le mappage des entiers vers des Objets, évite la création des objets Integer.

SparseBooleanArray

Effectue le mappage des entiers vers des booléens.

SparseIntArray

Effectue le mappage des entiers vers des entiers.

15-3 - Nettoyer l'utilisation mémoire

Depuis l'API 14, vous pouvez substituer la méthode onTrimMemory() dans les composants Android. Cette méthode est appelée par le système Android et vous demande de nettoyer la mémoire au cas où le système Android nécessite des ressources pour les processus de premier plan.

16 - Le mode strict

Android vous permet de donner au système l'instruction de signaler tout processus de longue durée effectué dans le fil d'exécution de l'interface utilisateur. Voir l'utilisation de StrictMode sous Android pour plus de détails.

17 - Retour d'information par boîte de dialogue

Si vous effectuez une opération de longue durée, c'est une bonne pratique de fournir à l'utilisateur des informations en retour sur l'opération en cours d'exécution.

Vous pouvez fournir des informations sur la progression via la barre d'action, par exemple, par l'intermédiaire d'une vue d'action. Sinon, vous pouvez utiliser dans votre mise en page, une barre de progression ProgressBar que vous réglez sur visible et mettez à jour au cours d'une opération de longue durée. Cette approche est appelée fournir un retour d'information en ligne(7)NDT  : providing inline feedback en anglais dans le texte original., vu qu'elle laisse l'interface utilisateur réactive.

Pour bloquer l'interface utilisateur pendant l'opération, vous pouvez utiliser la boîte de dialogue ProgressBar qui permet d'afficher la progression. La Javadoc de ProgressBar donne un bel exemple de son utilisation.

Si possible, évitez d'utiliser la boîte de dialogue ProgressBar ou des approches similaires. Privilégiez la fourniture d'un retour d'information en ligne pour que votre interface utilisateur reste réactive.

18 - Liens et littérature

18-1 - Code source

- Codes source d'exemple

18-2 - Ressources concernant l'accès concurrentiel

- Networking with Android (Tutoriel Developpez " vogella - L'accès HTTP sous Android ")

- Google blog about multithreating

19 - Remerciements

Vous pouvez retrouver l'article original à l'adresse Android Background Processing withHandlers and AsyncTask and Loaders - Tutorial. Nous remercions Lars Vogel qui nous a aimablement autorisé à traduire et héberger ses articles.

Nous remercions aussi Mishulyna pour sa traduction, ainsi que milkoseck pour sa relecture orthographique.

(1)NDT : la littérature anglaise utilise le mot « thread ». Ce terme a été traduit par « fil d'exécution » dans cet article.

(2)NDT : la littérature anglaise utilise le mot « handler ». Ce terme a été traduit par « gestionnaire » dans cet article.

(3)NDT : la littérature anglaise utilise le mot « AsyncTask ». Ce terme a été traduit par « tâche asynchrone » dans cet article.

(4)NDT : Application not responding(ANR) en anglais dans le texte original.

(5)NDT  : View not attached to window manager en anglais dans le texte original.

(6)NDT  : Least Recently Used - (LRU) en anglais dans le texte original.