Accueil / Articles PiApplications. / La plate-forme Android

Notre première application Android.

Pour illustrer les points clef vus jusqu'à présent et introduire la plate-forme de développement Android, nous allons réaliser une application dont l'objectif est de montrer sur une carte une adresse saisie.

La plate-forme à installer.

Les raisons qui ont poussé Google à choisir IntelliJ et Gradle comme plate-forme de développement en complément puis en remplacement du tandem Eclipse extension ADT ne sont pas très claires. On peut supposer qu'elles tiennent à la compacité d'IntelliJ vis-à-vis d'Eclipse et peut être aussi à l'ombre d'IBM sur Eclipse ou encore à la volonté de disposer à terme d'une plate-forme à deux vitesses : une gratuite pour les amateurs éclairés et une autre payante à l'instar d'IntelliJ soi-même...
L'avenir ne manquera pas de nous éclairer !

Dans ce qui suit, nous supposons le lecteur accoutumé à un IDE Java de type NetBeans (qui est gratuit, lui, alors qu'il est soutenu par Oracle). Venant du monde NetBeans, la prise en main d'IntelliJ ne pose pas vraiment de difficulté et en dehors des menus et raccourcis différents, un développeur averti trouvera rapidement ses marques. Android Studio n'est cependant pas tout à fait IntelliJ (la version "express" gratuite bien entendu). Pour développer une application Android, il faut accomplir à la main un certain nombre de tâches préalables qui disposent toutes de leur propre outil en ligne de commande. Pour que l'IDE donne le sentiment d'être "automatique", il faut un séquenceur de tâches intégré à l'outil (ce que faisait ADT pour Eclipse).

Le choix de Google aurait pu se porter sur de nombreux outils du monde Java comme ant, Maven, etc. Il a été retenu le dernier né de ces outils qui est à la fois le plus souple et le plus puissant : Gradle. Ce séquenceur accepte aussi bien des scripts en langage Java qu'en langage Groovy. Il est donc parfaitement adapté à l'environnement Java d'IntelliJ.

Nous n'irons pas plus loin dans les principes car cela nous conduirait rapidement au hors sujet.

Lorsque vous téléchargez Android Studio, vous avez deux options : le charger seul ou le charger avec le SDK Android. Le SDK est la pile des outils en mode ligne de commande nécessaires à la réalisation des applications Android. En sus de ces outils vous avez également la plate-forme de création et de lancement des AVD (Android Virtual Devices). Si vous chargez le SDK seul, vous aurez deux icônes sur le bureau en fin de chargement : une pour lancer la console de suivi en configuration du SDK (le SDK manager) et une autre pour la gestion des appareils émulés.

ATTENTION : le SDK Android "pèse" plusieurs giga-octets et nécessite donc des temps de téléchargements qui peuvent être longs. Ne téléchargez pas la version Android Studio avec le SDK si vous l'avez déjà installé. Cela créera une deuxième plate-forme !

Sous Windows, la plate-forme (le SDK) est créé sous C:\Users\%USER%\AppData\Local\Android ce qui évite d'avoir à gérer les problèmes de droits si vous n'êtes pas administrateur.

Un petit mot à ce stade sur les émulateurs. Ce sont en soi de véritables machines virtuelles (façon workstation de VMware) qui pèsent d'un certain poids (je dirai même d'un poids certain) sur les ressources de votre système. Ne soyez donc pas trop gourmands ou disposez d'une plate-forme de développement "musclée". Ceci dit, l'éventail des émulateurs disponibles permet de tester quasiment toutes les configurations basiques de smartphones et de tablettes. Il serait en effet bien trop onéreux d'acquérir chaque nouveau périphérique juste pour y tester des applications. Les émulateurs sont livrés pour deux types de processeur : x86 et ARM. Les versions x86 ne fonctionneront pas si votre machine n'est pas équipé d'un processeur Intel (en revanche pas de souci pour émuler des processeurs ARM). D'autre part la version x86 dispose d'un accélérateur qui n'a pas à ce jour d'équivalent pour AMD : HAXM. Heureux possesseurs de plates-formes AMD, inutile d'installer cet accélérateur, il n'y fonctionnera pas.

Si vous n'avez rien installé le plus simple est de charger la version intégrant le SDK. L'installateur chargera le SDK après l'installation d'Android Studio puis configurera automatiquement l'IDE pour utiliser ce SDK.

Mise en place de la structure de projet.

Une fois ces installations effectuées, lancez l'IDE. La fenêtre qui suit s'affiche :

Accueil d'Android Studio.

Lorsque vous créez un nouveau projet, l'IDE commence par afficher un formulaire de demande de renseignements :

Cliquez sur le bouton "Next" et un nouveau formulaire s'affiche. Ce formulaire a pour but de préciser le type d'appareils destinés à installer l'application et pour chacun d'eux la version Android supporté.

