Cycle de vie d’une application Android

Si vous avez essayé d’écrire un application un peu poussée avec ce que vous avez appris lors du tutoriel précédent, vous avez du vous heurter à une méchante erreur lorsque l’application passait en arrière plan. En effet, à l’appui sur le bouton « Home » (la petite maison) ou « Back » (la flèche de retour arrière), mais aussi dès qu’un coup de fil arrive, l’application en cours passe dans un autre état. Elle n’est pas fermée, mais ce changement d’état, si il n’est pas géré, fait généralement planter votre belle création.

Dans ce tuto, vous apprendrez :

  • Les différents états d’une application Android.
  • L’utilité des ces états et pourquoi c’est du tout bon pour vous.
  • Les conséquences sur le développement.
  • Comment gérer ces états, et notamment la sauvegarde de données.

Pré-requis :

  • Avoir fait le tuto précédent.
  • Bien maitriser le concept d’événement en programmation.

Temps estimé : 1 h 30 minimum.

Attention, chapitre dense. Pas beaucoup d’images, beaucoup d’explications, et un énorme bloc de code à la fin lourdement commenté. Le tuto a été pensé au mieux pour être accessible, mais ça reste imposant. Par la suite, il y aura des tuto plus légers, mais il faut passer par celui-ci. C’est très interressant, mais demande beaucoup de concentration.

Avec Android, vous programmez sur un terminal mobile

« Merci du tuyau vieux, on avait pas remarqué ». Certes, c’est un peu le principe, mais il faut savoir que cela implique un vision très différente d’une application de bureau :

  • L’écran est petit, on ne peut pas afficher plusieurs applications à la fois.
  • La mémoire est limitée, on ne peut pas laisser tourner trop d’applications à la fois.
  • On peut recevoir un appel à tout instant, et l’utilisateur doit pouvoir le décrocher, parler et raccrocher puis retourner à son activité précédente sans souci.
  • Les ressources CPU sont limitées, et le système s’octroie le droit de tuer tout processus jugé inutile, à n’importe quel moment.

Pour toutes ces raisons, une application sur téléphone portable ne peut pas rester à briller de mille feux tout le temps, jusqu’à ce que l’utilisateur la ferme explicitement. C’est vrai pour toutes les plateformes mobiles, iPhone, Symbian, Blackberry, etc, mais vous verrez que la gestion de ce problème a été particulièrement bien pensée sur Android.

Pour vous ça veut dire quoi ?

D’abord que votre application peut être quittée à tout moment. Ensuite qu’elle peut être relancée à tout moment. Mais surtout « quitter » ne veut pas toujours dire « fermer », et si votre application tourne à l’instant X, elle peut être tuée brusquement une seconde après, ou mise en pause. Non seulement vous n’avez pas votre mot à dire sur la question, vous n’avez aucun contrôle là-dessus, mais en plus il est impossible de prédire quand ça arrivera.

Dit comme ça, ça semble un peu ennuyeux.

Heureusement, un système d’événements permet à votre application d’être prévenue quand cela arrive et lui donne une change d’exécuter du code à ce moment là. La bonne nouvelle donc, c’est que tout est pensé pour que l’expérience utilisateur soit la plus agréable possible, tout en vous laissant la possibilité de créer une application stable et cohérente. Mais ça demande un petit peu de boulot de votre part. Let’s see.

Les différents États d’une application Android

Si vous regardez la doc officielle, vous allez tomber sur un schéma peu digeste, le fameux Android Activity Lifecyle que tout développeur débutant sur la plateforme a contemplé en se grattant la tête, le regard vitreux.

Ce qu’il faut en retenir, c’est que vous aller surcharger et réécrire pour toutes vos activités les méthodes d’événement qui annoncent ces états. Ainsi :

Vous n’avez pas de contrôle sur l’instanciation de Activity, c’est pour cela qu’on utilise onCreate() comme « main ». Cette méthode est le premier événement appelé quand une application démarre. Elle n’est appelée qu’une fois. La première fois que l’utilisateur clique sur le bouton de l’application. onCreate() ne sera rappelé que si le téléphone est redémarré (et l’application relancée) ou qu’Android a tué votre application pour récupérer des ressources système en période de crise. C’est dans onCreate() que l’on place l’initialisation des vues et des membres de la classes, ainsi ils sont définitivement mis en cache, plus besoin de les recréer. Le contraire de onCreate() est onDestroy(). Après onDestroy(), l’application est définitivement tuée, et on doit rappeler onCreate().

onStart() est appelé après onCreate() ET à chaque fois que votre application est mise en sommeil puis relancée. Une application est mise en sommeil dès qu’elle n’est plus visible à l’écran. Le redémarrage sera plus rapide que si l’application devait complètement redémarrer car il n’y pas besoin de tout initialiser. L’inverse de onStart() est onStop(), qui est appelée juste avant onDestroy() ET surtout à chaque fois que votre application sera mise en sommeil.

