Tutorial Chapitre #4

S7 Appro – Inf A3 EG - Applications concurrentes, mobiles et réparties en Java

Ce document est largement inspiré du tuto Dima Rodriguez. [Tutoriel Android sous Android Studio. École d'ingénieur](https://hal.archives-ouvertes.fr/cel-01241650/)., mis à jour pour prendre en compte l’évolution récente de l’interface d’Android Studio (Taille du téléchargement : environ 1GO. Place sur le DD : environ 1.8 GO).

Sur le site officiel des développeurs Android vous trouverez la documentation des classes, des tutoriels, ainsi que les lignes directrices pour préparer une distribution Google Play (mais ce n’est pas l’objet de ce cours).

Remarques :

  • Les figures données à titre d’exemple proviennent d’une distribution Android pour Mac. De légères modifications pour la distribution Windows peuvent être attendues.
  • La version d’Android Studio utilisée pour ce tuto est Android Studio Dolphin | 2021.3.1 (août 2022). Si la version que vous avez téléchargée est légèrement différente, il ne devrait pas y avoir de conséquences.

Configuration d’Android Studio

Lancez Android Studio. Dans la page de démarrage, sélectionnez le bouton « … » (mais aligné verticalement), et le menu SDK Manager. Cochez/décochez les cases de manière à obtenir les deux onglets similaires à la figure 1 (n’oubliez pas de cocher Show Package Detail, en bas de la fenêtre pour l’onglet SDK Platform).

Plateforme SDK

Le téléchargement des libraires et de l’émulateur peut prendre un temps très important et requiert plusieurs GO sur votre DD.


Première Application

Création d’un projet Hello World

  1. Sélectionnez New Project, et Empty activity. Renseignez les informations comme dans la figure ci-dessous.

Hello World first Android App

  1. Après avoir appuyé sur Finish, l’IDE construit votre projet. Laissez le temps au logiciel de faire les vérifications et les téléchargements nécessaires (plusieurs minutes). Si à la fin du processus, il y a des erreurs, alors un message apparaît dans une console. Cliquez sur les liens pour résoudre les conflits (généralement, il faut télécharger de nouvelles librairies). Vous devriez avoir un écran comparable à la fenêtre de la figure ci-dessous, qui montre que la compilation s’est finalement bien terminée.

OK!

Exécution de l’application sur un émulateur

Un émulateur permet de reproduire le comportement d’un appareil réel d’une façon virtuelle. L’utilisation d’un émulateur nous évite d’avoir à charger à chaque fois l’application dans un appareil pour la tester, et de la tester sur de très nombreux appareils. On pourra ainsi lancer l’application dans l’IDE et elle s’exécutera sur un appareil virtuel appelé Android Virtual Device (AVD) qui émule le comportement d’un téléphone, une tablette ou autre.

  • Allez dans le menu Run, et lancez Run App, vous verrez un message afficher No target device found. Pour remédier à cela,

  • Aller dans le menu Tool / Device Manager, puis bouton Create a Device, et sélectionner Phone / Nexus 6 (c’est le téléphone obligatoire sur lequel on travaillera tout le temps)

  • Vous pouvez alors lancer l’application avec le menu Run > Run App, aboutissant à quelque chose comme la figure ci-dessous.

Vue de l'émulateur Nexus 6

Truc : Pour lancer l’exécution sur l’émulateur, vous pouvez appuyer sur le bouton d’exécution (flèche verte dans la barre de menu principale d’Android Studio). Rassurez-vous, vous n’aurez pas à relancer l’émulateur à chaque fois que vous compilerez votre projet, laissez-le ouvert et à chaque fois nouvelle compilation, elle se rechargera dans l’émulateur en cours.

FIN DE L’INSTALL

Se repérer dans le projet

Interface _Android Studio_

Tout projet Android doit respecter une hiérarchie bien précise qui permettra au compilateur de retrouver les différents éléments et ressources lors de la génération de l’application. Cette hiérarchie favorise la modularité des applications Android.

A la création du projet, Android Studio crée automatiquement des dossiers pour contenir les fichiers de code Java, les fichiers XML, et les fichiers multimédias (image, son, vidéo). L’explorateur de projet vous permettra de naviguer dans ces dossiers. Les dossiers que nous utiliserons le plus sont : java et res. Le premier contient le code Java qui définit le comportement de l’application (situé dans le répertoire de votre projet sous app\src\main) et le second comporte des sous-dossiers (dans app\src\main\res) où sont stockés les ressources qui définissent l’interface de l’application (l’apparence).

Tout ce qui touche à l’interface utilisateur sera intégré dans les sous-dossiers de res, dont voici une brève description :

  • layout regroupe les fichiers XML qui définissent la disposition des composants sur l’écran. Il contient déjà, dès la création du projet, le layout de l’activité principale que nous avons créée (fichier activity_main.xml).
  • drawable contient tout élément qui peut être dessiné sur l’écran : images (png de préférence), formes, animations, transitions… Cinq dossiers drawable permettent aux développeurs de proposer des éléments graphiques pour tout genre d’appareil Android en fonction de sa résolution. En peuplant correctement ces dossiers, on peut ainsi créer des applications avec une interface qui s’adapte à chaque résolution d’écran avec un seul fichier .apk (apk : package compressé comprenant l’application).

    • ldpi (low-resolution dots per inch). Pour des images destinées à des écrans de basse résolution (120 dpi)
    • mdpi pour des écrans de moyenne résolution (160 dpi)
    • hdpi pour des écrans de haute résolution (240 dpi)
    • xhdpi pour des écrans ayant une extra haute résolution (320 dpi)
    • xxhdpi pour des écrans ayant une extra extra haute résolution (480 dpi).
  • mipmap contient les images de l’icône de votre applications sous différentes résolutions.

  • values contient les fichiers XML qui définissent des valeurs constantes (des chaînes de caractères, des dimensions, des couleurs, des styles…). Très utilisé pour réaliser des applications internationales (i.e. de différentes langues).

Le menu Gradel Scripts de l’explorateur : Android Studio utilise un système qu’on appelle Gradle pour compiler et générer les applications. Pour fonctionner le Gradle a besoin d’un script qui définit les règles de compilation et génération (configuration et dépendances). Android Studio crée ainsi un script Gradle pour chaque module (build.gradle (Module : HelloWorld.app)) du projet ainsi qu’un script pour le projet entier (build.gradle (Project : HelloWorld)). Dans le build.gradle de l’application, on définit, entre autres, la version du SDK utilisé pour la compilation, la version minimale du SDK nécessaire pour faire tourner l’application (rétro-compatibilité), l’identifiant de l’application (le nom du package)…

Vous trouverez également dans le dossier manifests du projet un fichier nommé AndroidManifest.xml. Ce fichier est obligatoire dans tout projet Android, et doit toujours porter ce nom. Ce fichier permet au système de reconnaître l’application. C’est à l’intérieure de celui-ci que l’on définit la ou les activités de l’application.

Modification de l’application

Pour l’instant notre application ne fait qu’afficher un message sur l’écran. Dans cette section, nous allons modifier l’interface pour y mettre un champ de saisie et un bouton.

Une interface utilisateur est en général constituée de ce qu’on appelle des ViewGroups qui contiennent des objets de type View ainsi que d’autres ViewGroups. Un View est un composant, tel un bouton ou un champ de texte, et les ViewGroups sont des conteneurs qui définissent une disposition des composants (Views) qui y sont placés. ViewGroup définit la classe de base des différents layouts.

La disposition de notre interface est définie dans le fichier activity_main.xml situé dans le dossier layout de res. Avec Android Studio, ouvrez ce fichier en mode texte (cf. ci-dessous).

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

La première balise que vous retrouverez est <ConstraintLayout> qui définit le type du conteneur qui compose l’interface. Elle impose la façon dont les composants seront disposés. Plusieurs types de conteneurs existent, les plus communs sont RelativeLayout, LinearLayout, TableLayout, GridView et ListView. L’utilisation d’un RelativeLayout, par exemple, implique que les composants seront placés selon des positions relatives les uns par rapport aux autres. Un LinearLayout implique une disposition linéaire verticale ou horizontale, un GridView permet la disposition des éléments selon une grille qui peut défiler…

A l’intérieur de la balise <ConstraintLayout>, vous verrez un ensemble d’attributs définis selon le format : Plateforme:caracteristique= ”valeur”. Par exemple le premier attribut xmlns:android précise où sont définis les balises Android utilisées dans ce fichier. La balise <TextView>, fille de la balise <ConstraintLayout>, définit un composant texte qui sera placé sur le layout. En effet, c’est sur ce composant là que l’on écrit le texte Hello World affiché par notre application. Cette chaîne de caractères est définie par l’attribut android:text.

Nous allons maintenant modifier le type du layout pour le transformer en LinearLayout. Nous rajouterons ensuite nos composants sur ce layout selon une disposition linéaire.

Dans le fichier activity_main.xml

  • Supprimez l’élément <TextView>.
  • Remplacez l’élément <android.support.constraint.ConstraintLayout> par <LinearLayout>.
  • Rajoutez l’attribut android:orientation et mettre sa valeur à horizontal.
  • Rajoutez un élément <EditText> dans le <LinearLayout> tel que nous obtenions finalement:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="horizontal"
    >

    <EditText
        android:id="@+id/chp_saisie"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:hint="@string/str_hint_saisie"
        android:inputType="textPersonName"
        android:text="@string/txt_saisie" />
</LinearLayout>

Nous avons ainsi placé un champ de saisie avec les attributs suivants :

  • android:id permet de donner un identifiant unique que l’on utilisera pour référencer cet objet à l’intérieur de notre code. Le symbole @ est nécessaire pour faire référence à un objet ressource à partir d’un fichier XML. id est le type de ressource et chp_saisie est le nom qu’on donne à notre ressource. Le symbole + est utilisé pour définir un ID pour la première fois. Il indique aux outils du SDK qu’il faudrait générer un ID pour référencer cet objet. Le symbole + ne doit être utilisé qu’une seule fois au moment où on déclare la ressource pour la première fois. Par la suite, si on veut faire référence à cet élément, à partir d’un XML, il suffira d’écrire @id/chp_saisie.

  • android:layout_width permet de spécifier la largeur de l’élément. "wrap_content" signifie que le View doit être aussi large que nécessaire pour s’adapter à la taille de son contenu. Si, en revanche, on précise "match_parent" comme on l’avait fait pour le LinearLayout, l’élément EditText occuperait alors toute la largeur de l’écran puisque sa largeur sera celle de son parent c.-à-d. le LinearLayout.

  • android:layout_height idem que pour le layout_width mais pour la hauteur.

  • android:layout_weight correspond au poids relatif de cet élément comparé aux éventuels autres éléments (cela sert à organiser les éléments dans le layout).

  • android:hint précise le texte par défaut à afficher dans le champ de saisie quand il est vide. Nous aurions pu préciser directement la chaîne de caractères ici, codée en dur, mais on préfère plutôt utiliser une ressource que l’on va définir dans res/values/strings.xml. Noter que l’utilisation de + ici n’est pas nécessaire parce qu’on fait référence à une ressource concrète (qu’on définira dans le fichier xml) et non pas à un identifiant que le SDK doit créer. D’une manière générale, privilégiez toujours l’utilisation des ressources strings plutôt que des chaînes de caractères codées en dur. Cela permet de regrouper tout le texte de votre interface dans un seul endroit pour simplifier la recherche et la mise à jour du texte. De plus ceci est indispensable pour que votre application puisse être multilingue. L’IDE vous affichera un avertissement en cas de non-respect de cette recommandation.

  • android:inputType Permet de préciser les caractères attendus dans le champs de saisie.

  • android:text Permet d’afficher un texte par défaut. C’est souvent utilisé pour donner une indication de ce qui attendu. Ici, nous avons utiliser une référence vers une ressource xml selon le principe qui vient d’être évoqué.

Le fichier strings.xml aura l’allure suivante:

<resources>
    <string name="app_name">HelloWorld</string>
    <string name="str_hint_saisie">hint text for saisie</string>
    <string name="txt_saisie">Give a name</string>
</resources>

Rajouter maintenant un bouton:

  • Dans le fichier strings.xml rajoutez une chaîne de caractères qui s’appelle btn_envoyer et qui vaut "Envoi".
  • Dans le fichier layout/activity_main.xml, rajoutez un élément <Button> tel que
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/btn_envoyer" />

Pour répondre à un appui sur le bouton, il suffit de définir un attribut android:onClick pour le bouton en lui donnant comme valeur le nom de la méthode qui devrait être appelée quand le bouton est appuyé, et d’implémenter cette méthode de réponse dans la classe principale de l’activité:

  • Dans le fichier xml du layout, rajoutez l’attribut android:onClick à l’élément bouton tel que :
<Button
    ...
    android:onClick="envoiMessage" />
  • Dans la classe Java principale MainActivity, rajoutez la méthode envoiMessage :
package com.example.helloworld;

import android.os.Bundle;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void envoiMessage(View view) {
        System.out.print("On passe ici après un appui sur le bouton ENVOI");
    }
}

