Les activités¶
Le projet d’exemple pour ce chapitre
Vous pouvez télécharger le projet servant d’exemple pour ce chapitre :
android-demo-activite.zip
Une activité (activity) représente un écran dans une application Android. Nous avons vu au chapitre précédent que l’emploi des layouts permet de garantir un rendu adapté selon le type d’appareil et les dimensions de l’écran.
Pour développer des activités, il est aussi nécessaire de comprendre que l’environnement Android se distingue du développement d’applications de bureau ou d’applications Web. En effet, l’environnement Android est généralement plus limité en ressources qu’un ordinateur classique. Il est donc important de limiter au mieux l’usage de ces ressources (batterie, mémoire…). Pour cela, le système Android gère un cycle de vie des activités en privilégiant l’économie de ressources. Même si l’utilisateur peut avoir l’impression que l’écran qui lui est présenté existe aussi longtemps que l’application s’exécute, dans la réalité, l’activité associée peut être détruite et recréée plusieurs fois durant la vie de l’application. Par exemple, si l’utilisateur répond au téléphone alors qu’une activité s’exécute, cette dernière est suspendue par le système et, si nécessaire, le système peut décider de la supprimer pour économiser des ressources. Lorsque l’utilisateur voudra reprendre l’utilisation de l’application, l’activité sera intégralement recréée.
Le développement d’activités suppose de s’astreindre à tenir compte de ce cycle de vie assez particulier afin que les applications se comportent comme attendues quelle que soit la situation.
Le cycle de vie¶
Note
Reportez-vous à la documentation officielle :
Les activités sont des composants, c’est-à-dire qu’il n’est pas possible d’instancier
directement une activité dans le code d’une application (avec le mot-clè new
par exemple). Une activité est créée et gérée par le système. Un développeur doit
donc être soucieux de respecter la façon dont le système va gérer les activités
de l’application. Le système Android définit un cycle de vie d’une activité :
cette dernière passe par des états différents. Une activité peut être prévenue
du passage dans un des états suivants :
initialisée (initialized)
créée (created)
démarrée (started)
relancée (resumed)
mise en pause (paused)
arrêtée (stopped)
détruite (destroyed)
Chaque passage vers un nouvel état (hormis initialisée) est signalé par un appel à une méthode de callback : onCreate, onStart, onResume, onPause, onStop, onDestroy plus la méthode onRestart pour indiquer qu’une activité qui a été arrêtée est à nouveau démarrée. Pour intervenir à un moment du cycle de vie de l’activité, il suffit de redéfinir la méthode voulue. Attention, il faut penser à appeler la super implémentation de la méthode.
Pour les méthodes onCreate, onStart, onResume, il vaut mieux appeler la super implémentation avant son propre code :
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// mon code ici
}
Pour les méthodes onPause, onStop, onDestroy, il vaut mieux appeler la super implémentation après son propre code :
@Override
protected void onDestroy() {
// mon code ici
super.onDestroy();
}
Le schéma ci-dessous détaille les transitions possibles entre chaque état avec l’appel des méthodes de callback.