onResume() est appelée juste après onStart() ET surtout à chaque fois qu’un écran de votre application est affiché. Ça veut dire que si l’utilisateur clique sur « Home », puis relance votre application immédiatement, cela lancera onResume(). L’inverse de onResume() est onPause(), appelée appelé juste avant onStop() ET à chaque fois que votre application est remplacée par une autre à l’écran. Cela arrive très souvent, principalement à cause d’un appel, du lancement d’une autre application ou de l’utilisation des boutons « Home » et « Back ».

En clair, si vous lancer une application, Android appelle onCreate() puis onStart() puis onResume(). Si Android tue votre application il appellera dans l’ordre onPause(), onStop() et onDestroy().

Si un appel survient pendant l’utilisation de votre application, celle-ci n’est plus affichée. Android appelle onPause(), laisse l’autre application se lancer, puis appelle onStop(). Votre utilisateur relance l’application ? Android appelle onSart() puis onResume().

Il existe en fait de nombreuses autres méthodes qui permettent de gérer plus finement ce cycle de vie, telle que onRestart() ou onPostCreate(), mais nous n’irons pas jusque dans ce niveau de détails. La doc est votre amie, fidèle, mais anglaise. Personne n’est parfait et on pardonne ses amis.

Ok, c’est bien, mais je fais quoi dans quelle méthode et pourquoi ?

Dans onCreate() :

On met toute la logique d’initialisation :

  • Création de fichiers temporaires.
  • Initialisation des vues du XML.
  • Initialisation des membres de la classe.

Dans onDestroy() :

On a rarement quelquechose à faire ici. Parfois supprimer les fichiers temporaires.

Dans onStart() :

On met ici la récupération des données sur la mémoire morte du téléphone :

  • Chargement des préférences utilisateur.
  • Chargement des états de l’application qu’on souhaite garder d’une ouverture à l’autre.

Dans onStop() :

En toute logique :

  • Sauvegarde des états de l’application qu’on souhaite garder d’une ouverture à l’autre.
  • On ferme la base de données.

Dans onResume() :

Principalement l’ouverture de la base de données car c’est une connexion active qui ne doit pas être maintenu quand l’activité est en arrière plan. Tout ce qui demande de rester « à l’écoute » est généralement redémarré ici. Le capteur de mouvement, le GPS, etc.

Dans onPause() :

La fermeture de la base de données.

Et pour quelques méthodes de plus :

onSaveInstanceState() et sa contrepartie onRestoreInstanceState() servent à accélérer le redémarrage de l’application si elle a été tuée par le système, pas l’utilisateur. Ce ne sont pas des méthodes du cycle de vie car elle ne sont pas toujours appelées, seulement si Android à besoin de respirer et fait un bon gros génocide numérique. Pourtant on constate parfois que ces méthodes sont appelées de ci, de là, par exemple quand on appuit sur “Home”.

On y met aussi le chargement et la sauvegarde des préférences utilisateurs et des états, mais en utilisant une méthode différente. Duplication, quand tu nous tiens…

Montre moi le code !

Les bases de données feront l’objet d’un autre chapitre, alors on va prendre un exemple académique. L’application fait sert à afficher une image. Une liste déroulante permet de choisir combien d’images afficher à la fois.

D’abord, strings.xml :

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- On reste dans le classique -->
    <string name="hello">Hello World, LifeCyleTutorial!</string>
    <string name="app_name">LifeCyleTutorial</string>
    <!-- 
    	Ici on met l'invitation de selection de la liste déroulante.
    	Un backslash permet de d'échapper l'apostrophe, sinon elle
    	ne s'affiche pas.
    -->
	<string name="invite">Choisissez le nombre d\'images</string>
	<!-- Le label du bouton "fermer" -->
	<string name="close">Fermer l\'application</string>
</resources>

Ensuite, nous allons découvrir un nouveau fichier, res/values/arrays.xml. Il faut le créer car il n’existe pas par défaut. Il permet de stocker des valeurs sous forme de liste, et de les récupérer dans le code Java comme un tableau. Là où c’est pratique, c’est qu’une liste de arrays.xml peut être utilisée directement pour remplir de données un type AbsListView. Or Spinner, la classe dont nous allons nous servir pour faire la liste déroulante, hérite de AbsListView.

arrays.xml (n’oubliez pas le « s ») :

<?xml version="1.0" encoding="utf-8"?>
<!-- ressources contient toutes les listes -->
<resources>
	<!-- on peut typer une liste
     ici une liste de chaînes de caractères 
     on lui donne un nom qui permet de la référencer plus tard -->
    <string-array name="number">
        <item>1</item>
        <item>2</item>
        <item>3</item>
        <item>4</item>
        <item>5</item>
    </string-array>
</resources>