Il faut absolument respecter cette signature de méthode afin que le système puisse l’associer au nom donné par android:onClick. L’argument view est fournit par le système et correspond à l’élément qui a généré l’événement (le bouton Envoi dans notre cas).

L’exécution doit donner l’allure suivante à l’application :

Etat de l'application

Avant d’aller plus loin dans le traitement, vous pouvez déjà tester si l’appel s’effectue correctement quand le bouton est appuyé. Pour cela,

  • Mettez un point d’arrêt (breakpoint dans le menu Run > Toggle breakpoint > line breakpoint) à l’intérieur de la méthode envoiMessage() et lancez l’application en mode Debug (icône correspondant à petit insecte).
  • Dans l’émulateur, après avoir donné votre nom, appuyez sur le bouton Envoi et vérifiez que le programme entre bien dans la méthode envoiMessage() (l’exécution du programme s’interrompant sur la ligne taguée d’un breakpoint).
  • Arrêtez le débogage et enlevez le point d’arrêt en cliquant dessus (ou en utilisant le même menu que ci-dessus).

Créer et lancer une seconde activité

Dans la suite, nous allons répondre à l’appui du bouton en lançant une deuxième activité qui affichera le texte qu’on a tapé dans le champ de saisie de l’activité principale.

Création d’une seconde activité