Le cycle de vie d’une activité (extrait de la documentation officielle)¶
Pour mieux comprendre ce schéma, prenons un exemple. Un utilisateur lance une application de consultation de mail qui doit afficher une activité pour présenter la liste des mails. Cette activité est créée par le système et ses méthodes onCreate, onStart et onResume sont appelées à la suite. Après cela, l’activité est affichée à l’écran. L’utilisateur clique sur un mail pour en consulter le détail. Cette action crée et affiche une nouvelle activité. L’activité de liste des mails est arrêtée (onPause puis onStop sont appelées) car elle n’est plus au premier plan. Puis l’utilisateur appuie sur le bouton Back qui a pour objectif de le ramener sur la liste des mails. L’activité doit être à nouveau affichée et ses méthodes onRestart, onStart, onResume sont appelées avant cela. Puis, l’utilisateur reçoit un coup de téléphone et répond. Cela amène l’application de téléphonie en premier plan. Du coup, l’activité de liste des mails est à nouveau arrêtée (onPause, onStop). Comme la communication téléphonique dure un certain temps, le système décide de libérer des ressources. Lorsque l’utilisateur raccroche et revient à l’application de mails, l’activité est entièrement recréée (onCreate, onStart, onResume). Enfin, l’utilisateur consulte la liste des applications sur son téléphone. Cela amène l’application en arrière-plan et suspend l’activité (onPause et onStop). L’utilisateur décide d’arrêter l’application de mail. L’activité est alors complètement détruite (onDestroy).
On voit avec l’exemple précédent que le cycle de vie d’une activité peut être très complexe à décrire. Cette complexité permet au développeur de gérer au mieux les ressources lorsqu’il implémente une activité. Pour chaque méthode de callback nous allons fournir quelques bonnes pratiques.
- onCreate
La redéfinition de cette méthode permet de réaliser les tâches d’initialisation les plus coûteuse (comme par exemple, créer le layout d’affichage de l’activité). C’est dans cette méthode que doivent être réalisés les traitements à exécuter une seule fois pour toute la vie de l’activité.
- onStart
La redéfinition de cette méthode permet de savoir quand une activité va être rendue visible à l’utilisateur. Vous pouvez, par exemple, faire des mises à jour de l’interface graphique avant affichage. Si votre activité joue une animation, cette méthode est une bonne candidate pour lancer automatiquement l’animation, soit parce que l’utilisateur démarre l’activité pour la première fois soit parce que l’activité a été arrêtée et que l’animation doit être continuée.
- onResume
La redéfinition de cette méthode permet de savoir quand une activité va être accessible (y compris quand elle sort d’une pause). C’est le bon moment pour votre activité pour déclencher des opérations consommatrices de ressources (activation de la caméra, ouverture de connexions réseau…)
- onPause
La redéfinition de cette méthode permet de savoir quand une activité n’est plus active pour l’utilisateur. C’est le moment de suspendre les opérations consommatrices de ressources (désactivation de la caméra, fermeture des connexions réseau…). Attention, une activité en pause peut toujours être visible de l’utilisateur. Vous ne devriez donc pas utiliser cette méthode pour modifier l’interface graphique ou stopper des animations.
- onStop
La redéfinition de cette méthode permet de savoir que l’activité va être stoppée et ne sera plus visible de l’utilisateur. Si votre activité a besoin de sauvegarder des informations (notamment sur disque) pour pouvoir redémarrer convenablement, vous pouvez effectuer ces traitements à ce moment-là. Vous pouvez utiliser cette redéfinition pour stopper les opérations graphiques (animations, lecteur de vidéo…).
- onDestroy
La redéfinition de cette méthode permet de savoir que l’activité va être détruite. Il s’agit de l’ultime étape et vous pouvez effectuer ici des traitements avant la suppression totale de l’activité. Attention, une activité peut être détruite par le système pour libérer temporairement des ressources. Cela ne signifie donc pas que l’activité a été simplement fermée par l’utilisateur.
Il existe une méthode de callback particulière :
- onRestart
La redéfinition de cette méthode permet de savoir qu’une activité va être à nouveau démarrée après avoir été stoppée. L’appel à cette méthode se situe entre onStop et onStart. Cela permet plus facilement de faire la différence entre le démarrage normal de l’activité et son redémarrage.
Terminer une activité¶
Vous pouvez terminer une activité en appelant sa méthode finish. L’activité
est alors détruite en passant par tous les états nécessaires. Si vous voulez
savoir si l’activité est détruite suite à un appel à finish, vous pouvez
tester le retour de la méthode isFinishing. Cette dernière méthode retourne
false
si l’activité est détruite à la demande du système pour libérer des
ressources.
Gestion de l’état d’une activité¶
Note
Reportez-vous à la documentation officielle :
Une des grandes difficultés lorsqu’on débute le développement d’activités est la gestion de leur état. En effet, comme nous l’avons vu à la section précédente, un objet activité peut être détruit et recréé plusieurs fois alors que l’utilisateur à l’impression à chaque fois de voir le même écran. Si une activité possède un état interne alors cet état doit être sauvé et restauré pour garantir une expérience utilisateur cohérente. Pour illustrer cette situation, prenons un exemple simple. Voici un exemple d’activité avec son layout :
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 | package dev.gayerie.premiereapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView message;
private int nbClics;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
message = findViewById(R.id.textMessage);
updateMessage();
}
public void onBoutonClic(View v) {
nbClics++;
updateMessage();
}
private void updateMessage() {
message.setText(String.format("Vous avez cliqué %d fois", nbClics));
}
}
|
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textMessage"
android:padding="15dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Clic"
android:onClick="onBoutonClic"
android:layout_gravity="center" />
</androidx.appcompat.widget.LinearLayoutCompat>
Cette application affiche un bouton et un message. À chaque fois que l’utilisateur
clique sur le bouton, l’attribut nbClics
est incrémenté et le message est
mis à jour.