On va maintenant composer notre layout dans main.xml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
 
    <!-- 
    	Spinner est le widget de liste déroulante.
    	android:prompt est une référence simple à strings.xml pour le message d'invite.
    	android:entries est une référence à arrays.xml pour « populer » la liste.
    -->
	<Spinner 
	        android:id="@+id/spinner"
	        android:layout_width="fill_parent"
	        android:layout_height="wrap_content"
	        android:prompt="@string/invite"
	        android:entries="@+array/number"
	 />
 
	  <!-- 
    	On rajoute un Layout qui contient les images.
    	Notez le android:layout_height="wrap_content".
    	Si il était en "fill_parent", on ne verrait pas
    	le dernier bouton.
       -->
 
	 <LinearLayout 
	    android:id="@+id/picture_list"
	    android:orientation="vertical"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
    >
 
		<!-- 
    	On Créé cinq images, dont une seule visible.
    	On pourrait tout aussi bien les mettre dynamiquement dans 
    	onCreate(), ça rendrait le programme plus souple mais 
    	autant ne pas surcharger cet exemple.
    	Notez qu'on ajoute pas d'id, car nous n'en utiliserons pas.
    	Enfin, l'image utilisée est icon, car cette image existe
    	toujours dans tout projet Android nouvellement créé.
       -->
 
		<ImageView 
		  android:src="@drawable/icon"
		  android:layout_width="wrap_content"
		  android:layout_height="wrap_content"/>		
 
		<ImageView 
		  android:src="@drawable/icon"
		  android:layout_width="wrap_content"
		  android:layout_height="wrap_content"/>
 
		<ImageView 
		  android:src="@drawable/icon"
		  android:layout_width="wrap_content"
		  android:layout_height="wrap_content"/>		
 
		<ImageView 
		  android:src="@drawable/icon"
		  android:layout_width="wrap_content"
		  android:layout_height="wrap_content"/>
 
		<ImageView 
		  android:src="@drawable/icon"
		  android:layout_width="wrap_content"
		  android:layout_height="wrap_content"/>		
 
    </LinearLayout>
 
    <!-- 
    	On rajoute un bouton pour fermer l'application.
 
    -->
 
    <Button 
     android:id="@+id/close_button"
	 android:text="@string/close"
	 android:layout_width="fill_parent"
	 android:layout_height="wrap_content"
	/>
 
</LinearLayout>

Bien, attaquons nous à la partie difficile, la classe Java. D’abord, essayons la méthode naïve, en déclarant la seule méthode du cycle de vie obligatoire, onCreate().

LifeCyleTutorial.java :

package net.evidence.LifeCyleTutorial;
 
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;
 
public class LifeCyleTutorial extends Activity {
 
	Spinner spinner;
	LinearLayout pictures_list;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
 
    	// TOUJOURS, TOUJOURS, TOUJOURS appeler la méthode du parent
    	// au début d'une méthode du cycle de vie qu'on override
        super.onCreate(savedInstanceState);
 
        // Dans onCreate(), on initialise la vue
        // car on a besoin de le faire une seule fois
        setContentView(R.layout.main);
 
        // On peut aussi initiliser des membres de classe
        // tel que des accès en cache pour les objets View
        // afin d'éviter d'appeler findViewById à chaque
        // fois qu'on a besoin d'eux
 
        // Ici, la liste déroulante
        this.spinner = (Spinner) findViewById(R.id.spinner);
 
        // Là, le conteneur des images
        this.pictures_list = (LinearLayout) findViewById(R.id.picture_list);
 
        // "liste" est un abus de langage car on utilise par vraiment
        // une liste mais un LinearLayout
        // nous verrons les vraies listes dans un autre chapitre
 
        /* Pour finir, on bind une bonne fois pour toute les événements
          de l'application. Le plus souvent on a besoin de le faire
          une seule fois, bien que certaines interfaces avancées
          demandent de le faire à la volée.
 
          On ajoute donc un comportement au changement de valeur
          de la liste déroulante. Ici on va utiliser une classe anonyme
          pour faire vite. Dans un programme propre, et comme expliqué
          dans le chapitre précédent, il vaudrait mieux créer un événement
          personnalisé dans un package "event" dédié.
        */ 
 
        this.spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
 
			@Override
			public void onItemSelected(AdapterView arg0, View arg1,
					int arg2, long arg3) {
 
				// On met ici ce qui arrive quand on change la valeur
				// du spinner. Dans notre cas, rafraichir les images
				// Comme c'est une innerclass, "this" pointe sur
				// OnItemSelectedListener et on doit utiliser la syntaxe
				// barbare vu dans un tuto précédent
				LifeCyleTutorial.this.refreshPicturesList();
 
			}
 
