Avant de commencer cette première partie, il me semble utile d’expliquer en quoi va consister cette série de tutoriaux sur le développement Android. Ce projet vient d’une envie de partager ma passion du développement sur Android et d’une constatation évidente : les tutoriaux en français traitant de la programmation sur la plateforme Android ne sont pas légion. Possédant déjà un blog personnel sur Android, il me semblait intéressant d’élargir l’audience de ce genre de tutoriaux. C’est donc grâce à PointGPhone et son créateur Loïc Kempf (que je remercie) que j’espère enroler motiver le plus possible de développeurs débutants.

La programmation sur Android commence tout d’abord par l’installation du SDK Android fourni par Google sur le site des officiels des développeurs. La rédaction d’une suite de tutoriaux sur le développement sur Android devrait tout naturellement contenir une partie introductive expliquant cette étape. Néanmoins, je n’ai pas souhaité inclure cette partie car elle est facilement réalisable et la documentation déjà existante est déja suffisante :

Comme le mentionne le titre de cette partie, l’objectif de ce tutorial est de se familiariser avec l’arborescence type d’un projet Android. Nous étudierons donc les différents packages et dossiers qui sont automatiquement créé par le plugin ADT (Android Development Tool). Pour ce faire, crééons tout d’abord un premier projet avec l’EDI Eclipse. Lorsque vous faites File>New>Other>Android Project, une boite de dialogue comme représentée ci-dessous s’ouvre :

Cette boite de dialogue demande en réalité d’entrer plusieurs informations sur le projet :

  • Project Name : Ce nom de projet est totalement indépendant de votre application Android. Ce n’est en fait que le nom donné à votre projet dans Eclipse (nom donné au dossier dans votre workspace).
  • Package Name : Nom du package principal de l’application. Le nom du package suit, en général, la règle suivante : extension_de_votre_domaine.nom_du_domaine.android.nom_du_projet (par exemple : com.cyrilmottier.android.tutorial). Le package définira en fait le nom interne de votre application sur le terminal Android.
  • Activity Name : C’est le nom de l’activité qui débutera au lancement de votre application. Dans une future partie, j’expliquerai le concept d’activité mais pour l’instant nous pouvons simplement considérer qu’une activité est la couche de présentation de votre application (concept d’écran). Ce nom d’activité sera automatiquement ajouté à votre AndroidManifest.xml (fichier qui décrit votre application au système Android et qui sera expliqué plus en détail dans la suite de ce tutorial)
  • Application Name : Pour finir vous devez entrer le nom de votre application. Cette chaine de caractère sera également inscrite dans AndroidManifest.xml et sera en fait utilisée par le système lors de l’affichage des applications dans l’écran d’accueil. C’est en réalité le nom de l’application affichée sous l’icône de l’application (nom utilisateur).

Note : Il n’y a aucune régle régissant et reliant les différents noms donnés ci-dessus. Vous pouvez en effet avec un package com.cyrilmottier.android.aviation, un nom d’Activity MonChien et un nom d’application “Cuisinez !”. On remarquera néanmoins, que dans l’exemple précédent, malgré un choix fantaisique poussé à l’extrême des noms, la compréhension du projet n’est absolument pas facilitée.

Après avoir validé la boite de dialogue précédente en cliquant sur “Finish”, nous nous retrouvons maintenant en présence d’un projet type Android. Ce projet suit une arborescence bien précise qui ressemble à celle représentée ci-dessous :

Comme à l’accoutumée, Android ne déroge pas à la régle et insère donc les différents packages dans le dossier src. C’est dans ce dossier que l’ensemble des fichiers de code (.java) doivent se trouver. Il est bien sûr possible de créer de nouveaux packages comme les développeurs Java ont l’habitude de le faire. Analysons maintenant le contenu du package com.cyrilmottier.android.tutorial. On y retrouve tout d’abord un fichier PartOne.java qui n’est autre que l’activité (Activity) principale de l’application. Un simple coup d’oeil au code contenu dans le fichier montre que PartOne hérite bien d’Activity :

package com.cyrilmottier.android.tutorial;
 
import android.app.Activity;
import android.os.Bundle;
 
public class PartOne extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

En dessous du dossier src, on retrouve un fichier android.jar. C’est tout simplement ce jar qui contient l’ensemble du framework Android. Sans ce dernier, notre projet Android serait tout simplement une coquille vide.