L’application au démarrage¶

L’application après quelques clics¶
Essayez cet exemple. Si vous cliquez plusieurs fois sur le bouton, le libellé est mis
à jour. Mais si vous changez l’orientation de votre téléphone, vous aurez la surprise
de voir à nouveau le message de départ : « Vous avez cliqué 0 fois ». Il se produira la
même chose si vous changez d’application et que vous revenez ensuite sur celle-ci.
Que se passe-t-il ? Le système gère votre activité comme décrit plus haut.
Lorsque vous changez l’orientation de votre téléphone ou que vous revenez à
l’application après l’avoir mise en tâche de fond, l’activité est entièrement
recréée : il s’agit d’un nouvel objet Java. Donc l’attribut nbClics
vaut
à nouveau 0. Cet attribut constitue l’état de votre activité. Et à chaque fois
que le système décide de supprimer votre activité, cet état est perdu. Ce
comportement est rarement celui attendu par l’utilisateur. En tant que développeur,
vous devez donc gérer la sauvegarde et la restauration de l’état de votre
activité.
Pour cela, vous pouvez redéfinir les méthodes onSaveInstanceState et
onRestoreInstanceState. La méthode onSaveInstanceState est appelée lorsque
le système va détruire l’activité pour récupérer des ressources afin que celle-ci
ait l’opportunité de sauver son contexte. Elle prend en paramètre un objet de
type Bundle. Un bundle permet de sauvegarder des types simples (valeur booléenne,
entier, chaîne de caractères…). Nous pouvons surcharger cette méthode pour
placer la valeur de l’attribut nbClics
dans le bundle.
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt("nbClics", nbClics);
super.onSaveInstanceState(savedInstanceState);
}
Lorsque vous redéfinissez la méthode onSaveInstanceState, vous devez impérativement appeler sa super implémentation car l’activité a besoin de sauvegarder d’autres informations d’état.
Pour récupérer les informations à la recréation, nous pouvons redéfinir la méthode onRestoreInstanceState pour, cette fois, lire les informations depuis le bundle reçu en paramètre et mettre à jour le message :
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
nbClics = savedInstanceState.getInt("nbClics");
updateMessage();
}
Lorsque vous redéfinissez la méthode onRestoreInstanceState, vous devez impérativement appeler sa super implémentation car l’activité a besoin de restaurer d’autres informations d’état.
Après l’ajout de ces deux méthodes, vous pouvez relancer l’application et constater qu’elle fonctionne comme attendu même lorsqu’on modifie l’orientation du téléphone ou que l’application est passée temporairement en arrière-plan.
Plutôt que de redéfinir la méthode onRestoreInstanceState, nous pouvons utiliser
directement la méthode onCreate qui prend en paramètre le bundle. Ce paramètre
vaut null
lorque l’activité est créée la première fois.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
message = findViewById(R.id.textMessage);
if (savedInstanceState != null) {
nbClics = savedInstanceState.getInt("nbClics");
}
updateMessage();
}
Astuce
La gestion de l’état est parfois une partie complexe à gérer pour le développeur.
Pour nous aider, l’implémentation par défaut sauvegarde automatiquement les informations
d’état de chaque vue. Si votre activité affiche un formulaire avec des champs
de saisie, la valeur de ces champs est automatiquement sauvegardée et restaurée
si ces champs possèdent un id donné par l’attribut android:id
.
Il existe d’autres manières de conserver l’état d’une activité. L’utilisation d’un bundle est adaptée pour conserver des états simples et uniquement pour gérer les cas de recréation de votre activité pour des raisons d’économie de ressources. Si vous souhaitez disposer d’un état persistant lorsque l’utilisateur relance l’application, vous devez opter pour une solution basée sur des fichiers ou même une base de données. Le modèle par composant défini par Android JetPack propose également une approche Model/View pour gérer la sauvegarde d’état lorsqu’une application est stoppée par le système.
Notion d’intent¶
Note
Reportez-vous à la documentation officielle :
Un intent est un message qui est envoyé pour activer un composant Android (comme une activité). Un intent peut être émis par un composant de l’application vers un autre composant de l’application. Par exemple, une activité peut ainsi démarrer une nouvelle activité (comme nous le verrons au chapitre suivant). Mais un intent peut également servir à déclencher un composant d’une autre application. Le système Android gère ainsi une système de messagerie inter-applicative. Certains composants émettent des intents et d’autres demandent au système à être sollicités lors de l’envoi de ces intents. Cela permet un système à couplage faible et permet à une application de s’insérer facilement dans l’écosystème des applications.
Un intent peut contenir les informations suivantes :
- Nom du composant
Un intent peut désigner une application spécifiquement avec son nom de package ou même un composant précis de l’application (comme une activité). Dans ce cas, on dit que l’intent est explicite. Mais il est également possible d’utiliser un intent implicite, c’est-à-dire qu’il ne fournit pas de nom de composant. Dans ce cas, le système est responsable de trouver l’application la plus appropriée pour répondre. Par exemple, on peut émettre un intent pour envoyer un mail. Le système doit ouvrir l’application la plus adaptée pour cela. Si plusieurs applications sont éligibles, le système demande à l’utilisateur celle qu’il préfère utiliser.
- Action
L’action est un identifiant sous la forme d’une chaîne de caractères. Il existe des identifiants d’action prédéfinis par le système mais vous pouvez très bien créer les vôtres si nécessaire.
- Data
La data se présente sous la forme d’une URI ou d’un type MIME pour désigner l’objet de l’intent.
- Catégorie
La catégorie est un identifiant sous la forme d’une chaîne de caractères. Elle permet de préciser l’action.
- Extras
Les extras sont en fait des paramètres sous la forme clé/valeur. Par exemple pour un intent destiné à envoyer un mail, on peut ajouter une extrait précisant le sujet du mail.
- Flags
Les flags sont des méta-informations que l’on peut ajouter à l’intent pour des utilisations avancées.
Associer une activité à un intent¶
Lorsque l’on déclare une activité dans le fichier AndroidManifest.xml
,
il est possible de lui associer un filtre d’intent (intent-filter) pour
indiquer au système dans quels cas cette activité doit être déclenchée. Si
vous consultez le fichier AndroidManifest.xml
pour un projet créé
par Android Studio, vous verrez un fichier ressemblant à celui-ci :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.gayerie.premiereapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
|
À partir de la ligne 13, ce ficher manifeste déclare une activité en l’associant
au filtre d’intent avec l’action android.intent.action.MAIN
de la catégorie
android.intent.category.LAUNCHER
. Il s’agit de l’intent qui est envoyé à
l’application lorsque l’utilisateur lance l’application. Donc ce filtre permet
au système de savoir quelle activité créer au lancement de l’application.
Une (et une seule) activité devrait être associée à ce type d’intent pour permettre à l’application de pouvoir être exécutée directement par l’utilisateur en cliquant sur son icône. Mais il est également possible d’envisager que la même activité (ou une autre) puisse être lancée à partir d’une autre action.
Un exemple assez courant est la possibilité de lancer une activité lorsque le système reçoit la directive d’afficher le contenu d’une URI. Prenons le cas du QR Code suivant :