			@Override
			public void onNothingSelected(AdapterView arg0) {
				// OSEF dans notre cas
			}
 
        });
 
        // Pour finir, on lie l'événement au bouton "fermer"
        ((Button) findViewById(R.id.close_button)).setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// finish est une méthode d'Activity qui ferme
				// l'application
				LifeCyleTutorial.this.finish();
			}
		});
 
        // on met à jour l'interface au moins une fois
        this.refreshPicturesList();
 
        // Bien montrer que l'on est dans onCreate()
        this.popUp("onCreate()");
 
    }
 
    /**
     * Créer un Toast avec un message qui s'affiche immédiatement
     * pour une seconde.
     * On va utiliser ça pour bien visualiser les changements d'états.
     * Vous allez voir que les moments où on passe d'un état à l'autre
     * ne sont pas vraiment instinctifs : en effet votre application
     * se ferme et s'ouvre elle même en parallèle, et les messages
     * semblent mélangés.
     * Essayez de cliquer sur "home", "back", "téléphoner" ou une autre
     *  application pour voir les différents effets.
     *  Vous verrez alors l'interêt de bien remplir toutes les méthodes.
     */
    public void popUp(String message)
    {
    	Toast.makeText(this, message, 1).show();
    }
 
    /**
     * Retourne le nombre d'images à afficher d'après la sélection
     * dans la liste déroulante
     */
    public int getNumberOfPictures()
    {
    	return Integer.parseInt((String) this.spinner.getSelectedItem());
    }
 
    /**
     * Place la liste déroulante sur la valeur selectionnée.
     * Ici on utilise le fait que la valeur du nombre de photos
     * correspond presque à sa position dans le spinner.
     * Dans un vrai programme, on a généralement un système de mapping
     * plus complexe, mais ce n'est pas le but du tuto.
     */
    public void setNumberOfPictures(int number_of_pictures)
    {
    	// -1 car la numérotation du spinner commence à zéro
    	// mais notre array commence à 1
    	this.spinner.setSelection(number_of_pictures - 1);
    }
 
    /**
     * Met à jours la liste des images. On ne va pas créer et retirer
     * des vues à tout bout de champ. On va donc juste les cacher.
     * Bien sûr c'est un exemple académique et pas optimisé. On cherche
     * ici à comprendre comment ça marche, pas à recoder Google Map.
     */
	private void refreshPicturesList() {
 
		/* On récupère le nombre de vues.
		 on sait qu'il y en a 5, mais cette méthode est propre car
		 elle marche dans tous les cas. Si par exemple vous décidez
		 de créér un nombre de ImageView à la volée dans onCreate(),
		 cette méthode marche toujours. */
		int number_of_picture_views = this.pictures_list.getChildCount();
 
		// On récupère le nombres d'image qu'on veut afficher
		int picture_to_display = this.getNumberOfPictures();
 
		// On va utiliser une vue image, mieux vaut l'initialiser hors de la boucle
		ImageView img;
 
		// On met autant d'images que voulu en visible
		for (int i=0; i < picture_to_display; i++ )
		{
			img = (ImageView) this.pictures_list.getChildAt(i);
			img.setVisibility(View.VISIBLE);
		}
 
		// Tout le reste est en invisible
		for (int i=picture_to_display; i < number_of_picture_views; i++ )
		{
			img = (ImageView) this.pictures_list.getChildAt(i);
			img.setVisibility(View.GONE);
		}
 
	}
 
}

Normalement, rien d’insurmontable si vous avez fait les précédents tutos, tout ça reste dans les rails des vos acquis. Vous pouvez donc apprécier l’application suivante :

Changer la liste déroulante fait bien ce qui est attendu. Mais si vous relancez l’application, on retourne à la case départ, avec le spinner sur « 1 » et une seule image. Voyons comment changer ça :

package net.evidence.LifeCyleTutorial;
 
// quelques nouveaux imports
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;
 
public class LifeCyleTutorial extends Activity {
 
	Spinner spinner;
	LinearLayout pictures_list;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
 
    	// TOUJOURS, TOUJOURS, TOUJOURS appeler la méthode du parent
    	// au début d'une méthode du cycle de vie qu'on override
        super.onCreate(savedInstanceState);
 
        // Dans onCreate(), on initialise la vue
        // car on a besoin de le faire une seule fois
        setContentView(R.layout.main);
 
        // On peut aussi initiliser des membres de classe
        // tel que des accès en cache pour les objets View
        // afin d'éviter d'appeler findViewById à chaque
        // fois qu'on a besoin d'eux
 
        // Ici, la liste déroulante
        this.spinner = (Spinner) findViewById(R.id.spinner);
 
        // Là, le conteneur des images
        this.pictures_list = (LinearLayout) findViewById(R.id.picture_list);
 
        // "liste" est un abus de langage car on utilise par vraiment
        // une liste mais un LinearLayout
        // nous verrons les vraies listes dans un autre chapitre
 