On retrouve ensuite dans le package par défaut le fameux R.java. Ce fichier est automatiquement géré par ADT et n’est donc pas modifiable par l’utilisateur. Ce fichier contient en fait l’ensemble des références vers les ressources de votre projet. Imaginons par exemple que nous souhaitions utiliser une image (Drawable dans le langage Android), on y accèdera par R.drawable.nom_de_l_image.

Au même niveau que le dossier src, on retrouve un dossier nommé assets et qui contient simplement des données qui seront chargées sur le mobile lors de la compilation de votre projet. Les données inclues dans ce dossier ne sont normalement pas en accord avec l’arborescence classique d’un projet Android : cela peut, par exemple, être des fichiers texte décrivant la license de votre application, des fichiers audio ou vidéo, etc.

Un projet Android contient enfin un dossier res qui regroupe l’ensemble des ressources relatives au projet. C’est ce dossier qui est “lu” par ADT pour créer le fameux R.java. Ce dossier contient lui même des sous dossiers regroupant les ressources selon leur type. A la création d’un projet Android, les sous-dossiers créés sont :

  • drawable : regroupera l’ensemble de vos images (png, jpg, gif), Drawables, etc. On pourrait traduire ce dossier par l’expression (pas vraiment française) “dessinable”.
  • layout : le framework Android a l’avantage d’offrir une technique de création d’interfaces graphiques à l’aide de fichiers XML (on étudiera dans de futures parties que le concept a même été porté à l’extrême et qu’un grand nombre de classes peuvent être instanciées par XML) . C’est dans ce dossier que vous incluerez l’ensemble des fichiers décrivant vos interfaces.
  • values : ce dossier contient également un ensemble de fichiers décrivant les valeurs utilisées par votre application. On peut, par exemple, y mettre des chaines de caractères (strings.xml), des tableaux (arrays.xml), des couleurs, des dimensions, etc…

Pour finir, tout projet Android contient un fichier nommé AndroidManifest.xml qui définit le comportement de votre application au système Android. Ce fichier définit par exemple, le nom, l’icone (par défaut drawable/icon.png), le thème, la version minimale du système nécessaire à l’exécution de l’application, les activités, les services, etc. de votre application.

Comme vous devez l’avoir compris, Android est une plateforme orientée sur la mobilité. Pour ce faire, elle dispose de nombreuses API facilitant les échanges réseau et aidant à la gestion des différents type de données reçus à l’aide des différentes interfaces disponibles (Internet, SMS, MMS, etc.).

Cette faculté confère à Android une méthodologie de développement assez particulière et offre des possibilités intéressantes. Il devient en effet facile de créer des applications disposant de deux parties distinctes : la partie serveur (PHP, J2EE, etc.) et la partie cliente (en Java sur Android). Dans ces cas le développement de l’application Android se déroule généralement en deux phases : l’écriture d’un serveur et celle du client.

Alors que la première phase se passe très bien, les programmeurs sont souvent confrontés à un problème lors de la réalisation de la seconde : l’accès au serveur applicatif. En effet, bien souvent, les développeurs utilisent un serveur local (appelé localhost et disponible à l’adresse 127.0.0.1). Cette adresse facilement accessible par un navigateur sur la machine de développement n’est “bizarrement” pas atteignable lorsqu’on tente de l’atteindre depuis l’émulateur.

Pourquoi une telle impossiblité? Replaçons nous tout simplement dans le contexte de l’émulateur fourni avec le SDK Android. Ce dernier est en fait un système Linux émulé par QEmu (un émulateur s’occupe en fait de simplement traduire les instructions ARM - type des instructions de l’image système fournie avec le SDK - en instructions compréhensibles par votre système de développement - généralement Intel 8086). Le problème vient tout simplement de cette particularité qui fait que l’émulateur Android fait tourner un second système qui dispose de sa propre adresse locale (localhost).

Cette situation explique pourquoi il est impossible d’atteindre son localhost de développement. Taper 127.0.0.1 sur le browser Android revient en fait à chercher un serveur local à l’émulateur (qui est normalement absent). Vous allez alors me demander pourquoi les autres adresses fonctionnent … et bien cela vient du fait que l’émulateur créé un pont entre votre connexion Internet et lui-même vous laissant croire que tout est possible :)…