L'évolution continue de la plate-forme Android se traduit par une évolution de ses librairies et donc de son API de programmation. Ce pourrait être un véritable cauchemar sans l'assistance d'un outil. Un lien "Help me choose" permet d'avoir une statistique des plates-formes actuellement déployées selon les statistiques de Google. Ceci peut vous aider à cibler votre population. Nous vous recommandons toutefois de na pas viser systématiquement les versions les plus récente pour deux raisons :

  1. en cherchant la compatibilité vers les versions les plus anciennes on en apprend plus sur le système ;
  2. un utilisateur qui ne peut accéder à un de vos produits risque de s'en souvenir par la suite...

Dans notre cas, nous allons choisir un point moyen : l'API 15 (Android 4.0.3) pour smartphones et tablettes.

Comme notre projet est pédagogique, sur l'écran suivant nous choisissons "No activity" (projet vide). Ceci validé, vous constaterez qu'il faut quelques secondes avant que l'outil ne s'affiche.

Une fois ouvert, faites afficher les données du projets (onglet vertical sur le bord gauche). Voici ce que vous devriez observer.

Structure du projet.

Il s'agit d'une structure "logique". Si vous allez sur le répertoire racine que vous avez indiqué pour construire le projet, vous constaterez que la structure "physique" est différente.

Vous noterez également que la seule classe créée est la classe ApplicationTest. Cette classe permet de tester de façon unitaire les méthodes (intégration de la librairie JUnit). Ceci est d'autant plus pratique que le mise au point en utilisant un émulateur (AVD) est assez lente.

Enfin, si vous observez le manifeste de l'application, il est particulièrement "vide" :

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="piapplications.eu.gomap">
    <application android:allowBackup="true"
                 android:label="@string/app_name"
                 android:icon="@mipmap/ic_launcher"
                 android:supportsRtl="true"
                 android:theme="@style/AppTheme">

    </application>
</manifest>

Construction de l'application

Bien qu'elle produise un résultat qui peut paraître complexe, notre application est assez simple. Comme nous avons une saisie d'adresse à effectuer, il faut la doter d'une vue et donc de son contrôleur : l'activité. Nos allons nommer GoMapActivity cette activité.

Création d'une nouvelle activité.

Pour créer cette activité, cliquez droit sur le module "app" puis suivez la chaîne de menu décrite ci-dessus.

Dans le formulaire qui s'affiche, saisissez le nom de l'activité (GoMapActivity) puis laissez le nom proposé pour le descripteur de la vue (activity_go_map). Comme cette activité est également celle que doit lancer l'application, pensez à cocher la case "Launcher activity". Cela créera le filtre d'intention idoine dans le manifeste de l'application.

L'ajout de notre activité à fait au moins 3 choses :

  1. Elle a créé la classe GlobalMapActivity

    package piapplications.eu.gomap;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    
    public class GoMapActivity extends AppCompatActivity
    {
    
      @Override
      protected void onCreate(Bundle savedInstanceState)
      {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_go_map);
      }
    }
  2. Elle a créé le descripteur XML de "vue" activity_go_map.xml :

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        tools:context="piapplications.eu.gomap.GoMapActivity">
    </RelativeLayout>
  3. Elle a enrichi le manifeste de l'application :

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="piapplications.eu.gomap" >
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme" >
            <activity android:name=".GoMapActivity" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    </manifest>

2 remarques à ce niveaux :

  1. Le code source généré pour la classe contrôleur GoMapActivity comporte l'instruction setContentView(R.layout.activity_go_map); qui permet de lier le descripteur de "vue" au contrôleur et au passage d'interpréter le fichier activity_go_map.xml comme un ensemble de contrôles graphiques Android. Mais d'où vient la classe R ?
  2. le libellé de l'application est donné par le manifeste sous la forme @string/app_name. Pourtant si on lance l'application (clic droit sur la classe GoMapActivity puis "debug") en l'état, elle apparaît sous le titre @string/app_name -> "GoMap". Où et comment se fait la traduction ?

Ces deux questions sont relatives à un même concept : les ressources. Chaque ressource est exprimée dans la vue sous la forme d'un pseudo type mime préfixé par le symbole @. Le nom qui suit directement ce symbole indique la nature de la ressource : "mipmap" pour icône, "string" pour chaîne de caractères, "style" pour un style CSS, etc. La documentation Android, donne une liste exhaustive des types de ressources. La nom qui suit le symbole / est l'identifiant de la ressource. A chaque identifiant correspond une valeur entière.

La classe Context dont dérive toute activité possède des méthodes pour accéder aux ressources via leur identifiant. Par exemple la méthode getString(int i) retournera la chaîne d'identifiant 'i'.

Avant de voir comment un nom de ressource correspond à un identifiant, jetons un coups d'oeil à ces ressources. Elles sont toutes placées sous le noeud nommé "res" dans la structure de projet. A ce titre, notre descripteur de "vue" activity_go_map.xml est une ressource. Si nous ouvrons par exemple la ressource strings.xml sous res/values, nous observons que la chaîne "GoMap" est identifié par "app_name". C'est donc là que ce fait la traduction @string/app_name -> "GoMap".