        /* Pour finir, on bind une bonne fois pour toute les événements
          de l'application. Le plus souvent on a besoin de le faire
          une seule fois, bien que certaines interfaces avancées
          demandent de le faire à la volée.
 
          On ajoute donc un comportement au changement de valeur
          de la liste déroulante. Ici on va utiliser une classe anonyme
          pour faire vite. Dans un programme propre, et comme expliqué
          dans le chapitre précédent, il vaudrait mieux créer un événement
          personnalisé dans un package "event" dédié.
        */ 
 
        this.spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
 
			@Override
			public void onItemSelected(AdapterView arg0, View arg1,
					int arg2, long arg3) {
 
				// On met ici ce qui arrive quand on change la valeur
				// du spinner. Dans notre cas, rafraichir les images
				// Comme c'est une innerclass, "this" pointe sur
				// OnItemSelectedListener et on doit utiliser la syntaxe
				// barbare vu dans un tuto précédent
				LifeCyleTutorial.this.refreshPicturesList();
 
			}
 
			@Override
			public void onNothingSelected(AdapterView arg0) {
				// OSEF dans notre cas
			}
 
        });
 
        // Pour finir, on lie l'événement au bouton "fermer"
        ((Button) findViewById(R.id.close_button)).setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// finish est une méthode d'Activity qui ferme
				// l'application
				LifeCyleTutorial.this.finish();
			}
		});
 
        // plus de mise à jour de l'interface ici
        // this.refreshPicturesList();
        // on mettra tout dans onResume() qui est appelé
        // à chaque fois
 
        // Bien montrer que l'on est dans onCreate()
        this.popUp("onCreate()");
 
    }
 
    // TOUTE CETTE PARTIE NE CHANGE PAS
 
    /**
     * Créer un Toast avec un message qui s'affiche immédiatement
     * pour une seconde.
     * On va utiliser ça pour bien visualiser les changements d'états.
     * Vous allez voir que les moments où on passe d'un état à l'autre
     * ne sont pas vraiment instinctifs : en effet votre application
     * se ferme et s'ouvre elle même en parallèle, et les messages
     * semblent mélangés.
     * Essayez de cliquer sur "home", "back", "téléphoner" ou une autre
     *  application pour voir les différents effets.
     *  Vous verrez alors l'interêt de bien remplir toutes les méthodes.
     */
    public void popUp(String message)
    {
    	Toast.makeText(this, message, 1).show();
    }
 
    /**
     * Retourne le nombre d'images à afficher d'après la sélection
     * dans la liste déroulante
     */
    public int getNumberOfPictures()
    {
    	return Integer.parseInt((String) this.spinner.getSelectedItem());
    }
 
    /**
     * Place la liste déroulante sur la valeur selectionnée.
     * Ici on utilise le fait que la valeur du nombre de photos
     * correspond presque à sa position dans le spinner.
     * Dans un vrai programme, on a généralement un système de mapping
     * plus complexe, mais ce n'est pas le but du tuto.
     */
    public void setNumberOfPictures(int number_of_pictures)
    {
    	// -1 car la numérotation du spinner commence à zéro
    	// mais notre array commence à 1
    	this.spinner.setSelection(number_of_pictures - 1);
    }
 
    /**
     * Met à jours la liste des images. On ne va pas créer et retirer
     * des vues à tout bout de champ. On va donc juste les cacher.
     * Bien sûr c'est un exemple académique et pas optimisé. On cherche
     * ici à comprendre comment ça marche, pas à recoder Google Map.
     */
	private void refreshPicturesList() {
 
		/* On récupère le nombre de vues.
		 on sait qu'il y en a 5, mais cette méthode est propre car
		 elle marche dans tous les cas. Si par exemple vous décidez
		 de créér un nombre de ImageView à la volée dans onCreate(),
		 cette méthode marche toujours. */
		int number_of_picture_views = this.pictures_list.getChildCount();
 
		// On récupère le nombre d'images qu'on veut afficher
		int picture_to_display = this.getNumberOfPictures();
 
		// On va utiliser une vue image, mieux vaut de l'initialiser hors de la boucle
		ImageView img;
 
		// On met autant d'images que voulu en visible
		for (int i=0; i < picture_to_display; i++ )
		{
			img = (ImageView) this.pictures_list.getChildAt(i);
			img.setVisibility(View.VISIBLE);
		}
 
		// Tout le reste est en invisible
		for (int i=picture_to_display; i < number_of_picture_views; i++ )
		{
			img = (ImageView) this.pictures_list.getChildAt(i);
			img.setVisibility(View.GONE);
		}
 
	}
 
	// A PARTIR DE LA ON MET LES METHODES DU CYCLE DE VIE
        // en général on les met en debut de classe après onCreate()
	// ici c'est pour bien vous montrer l'évolution
 
    @Override
    protected void onStop()
    {
    	// Ai-je préciser de toujours appeler super en premier ?
        super.onStop();
 
        /*
         * Dans onStop(), vous allez très probablement avoir besoin
         * de sauver des états de manière permanente. On peut
         * le faire automatiquement en utilisant le framework des préférences
         * d'Android. Il peut faire de nombreuses choses telles que générer
         * des menus automatiquement, mais nous l'utiliserons pour
         * un usage basique : sauver vos données.
         *
         * Android fournit SharedPreferences, qui agit comme un mapping entre des clés
         * (chaînes de caractères arbitraires de votre choix) et des valeurs.
         * Elles sont sauvegardées de manière permanente, même si vous éteignez votre
         * téléphone. C'est un peu comme si vous utilisiez une base de données
         * avec deux champ, un pour le nom, un pour la valeur. Si ce n'est que
         * tout est fait automatiquement pour vous. Si vous êtes un pythoniste,
         * cela vous rappelera le module shelve.
		 */
 
        // On créé des préférences partagées en leur donnant un nom et des permission
        SharedPreferences settings = getSharedPreferences("my_saved_pref", // on devrait faire une constante pour ça
        													Context.MODE_PRIVATE); // vous seuls pouvez lire les prefs
 
        // Pour modifier des préférence, il faut passer par un objet éditeur
        SharedPreferences.Editor editor = settings.edit();
 
        // On sauve ensuite les valeurs que l'on souhaite en précisant leur type
        // ici on sauve le nombre d'images
        editor.putInt("number_of_picture",  // ça aussi il faudrait le mettre en constante
        			  this.getNumberOfPictures());
 
        // On sauvegarde les modifications
        editor.commit();
 
        // Bien montrer que l'on est dans onStop()
        this.popUp("onStop()");
    }
 
    @Override
    public void onStart()
    {
        super.onStart();
        // Dans onStart(), on fait exactement l'opération inverse
        // de onStop()
 
        // On récupère les préférences partagées
        SharedPreferences settings = getSharedPreferences("my_saved_pref", // on réutilise le nom
        													Context.MODE_PRIVATE); // et le mode
 
        // Récupère l'info qu'on a sauvegardée.
        // Comme on a précisé le type à la sauvegarde
        // on peut récupérer une valeur typée directement
        this.setNumberOfPictures(settings.getInt("number_of_picture",
                								 1)); 
 
        /*
         * Vous aurez noté qu'il y a un deuxième argument. Tous les getters
         * des conteneurs dans Android possèdent cet argument. Il permet de
         * spécifier une valeur à retourner si la clé n'existe pas. C'est
         * TRÈS pratique car cela évite de tester l'existance de la valeur.
         * On peut ainsi récupérer une valeur par défaut si la clé n'a encore
         * jamais été utilisée. Ici, si jamais c'est la première fois que
         * l'on utilise le programme, on a pas encore de nombre de photos
         * enregistré. On retourne donc le nombre par défaut : 1.
         */
 
        // Bien montrer que l'on est dans onStart()
        this.popUp("onStart()");
    }
 
    @Override
    public void onSaveInstanceState(Bundle outState)
    {
    	/*
    	 * On va mettre ici la même chose dans onStop() mais en utilisant
    	 * une méthode différente.
    	 * onSaveInstanceState() est appelée uniquement si Android kill
    	 * brusquement l'application. Dans ce cas le système donne une chance
    	 * à l'application de sauvegarder son état dans un objet de type
    	 * Bundle. Vous aurez l'occasion de revoir Bundle car c'est un objet
    	 * utilisez massivement pour la communication entre processus
    	 * dans Android.
    	 *
    	 * Et oui, ça fait un peu doublon avec onStop(), mais c'est comme ça et puis
    	 * c'est tout.
    	 */
 
        super.onSaveInstanceState(outState);
 
        // Contrairement à l'usage de SharedPreference, l'objet qui va contenir
        // nos données est à usage unique et déjà fourni en paramètre
        // il n'y a donc plus qu'à sauvegarder, en utilisant la même syntaxe
 
        outState.putInt("number_of_picture",
        				this.getNumberOfPictures());
 
        // Bien montrer que l'on est dans onSaveInstanceState()
        this.popUp("onSaveInstanceState()");
    }
 
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState)
    {
    	/*
    	 * Dans onRestoreInstanceState(), on fait exactement l'inverse de l'opération
    	 * de onSaveInstanceState().
    	 */
        super.onRestoreInstanceState(savedInstanceState);
        this.setNumberOfPictures(savedInstanceState.getInt("number_of_picture",
        // Toujours utiliser la valeur par défaut même si elle est facultative !
        													1)); 
 
        // Bien montrer que l'on est dans onRestoreInstanceState()
        this.popUp("onRestoreInstanceState()");
    }
 
    @Override
    protected void onPause()
    {
        super.onPause();
        /* Normalement ici on met le traitement des connections.
         * Dans cette application simple, on a rien de tout cela donc
         * la méthode est vide.
         *
         * Généralement ça donne un code du style :
         *
         * this.db_helper.close();
         * this.sensor_listener.pause();
         */
 
        // Bien montrer que l'on est dans onPause()
        this.popUp("onPause()");
    }
 
    @Override
    protected void onResume()
    {
        super.onResume();
        /* Idem, on met l'inverse de onPause().
         * Dans cette application simple, on a pas
         * de connexion mais généralement ça donne un code du style :
         *
         * this.db_helper.open();
         * this.sensor_listener.resume();
         *
         * On place aussi ici une méthode qui rafraichit l'affichage.
         * En effet l'affichage est celui par défaut, dans onCreate().
         * Mais si on restore un état, il faut mettre à jour ce que
         * l'utilisateur voit pour qu'il se retrouve visuellement là où
         * il était. Sinon on fait tout ça pour rien :-)
         */
 
        this.refreshPicturesList();
 
        // Bien montrer que l'on est dans onResume()
        this.popUp("onResume()");
    }
 
}