Heureusement, Google a, comme à son habitude, pensé à tout : il existe une adresse IP “magique” qui permet de contourner ce problème de double localhost : la 10.0.2.2. Lorsque vous accéderez à 10.0.2.2 depuis l’émulateur, vous accéderez en fait à votre adresse de boucle locale (la localhost de votre machine de développement). Le problème est donc surmonté et il ne reste maintenant devant vous que des possibilités illimitées. A vous de jouer !

Lors de la création d’applications, les développeurs se trouvent confrontés à des problèmes graphiques similaires. Prenons l’exemple d’une application dont le menu contient un sous-menu affichant une icône ronde incrusté d’un ‘i’ (signifiant ‘info’) qui, si il est cliqué, ouvre une fenêtre affichant l’aide de l’application en cours. Cette icône, après avoir été créée une fois par le développeur pourra être utilisée dans ses futures applications pour agrémenter le sous-menu affichant l’aide.

Une telle démarche comporte néanmoins quelques désavantages :

  • Les développeurs ne sont souvent pas des graphistes confirmés, créer une icône relève donc de compétences qu’il n’a pas.
  • Le développeur peut également utiliser une icône trouvée sur Internet mais il devra alors s’informer des licenses sur l’image et s’assurer que son incorporation dans un programme tiers na va pas l’encontre des règles de l’image en question (droit d’auteurs par exemple).
  • Trouver ou créer des icônes prend du temps. Le temps perdu à effectuer cette démarche aurait probablement été plus utile à optimiser l’application ou à y ajouter de nouvelles fonctionnalités.

  • Les utilisateurs ne retrouvent pas la même icône pour l’ensemble des applications disponibles sur leur appareil Android. La cohérence est par contre maintenue entre applications d’un même développeur si ce dernier réutilise toujours la même icône.
  • Le style graphique des icônes utilisées n’est pas forcément en accord avec le style d’Android.

Créer un environnement cohérent graphiquement est un problème sur lequel Apple et son iPhone mettent un point d’honneur à résoudre au mieux. Je ne cacherai pas que la firme de Cupertino s’en sort avec brio ! Le SDK iPhone fournit, de base, un look ‘n feel similaire aux applications nativess (Maps, Settings, etc.).

Android, pour sa part, ne déroge pas à la règle. A l’instar de l’iPhone OS, il est possible d’utiliser le même style graphique et les mêmes images que celles utilisées par les applications natives d’Android. La seule différence (notable) réside dans le fait qu’Android facilite grandement le développeur souhaitant déroger à la charte graphique d’Android.