Un QR Code pour l’URI demo://test¶
Le QR Code ci-dessus est la représentation graphique de l’URI demo://test
.
Si vous utilisez une application de lecture de QR Code sur un système Android,
cette dernière va décoder le QR Code et va demander au système d’afficher l’URI
décodée demo://test
. Pour cela, elle va émettre un intent. Le système va
alors chercher une application capable de prendre en charge le schéma
demo
. Les schémas les plus courants comme http
, https
ou tel
sont déjà pris en charge par des applications installées par défaut. Mais nous
pouvons fournir une activité prenant spécifiquement en charge une URI de la forme
demo://
grâce à l’utilisation d’un filtre d’intent.
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 | <?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.gayerie.premiereapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".DemoActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="demo"/>
</intent-filter>
</activity>
</application>
</manifest>
|
L’activité déclarée dans le manifeste à partir de la ligne 19 est associée
à un filtre d’intent pour l’action android.intent.action.VIEW
et dont
les données correspondent au schéma demo
(ligne 24). Dit autrement, si
on demande au système d’afficher une URI commençant par demo://
, il
démarrera notre application pour lancer l’activité DemoActivity
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package dev.gayerie.premiereapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.net.Uri;
import android.os.Bundle;
import android.widget.TextView;
public class DemoActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView textView = new TextView(this);
Uri intentData = getIntent().getData();
String message = String.format("Vous avez ouvert l'activité à partir de l'adresse %s", intentData.toString());
textView.setText(message);
setContentView(textView);
}
}
|
Notez à la ligne 16 que l’activité peut accéder à l’intent qui a déclenché sa
création grâce à la méthode getIntent. On peut ainsi facilement récupérer
l’URI grâce à la propriété data
. Dans notre exemple, on se contente
de l’afficher à l’écran.
Note
De la même manière, vous pouvez associer une activité à l’hôte ou même au chemin d’une ressource dans l’URI. Cela permet, par exemple, de déclencher le lancement de votre application quand l’utilisateur clique sur un lien dans une page Web :
1 2 3 4 5 6 7 8 9 10 | <activity android:name=".OtherDemoActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https"
android:host="api.mondomaine.com"
android:path="/launch"/>
</intent-filter>
</activity>
|
L’activité déclarée ci-dessus est ouverte quand le système reçoit un message
lui demandant d’ouvrir l’URI https://api.mondomaine.com/launch
. Vous
pouvez vous contenter de placer un lien sur une page Web de votre site.
Émettre un intent implicite¶
Nous pouvons très facilement demander le lancement d’une activité en utilisant un intent. Android distingue les intents implicites et les intents explicites. Un intent implicite doit être résolu par le système, c’est-à-dire qu’il doit sélectionner l’application la plus appropriée pour réaliser l’action (si plusieurs applications sont éligibles, alors le système affiche une boite de dialogue à l’utilisateur pour qu’il choisisse l’application qu’il préfère lancer). A contrario, un intent explicite permet de désigner explicitement l’application, voire l’activité, que l’on souhaite exécuter. Un intent implicite permet très facilement à une application d’interagir avec les autres applications. En effet, le programme se contente de déclarer le type d’action qu’il souhaite voir réaliser et le système doit choisir l’application appropriée. Un intent explicite est généralement utilisé pour ouvrir des activités au sein de la même application et gérer les enchaînements entre les activités (comme nous le verrons au chapitre suivant).
L’émission d’un intent implicite se fait en trois étapes :
On crée l’intent pour déclarer l’action et les données
On vérifie qu’il existe au moins une application pour prendre en charge ce type d’intent
On demande au système de lancer l’activité capable de prendre en compte notre intent.
Ci-dessous un exemple d’émission d’un intent implicite pour déclencher un appel téléphonique :
1 2 3 4 5 6 7 8 9 10 | Uri numero = Uri.parse("tel:0601020304");
Intent intentAppel = new Intent(Intent.ACTION_DIAL, numero);
List<ResolveInfo> infos = getPackageManager().queryIntentActivities(intentAppel,
PackageManager.MATCH_DEFAULT_ONLY);
if (! infos.isEmpty()) {
startActivity(intentAppel);
} else {
Toast.makeText(this, "Impossible d'appeler", Toast.LENGTH_LONG).show();
}
|
Aux lignes 1 et 2, on crée une URI représentant le numéro de téléphone à appeler
avec le schéma tel:
et on crée l’intent pour une action DIAL
. La ligne
4 permet d’effectuer une requête pour savoir s’il existe une ou plusieurs
activités capables de prendre en charge l’intent. Pour notre code, il suffit
de nous assurer que cette liste n’est pas vide pour être sûr que l’intent
sera pris en charge. Puis, ligne 7, on émet l’intent grâce à la méthode
startActivity et le système se charge des opérations nécessaires.