Dans la barre de menu d’Android Studio, allez dans le menu File > new et sélectionner Activity, puis Empty Activity, et donnez lui le nom de AffichMessage.

Une fois l’activité créée, Android Studio génère : - un fichier AffichMessage.java contenant le code la classe. - le fichiers xml correspondant au layout de la nouvelle activité. - un élément <activity> dans le fichier AndroidManifest.xml et affecte ses attributs avec les valeurs que nous avons précisées lors de la création de l’activité :

<activity
    android:name=".AffichMessage"
    android:exported="true" />

Précisons que l’activité parente est .MainActivity et que son label est définit dans la chaîne title_activity_affich_message (n’oubliez pas d’ajouter cette chaîne dans le fichier strings.xml):

<activity
    android:name=".AffichMessage"
    android:exported="true"
    android:parentActivityName=".MainActivity"
    android:label="@string/title_activity_affich_message" />

Lancement de l’activité

Pour faire communiquer les deux activés (l’activité principale et celle que nous venons de créer), il faut passer par un Intent. Ce dernier représente l’intention de faire quelque chose, et permet à l’activité principale de lancer l’activité d’affichage.

Dans la méthode envoiMessage() de la classe MainActivity :

  • Créez une intention
Intent intent = new Intent(this, AffichMessage.class);