Le SDK Android, en plus de contenir fournir l’ensemble des frameworks de localisation, graphiques, de persistance de données, etc. contient donc un fichier d’accès aux ressources du système (R.java). Ce dernier est accessible dans le package android en tapant android.R. Comme à l’accoutumée, le fichier R.java suit les conventions de développement sur Android. On retrouve ainsi dans ce package :

  • L’ensemble des images utilisées par le système (icônes de menu, nine patchs) dans android.R.drawable
  • Des couleurs utilisées par le système ainsi qu’un (faible) ensemble de couleurs classiques dans android.R.color
  • Des layouts récurrents (trés utiles dans la création de ListView ou de Spinner par exemple: android.R.color
  • Des animations, des chaines de caractères, etc.

L’accès aux ressources du système peut également s’effectuer directement dans vos layouts XML. C’est d’ailleurs de cette façon qu’on utilise les thèmes prédéfinis. L’utilisation de ressources Android doit se faire en ajoutant simplement android: devant votre ressources. Imaginez que vous souhaitez créer une ImageView affichant ce fameux cercle incrusté d’un ‘i’ :

<ImageView
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:src="@android:drawable/ic_menu_info_details" />

Les ressources incluses dans Android sont pléthores et il me parait donc impossible d’en donner une liste exhaustive ici. Il est nénamoins possible d’accéder aux ressources en téléchargeant le code source d’Android ou en utilisant le service GitWeb du projet Android.

Malgré les incroyables et infinies possibilités de configuration d’Android, n’oubliez pas qu’un utiliseur lambda préfère ne pas être trop “chamboulé” dans ses habitudes. Lui permettre d’accéder à une nouvelle application ne dérogeant pas à la charte graphique du système ne fait qu’accélérer son temps de prise en main en réduisant significativement son temps d’adaptation. Un utilisateur trop “choqué” graphiquement pourra également totalement supprimer votre application sans même prendre le temps d’en analyser les possibilités et performances (aussi bonnes soient elles). Faites donc très attention à ce point lors de la réalisation de vos applications et n’hésitez pas à utiliser les ressources d’Android.

Comme chacun le sait, la méthode de débuggage probablement la plus commune (quelque soit le langage), est d’utiliser des primitives d’affichage sur la sortie standard. Qui ne se rappelle pas des fameux printf du langage C ou des NSLog de l’Objective-C? Le langage Java offre également la même possibilité grâce à des méthodes de type print du package System.out.

Le développement sur Android ne déroge pas à la règle puisque le SDK fournit une classe nommée android.util.Log qui inclue un bon nombre de méthodes d’aide au développement données dans la liste ci-dessous :

  • Log.v : Affiche le message en mode “verbose” c’est à dire “verbeux” ou “abondant” en français
  • Log.d : Affiche le message en mode “debug”, utilisé pour le débuggage
  • Log.e : Affiche le message en mode “error” (erreur)
  • Log.w : Affiche le message en mode “warning”, c’est à dire les avertissements
  • Log.i : Affiche le message en mode “info”

L’utilisation de ces méthodes n’est pas à prendre à la légère. En effet, l’utilisation abusive de ces méthodes engendre un ralentissement du temps d’exécution de votre application. De plus, il faut bien prendre en compte les différences entre ces méthodes. Les méthodes v et d ne doivent être utilisées que lors du développement et ne surtout pas être présentes lors de la diffusion de votre programme (sur l’Android Market par exemple). Les méthodes e, w et i peuvent être présente dans une application en “release” mais de façon “limitée” afin de ne pas ralentir inutilement l’application. En conclusion, malgré leur efficacité redoutable (en comparaison d’un débogueur conventionnel), ces méthodes sont à utiliser avec parcimonie et de façon intelligente.

Lorsque vous utilisez cette méthode commune de débuggage, vous êtes confronté à un problème. En effet, malgré l’étonnante efficacité de ce type de débuggage, lorsque vous souhaiter passer votre application en “release” (c’est à dire que vous êtes sur le point de la diffuser), vous devez supprimer l’ensemble des méthodes Log.x. Malheureusement, seule une recherche manuelle suivie d’une suppression des lignes contenant les Log.x est possible. Cette démarche est longue et sujette à erreur. Je vais donc vous présenter quelques astuces pour contourner le problème.

Une astuce pour supprimer facilement les Log.x

Lorsque vous souhaitez supprimer tous les appels à ces méthodes d’affichage, il peut être assez pénible de rechercher toutes les occurrences à Log.x. Une technique consiste à utiliser le modèle suivant :

package com.cmottier.android;
 
import android.util.Log;
 
public class MyClass {
 
	// Le tag affiché pour les logs de la classe
	private static final String LOG_TAG = "MyClass";
	// true si on souhaite afficher les logs de cette classe sinon false
	private static final boolean LOGV = true;
	// Config.LOGV est une variable publique statique finale relative au
	// projet global. Ainsi si cette variable est à true, l'ensemble
	// des logs du projets sont affichés.
	private static final boolean isLogged = LOGV || Config.LOGV;
 
	public MyClass() {
		log("Constructeur");
	}
 
	private void log(String log) {
		if (isLogged) {
			Log.d(LOG_TAG, log);
		}
	}
}

L’utilisation de cette astuce consiste en réalité à “exporter” l’affichage des logs dans une méthode externe : log. Cette méthode n’affiche les logs que si au moins une des deux variables LOGV ou Config.LOGV est à true. L’astuce semble pourtant n’apporter qu’un très faible avantage : celui de ne pas avoir à réécrire Log.d(LOG_TAG,. Eh bien en fait ce n’est pas tout ! Si on utilise un décompilateur sur le bytecode Java, on se rend compte que le compilateur supprime purement et simplement le bloc conditionnel if (isLogged) { ... } si la variable isLogged est à false. Cela provient du fait que la variable isLogged est déclarée en static final (constante). Le compilateur “comprend” donc que le bloc conditionnel est inutile et “transforme” alors la méthode log(String log) en une méthode vide.

Pour conclure, cette astuce se résume de la façon suivante :

  • Si isLogged est à true (c’est à dire qu’une des deux variables LOGV ou Config.LOGV est à true) alors le compilateur génère la méthode log(String log) de la façon donnée ci-dessous. Cela revient donc à afficher les logs par une indirection (appel à log au lieu de Log.x directement).
private void log(String log) {
	if (isLogged) {
		Log.d(LOG_TAG, log);
 	}
}
  • Si isLogged est à false, le compilateur “vide” totalement la méthode log(String log). Votre programme fait alors des appels à une méthode vide. Le temps perdu pour ces appels inutiles est négligeable en comparaison du lancement d’une Activity vide par exemple - ratio de l’ordre 15 pour 3 000 000.
private void log(String log) {
}

C’est pratique mais attention !

Cette astuce comporte néanmoins une contrepartie. En effet, elle n’empêche pas la création d’objets même si ces derniers sont inutiles. Prenons l’exemple suivant en supposant que isLogged est à false (et donc que la méthode log(String log est vide) :

public void myMethod(int i) {
	log("index = " + i);
}

Si on compile puis décompile ce code, on s’aperçoit que le code résultant est le suivant :

public void myMethod(int i) {
	log((new StringBuilder("index = ")).append(i).toString());
}

Le code généré créé donc un nouvel objet StringBuilder auquel on ajoute l’entier i. Le compilateur n’a pas été assez intelligent pour “deviner” que cette portion de code n’était pas nécessaire et instancie alors des objets inutiles. Cette création d’objets s’accompagne alors d’un possible passage du ramasse-miettes et d’un ralentissement hypothétique de votre programme.

Conclusion

La méthode “ultime” pour éviter de devoir supprimer les Log.x serait d’inclure le contenu de la méthode log(String log) dans votre code. Le compilateur supprimera alors purement et simplement les bloc conditionnel si isLogged est à false. De cette façon, il n’y a aucune instanciation de StringBuilder. Cela implique que votre code compilé est parfaitement sain mais votre code source un peu plus lourd :

public void myMethod(int i) {
	if (isLogged) {
		Log.d(LOG_TAG, "index = " + i);
	}
}

Les astuces que je viens de vous donner ont leurs avantages et leurs désavantages. Je pense que les éléments donnés ici vous permettront de mieux appréhender ce que fait le compilateur et d’en déduire la méthode la plus appropriée dans votre cas. Bon débuggage ! ;).

Bonjour tout le monde ! Me revoilà après un petit moment d’absence sur ce blog. Ne vous inquiétez surtout pas je n’ai pas encore l’intention de laisser tomber la rédaction d’articles sur ce blog. Disons simplement que la préparation d’un évènement sportif m’a prise tout mon temps libre durant la semaine.

Cet article fait suite à l’astuce sur la prise de copie d’écran et traite d’une astuce permettant de ne pas redémarrer Eclipse et/ou l’émulateur lorsque la connexion entre Eclipse et l’émulateur semble rompue. Il arrive en effet qu’on assiste, lors de ses développement à un affichage console ressemblant à ceci :

 
[2009-03-04 14:13:10 - adb]
[2009-03-04 14:13:10 - adb]This application has requested the Runtime to terminate it in an unusual way.
[2009-03-04 14:13:10 - adb]Please contact the application's support team for more information.
[2009-03-04 14:13:10 - DeviceMonitor]Adb connection Error:Une connexion existante a dû être fermée par l'hôte distant
[2009-03-04 14:13:10 - ddms]ADB rejected shell command (pm install "/data/local/tmp/MyHouse.apk"): 
[2009-03-04 14:13:10 - Logcat]Une connexion existante a dû être fermée par l'hôte distant
java.io.IOException: Une connexion existante a dû être fermée par l'hôte distant
	at sun.nio.ch.SocketDispatcher.read0(Native Method)
	at sun.nio.ch.SocketDispatcher.read(Unknown Source)
	at sun.nio.ch.IOUtil.readIntoNativeBuffer(Unknown Source)
	at sun.nio.ch.IOUtil.read(Unknown Source)
	at sun.nio.ch.SocketChannelImpl.read(Unknown Source)
	at com.android.ddmlib.AdbHelper.executeRemoteCommand(Unknown Source)
	at com.android.ddmlib.Device.executeShellCommand(Unknown Source)
	at com.android.ddmuilib.logcat.LogPanel$3.run(Unknown Source)
[2009-03-04 14:13:11 - DeviceMonitor]Connection attempts: 1
...

L’astuce, bien que triviale, n’est pas forcément facile à dénicher dans le plugin ADT d’Eclipse. Il suffit en réalité de faire un “reset” de ADB. Pour ce faire, aller dans l’onglet Devices (si ce dernier n’est pas présent, il est accessible en faisant Window > Show View > Android/Devices), puis faites Reset adb.