Et c’est bon, si vous sortez de l’application, elle retourne à son état précédent.

Vers une application élaborée

Vous avez maintenant de quoi coder une application complète sous Android, professionnelle, digne de l’Android Market. Vous pouvez quitter ce site bac à sable, et marcher seul dans la cour des grands :-) Ou alors vous pouvez rester et apprendre comment faire encore mieux, avec par exemple, dans le prochain tuto, l’utilisation de la base de données.

19 Réponses à “Cycle de vie d’une application Android”

  1. Olivier Dit:

    Un grand merci pour ce dernier tutoriel (en attendant les prochains :-))
    J’ai juste deux petites remarques : je crois que le code du fichier arrays.xml n’est pas le bon et sur mon navigateur les symboles < (plus petit) apparaissent en code : <
    for (int i=0; i < picture_to_display; i++ )

    Encore merci pour les tutoriels,
    Olivier

  2. kevin Dit:

    En effet, merci de signaler ces coquilles.

  3. 2beta Dit:

    Hello, merci pour ces super tuto (les seuls en français de vraiment valables) et continue sur cette lignée …

  4. Frédéryck Dit:

    Juste excellent!
    J’ai lu quelques tutos sur Android en français et en anglais celui-ci est pour moi de loin le plus pédagogique. Les explications m’ont semblé claires et c’est avec grande hâte que j’attends le prochain ^^.

    Encore merci!

  5. Fred Dit:

    Bonjour,

    Merci pour cette liste de tutoriels et effectivement vivement les prochains (prévu pour quand …?).

    Vraiment juste pour signaler 2 minuscules coquilles :
    - Pour le fichier strings.xml il manque le ’s’ à string : je l’ai vu juste car c’est souligné pour le fichier xml arrays
    - Dans le code JAVA la popup affiche onStop() dans la méthode onStart() au lieu de onStart() évidemment …

    Voilà c’est parfait, merci encore

  6. kevin Dit:

    Encouragements et feedback toujours très appréciés :-) En tant que formateur pro, je peux facilement voir le résultat du cours en entreprise en discutant avec les participants. Sur le Web, je ne peux me fier qu’aux commentaires, alors c’est vraiment bienvenue

    Merci pour les coquilles. Sur des tutos aussi grands, il y en a forcément, et je peux uniquement les corriger grâce à vous.

    Au sujet de la fréquence des tutos, j’écris un article en moyenne toutes les unes ou deux semaines, mais ce n’est pas toujours sur Android, c’est plus une question d’humeur. Le but du jeu étant quand même de couvrir la création d’une application jusqu’à sa mise en ligne sur Android Market, il y a encore pas mal de publications à venir.

    @ +

  7. Asbog Dit:

    Salut.

    Je suis tes tutos pas à pas mais je ne crois pas avoir vu à quoi sert “@override”. Tu peux m’éclairer?
    merci

  8. kevin Dit:

    Hello Asbog,

    @Override est une notation Java, et n’est donc pas liée à Android en particulier. On l’utilise au dessus d’une méthode d’une classe enfant quand celle-ci surchage la méthode de son parent. C’est le principe même du polymorphisme.

    Si les lignes ci-dessus semblent sortir d’un dialogue Klingon pour toi, alors tu trouveras très intéressant ce tuto, et particulièrement ce chapitre. Connaitre Java est en effet indispensable avant de commencer à coder sur Android. Bien maîtriser ces notions te permettra d’avancer beaucoup plus vite, et tu te surprendras à écrire du code plus astucieux.

    A bientôt,

    Kevin

  9. Yann Dit:

    Merci beaucoup pour tout ces tutos !!
    Aurais-tu des liens de Tutos Android en Anglais ?! Je me noie dans la masse google, et surtout, je ne sais pas trop si les sources sont réellement sûre.
    Aurais tu par exemple, un site Anglais avec de super explications comme sur ton blog ?

  10. kevin Dit:

    Hello Yann,

    Généralement je travers le Web en fonction de mes besoins, sans m’attarder sur un site en particulier. Mais si tu cherche une ressource majeure, je dirais :

    - http://www.anddev.org/, un des tout premiers fora;
    - http://android.ifies.org/content/learning-android-source-code-examples, un nouveau né prometteur.

    Quand à moi, je m’y remet dès que j’ai moins de boulot.

    @+

  11. aliaz Dit:

    le tutoriel est bien realise,
    j’ai rarement fait marche une app du premier coup. tous mes encouragements en attendant le prochain

  12. Leimi Dit:

    Je viens de finir tout les tutos android et vraiment, merci pour tout ce travail !
    Tu expliques très bien les choses, merci beaucoup, vivement la suite :)

  13. Benji Dit:

    Salut tout le monde très intéressant ces tutos surement ceux du web fr les plus simples et compréhensibles pour les noobs comme moi bonne continuation j’attends les prochains avec impatience
    Benji

  14. Tom de Savoie Dit:

    Bonjour,

    Tout d’abord félicitation pour la réalisation de ce tutoriel très clair !

    Sinon, trois petites remarques :
    1. Dans la méthode popUp(), est-ce un oubli le show() ? (Il y a Toast.makeText(this, message, 1) et cela ne renvoit rien, alors que Toast.makeText(this, message, 1).show() renvoit bien les pop-up).
    2. Le @Override précédant les méthodes onItemSelected(), onNothingSelected(), et onClick() générent des erreurs. J’ai testé sans et cela marche.
    3. Dans le main.xml tu fait référence à @+id/picture_list alors que dans le LifeCycleTutorial.java tu fait référence à pictures_list.

    Merci pour ton travail, j’ai hâte de faire le prochain tuto.

    PS : C’est avec toi que j’ai appris Java, c’est quand que tu enseignes à SUPINFO ? :P

  15. kevin Dit:

    Bonjour Tom,

    1. Je relis l’article et je vois :

         public void popUp(String message)
        {
        	Toast.makeText(this, message, 1).show();
        }

    Donc le show me semble bien là mais je l’ai peut être corrigé depuis sans m’en rendre compte car je relis souvent mes articles. Ou tu parles peut être d’autres chose ?

    2. Le @override fonctionne ou non selon la version d’Android que tu as. Je crois qu’il est temps que j’installe la 2.1 pour mettre à jour les articles :-)

    3. Fais tu référence à :

     <LinearLayout 
    	    android:id="@+id/picture_list"
    	    android:orientation="vertical"
    	    android:layout_width="fill_parent"
    	    android:layout_height="wrap_content"
       >

    et

    this.pictures_list = (LinearLayout) findViewById(R.id.picture_list);

    ?

    Si oui, c’est normal, quelques tutos avant, tu pourras lire une explication plus détaillée sur l’usage des ID. Sinon, donne moi l’endroit que je corrige.

    Encore une fois il est possible que j’ai corrigé depuis les coquilles du tuto, auquel cas je suis désolé si elles t’ont géné dans l’apprentissage.

    Quand a enseigner à SUPINFO, sur le campus de Nice c’est tout à fait possible ! A Paris, ça demanderait quelques aménagements ;-) Je fais souvent des formations pour le privé, mais enseigner pour une école demande généralement du piston. Moralité, il faudrait que je me fasse introduire par un membre du personnel, comme je l’ai fais à Unice.

    Mais pour le moment je suis en mission humanitaire au Mali jusqu’en avril, donc la question ne se pose pas. Enseigner à Bamako donne une toute autre perspective de la pédagogie, et les élèves ne lancent pas discrètement WOW pendant les TD…

    @ bientôt sur évidence

  16. Chris Dit:

    Vraiment super tutos Android, MERCI mille fois ! je vais me lancer dans la réalisation d’un programme Android pour l’université. J’espère avoir acquis le nécéssaire pour celà… aurait tu quelques liens pour d’autre tutos ?
    Vivement la suite et merci encore :)

  17. kevin Dit:

    Bonjour chris,

    L’engouement autour d’Android est retombé mais Google continue d’avancer à un rythme effréné. Du coup, la majeure partie des tutoriaux sont obsolètes, surtout en français.

    Je vais prendre le temps de mettre e-vidence.net à jour, mais pour le reste, les meilleurs tutos sont pour l’instant la doc de Google officielle :-)

  18. VinS Dit:

    Bonjour,

    Très bonne explication.

    J’ai relevé une petite coquille. Paragraphe après onResume():
    ” Si Android tue votre application il appellera dans l’ordre onPause(), onStopt() et onDestroy().” Tu as écrit onStopt()

  19. kevin Dit:

    Merci pour la coquille :-) Corrigé.

Ça se comprend tout seul