sans oublier d’importer la classe (cela devrait se faire automatiquement mais à vous de vérifier) :

import android.content.Intent;

À la construction de l’objet intent, nous précisons deux arguments : le premier est un objet de type Context qui fait référence à l’application qui crée l’intention et le deuxième précise le nom (de la classe) de l’activité qui reçoit l’intention. Comme le Intent peut être utilisé pour faire communiquer deux applications, il ne suffit pas de préciser uniquement le nom de l’activité qui le crée mais il faut également définir l’application qui l’invoque.

  • Lancez l’activité à l’aide de la commande
startActivity(intent);

Compilez et exécutez l’application et appuyez sur le bouton Envoi. La nouvelle activité se lance : pour l’instant elle est vide. Notez que le bouton de retour est déjà fonctionnel et permet de remonter à l’activité principale. Ceci est dû au fait que nous l’avons indiqué comme activité parent au moment de la création de notre activité d’affichage.

Communication entre les activités

Envoyer un message

Si nous souhaitons que le texte tapé dans l’activité principale soit affiché dans l’activité d’affichage, il faut faire communiquer les deux activités de sorte à ce que la première envoie le texte à la deuxième. Ceci s’effectue en utilisant le même Intent qui a servi pour le lancement de l’activité. En effet une intention peut transporter un paquet de données.