Toutefois, les méthodes ne peuvent pas identifier les ressources par leur nom mais par une valeur entière. Où se trouve cette valeur entière ? C'est à ce niveau qu'Android Studio nous aide un peu. Chaque fois que nous créons une ressource, l'outil génère automatiquement une classe R qui affecte à chaque ressource une valeur entière puis qui fourni un ensemble de méthodes statiques et de constantes pour accéder à ces ressources. Par exemple R.string accède aux identifiants des ressources de type chaîne de caractères, R.layout accède aux ressource de type descripteur de vue, etc. Il suffit d'ajouter à ce suffixe le nom de la ressource pour obtenir sa valeur entière : R.string.app_name retourne la valeur entière de la ressource de type chaîne (strings.xml) app_name. Du coup, l'instruction setContentView(R.layout.activity_go_map); de notre classe activité prend tout son sens.

Construction de la vue.

L'étape suivante consiste à créer la vue de noter activité. Elle doit comporter un libellé invitant à saisir l'adresse, un champ de saisie et un bouton de validation. Pour cela, dans la structure de projet sous res/layout, double-cliquez sur activity_go_map.xml et assurez vous dans la fenêtre qui s'affiche que l'onglet "Design" au bas de la fenêtre est bien l'onglet actif.

Ajoutons un contrôle "Plain TextView" pour l'invite de saisie avec le texte "Veuillez saisir votre adresse :" Placez dessous un contrôle "Plain Text" pour le champ de saisie et affectez à l'attribut 'id' la valeur "tbxAddress". Enfin ajoutez un bouton intitulé "Soumettre". Faites que ce bouton puisse être cliqué.

Apparence du concepteur.

Voici maintenant l'allure du descripteur :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="piapplications.eu.gomap.GoMapActivity">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Veuillez saisir votre adresse :"
        android:id="@+id/textView"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"/>
    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/editText"
        android:layout_below="@+id/tbxText"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Soumettre"
        android:id="@+id/button"
        android:layout_below="@+id/editText"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:clickable="true"/>
</RelativeLayout>

Mise au point de la classe contrôleur.

Maintenant que nous avons notre vue, il est possible d'adjoindre le code au contrôleur. La première chose à faire est de créer un gestionnaire d'évènement de prototype public void onCallMap(View viw). Revenez momentanément sur la vue et ajoutez ce gestionnaire à l'attribut "onclick" du bouton de la vue.

Les pièces sont en place ! Que devons nous faire ? En fait, c'est assez simple, on ne va pas coder le positionnement cartographique nous même. Nous allons créer une "intention" en demandant à Android de trouver une vue capable de présenter la carte avec un point placé sur notre adresse.

Toutefois avant de faire cela, nous devons lier le contrôle de saisie de l'adresse à notre contrôleur de façon à pourvoir lire la saisie de l'utilisateur. Cela se fait dans la méthode onCreate après l'initialisation du contrôleur :

_tbxAddress = (EditText)findViewById(R.id.tbxAddress);

Voici le code complet de la classe contrôleur :

package piapplications.eu.gomap;

import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;

public class GoMapActivity extends AppCompatActivity
{
  public static String TAG = "GOMAP";
  EditText _tbxAddress;

  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_go_map);
    _tbxAddress = (EditText)findViewById(R.id.tbxAddress);
  }

  public void onCallMap(View viw)
  {
    try
    {
      String sAddress = _tbxAddress.getText().toString();
      sAddress = sAddress.replace(' ', '+');
      Intent innGeoLocation = new Intent(android.content.Intent.ACTION_VIEW,
          Uri.parse("geo:0,0?q=" + sAddress));
      startActivity(innGeoLocation);
    }
    catch (Exception exc)
    {
      Log.e(TAG, String.format("Erreur (%s) : %s",
          exc.getClass().getName(), exc.getLocalizedMessage()));
    }
  }
}

Voilà une application complexe qui ne nous aura pas coûté grand chose grâce aux "intentions". Notez qu'elle ne fonctionnera probablement pas sur une machine émulée car l'intention n'aura personne capable d'y répondre.

Pour les développeurs habitués à log4j ou à tracer leurs applications sur console, cela n'est plus possible avec Android. En revanche, il existe un moniteur qui reçoit les traces émis par les méthodes de la classe Log. Ces méthodes prennent en premier paramètre une "étiquette" sur laquelle il est possible d'appliquer un filtre dans le moniteur de l'IDE. Sur une machine émulée, vous y lirez certainement :

E/GOMAP: Erreur (android.content.ActivityNotFoundException) : No Activity found to handle Intent \
{ act=android.intent.action.VIEW dat=geo:0,0?q=2,+rue+jules+ferry+paris }

(c) PiApplications 2016