Modifier la méthode envoiMessage() pour qu’elle contienne le code ci-dessous, sans oublier d’importer les classes nécessaires :

public void envoiMessage(View view) {
    Intent intent = new Intent(this,  AffichMessage.class);
    EditText editTexte = (EditText) findViewById(R.id.chp_saisie);
    String message = editTexte.getText().toString();
    intent.putExtra(MESSAGE_SUPP, message);
    startActivity(intent);
}

La méthode findViewById() permet de retrouver un objet de type View à partir de son identifiant. Ici elle renvoie l’objet correspondant à chp_saisie qu’on cast en EditText. La variable editText contient désormais l’objet champ de saisie que nous avions posé sur l’interface principale. Nous récupérons ensuite la chaîne de caractères que contient ce champ en appelant editText.getText().toString(). Cette chaîne est ensuite stockée dans la variable message qui est passée en paramètre à la méthode putExtra() de l’objet intent afin de charger l’intention avec ce message. Afin que l’activité d’affichage puisse identifier et récupérer les données supplémentaires transportées par l’intention, il faut définir une clé pour ces données moyennant une constante publique. Nous définissons donc la constante MESSAGE_SUPP dans la classe ActivitePrincipale:

...
import androidx.appcompat.app.AppCompatActivity;

public final static String MESSAGE_SUPP = "ecl.MESSAGE";

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
}

En général on définit ce genre de clé en utilisant le nom de notre package comme préfixe (ecl.MESSAGE). Ceci garantit l’unicité des clés dans le cas où notre application interagit avec d’autres.

Récupérer et afficher le message

Arrivés à ce point, nous avons fait en sorte à ce que l’activité principale envoie un message à l’activité d’affichage. Il nous reste maintenant à récupérer ce message dans l’activité AffichMessage. Pour cela il suffit de rajouter le code ci-dessous dans la méthode onCreate() de la classe MainActivity. Cette méthode est appelée à la création de l’activité :

public class MainActivity extends AppCompatActivity {

    public final static String MESSAGE_SUPP = "ecl.MESSAGE";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = getIntent();
        String message = intent.getStringExtra(MainActivity.MESSAGE_SUPP);
    }
    ...
}

Ensuite, pour afficher le message, nous allons créer un TextView et lui affecter le message puis le rajouter au layout activity_affich_message. Dans les sections précédentes nous avons appris à créer et rajouter des composants à partir du fichier xml, ici nous allons le faire directement dans le code. Voici le code complet de la méthode onCreate() de la classe AffichMessage:

public class AffichMessage extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_affich_message);

        Intent intent = getIntent();
        String message = intent.getStringExtra(MainActivity.MESSAGE_SUPP);

        TextView textView = new TextView(this);
        textView.setTextSize(40);
        textView.setText(message);

        setContentView(R.layout.activity_affich_message);
        ConstraintLayout monLayout = (ConstraintLayout) findViewById(R.id.affich_message_layout);
        monLayout.addView(textView, 0);
    }
}

Dans le fichier activity_affich_message.xml, rajoutez un identifiant pour le layout de la façon suivante :

    android:id="@+id/affich_message_layout"

Exécutez l’application, entrez un texte dans le champ de saisie et appuyez sur le bouton Envoi. Votre texte devrait apparaître sur l’écran suivant, ce qui conclut le tutorial.