Vous êtes sur la page 1sur 371

Mark Murphy

Android
Lart du dveloppement

Mark L. Murphy
Lart
du dveloppement
Android


Traduit par ric Jacoboni,
avec la contribution d'Arnaud Farine


L E P R O G R A M M E U R


Pearson Education France a apport le plus grand soin la ralisation de ce livre an de vous four-
nir une information complte et able. Cependant, Pearson Education France nassume de respon-
sabilits, ni pour son utilisation, ni pour les contrefaons de brevets ou atteintes aux droits de tierces
personnes qui pourraient rsulter de cette utilisation.
Les exemples ou les programmes prsents dans cet ouvrage sont fournis pour illustrer les descriptions
thoriques. Ils ne sont en aucun cas destins une utilisation commerciale ou professionnelle.
Pearson Education France ne pourra en aucun cas tre tenu pour responsable des prjudices
ou dommages de quelque nature que ce soit pouvant rsulter de lutilisation de ces exemples ou
programmes.
Tous les noms de produits ou marques cits dans ce livre sont des marques dposes par leurs
propritaires respectifs.
Le logo reproduit en page de couverture et sur les ouvertures de chapitres a t cr par Irina
Blok pour le compte de Google sous la licence Creative Commons 3.0 Attribution License
http://creativecommons.org/licenses/by/3.0/deed.fr.
Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larticle L. 122-5 2 et 3 a) du code de la
proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France ou, le cas chant, sans le
respect des modalits prvues larticle L. 122-10 dudit code.
No part of this book shall be reproduced, stored in a retrieval system, or transmitted by any means, electronic, mechanical,
photocopying, recording, or otherwise, without written permission from the publisher.
Publi par Pearson Education France
47 bis, rue des Vinaigriers
75010 PARIS
Tl. : 01 72 74 90 00
www.pearson.fr
Mise en pages : TyPAO
ISBN : 978-2-7440-4094-8
Copyright 2009 Pearson Education France
Tous droits rservs
Titre original : Beginning Android
Traduit par ric Jacoboni,
avec la contribution d'Arnaud Farine
ISBN original : 978-1-4302-2419-8
Copyright 2009 by Mark L. Murphy
All rights reserved
dition originale publie par
Apress,
2855 Telegraph Avenue, Suite 600,
Berkeley, CA 94705 USA


Sommaire
propos de lauteur .............................. IX
Remerciements ....................................... XI
Prface ldition franaise .................. XIII
Introduction ............................................ 1
Partie I Concepts de base ................... 3
1. Tour dhorizon ................................... 5
2. Structure dun projet ......................... 9
3. Contenu du manifeste ........................ 13
Partie II Les activits .......................... 19
4. Cration dun squelette dapplication 21
5. Utilisation des layouts XML ............. 29
6. Utilisation des widgets de base ......... 35
7. Conteneurs ......................................... 45
8. Widgets de slection ........................... 65
9. Samuser avec les listes ...................... 83
10. Utiliser de jolis widgets
et de beaux conteneurs .................... 107
11. Utilisation des menus ....................... 129
12. Polices de caractres ........................ 141
13. Intgrer le navigateur de WebKit ... 147
14. Afchage de messages surgissant ... 155
15. Utilisation des threads ..................... 161
16. Gestion des vnements du cycle
de vie dune activit ........................ 173
Partie III Stockage de donnes,
services rseaux et API .......................... 177
17. Utilisation des prfrences .............. 179
18. Accs aux chiers ............................ 191
19. Utilisation des ressources ................ 199
20. Accs et gestion des bases
de donnes locales ........................... 217
21. Tirer le meilleur parti
des bibliothques Java .................... 227
22. Communiquer via Internet ............. 235
Partie IV - Intentions (Intents) ............. 241
23. Cration de ltres dintentions ....... 243
24. Lancement dactivits
et de sous-activits ........................... 249
25. Trouver les actions possibles grce
lintrospection ............................... 259
26. Gestion de la rotation ...................... 265


Partie V Fournisseurs
de contenus et services .......................... 277
27. Utilisation dun fournisseur
de contenu (content provider) ......... 279
28. Construction dun fournisseur
de contenu ........................................ 287
29. Demander et exiger des permissions 297
30. Cration dun service ...................... 303
31. Appel dun service ........................... 309
32. Alerter les utilisateurs
avec des notications ...................... 313
Partie VI Autres fonctionnalits
dAndroid ............................................... 319
33. Accs aux services de localisation ... 321
34. Cartographie avec MapView
et MapActivity ................................. 327
35. Gestion des appels tlphoniques ... 337
36. Recherches avec SearchManager ... 341
37. Outils de dveloppement ................. 351
38. Pour aller plus loin .......................... 363
Index ....................................................... 367


Table des matires
propos de lauteur .............................. XI
Remerciements ....................................... XIII
Prface ldition franaise .................. XV
Introduction ............................................ 1
Bienvenue ! ......................................... 1
Prrequis ............................................. 1
ditions de ce livre ............................. 2
Termes dutilisation du code source ... 2
Partie I Concepts de base ................... 3
1. Tour dhorizon ................................... 5
Contenu dun programme Android ..... 6
Fonctionnalits votre disposition ..... 8
2. Structure dun projet ......................... 9
Contenu de la racine .......................... 9
la sueur de votre front ..................... 10
La suite de lhistoire ........................... 11
Le fruit de votre travail ....................... 11
3. Contenu du manifeste ........................ 13
Au dbut, il y avait la racine ............... 14
Permissions, instrumentations
et applications ..................................... 14
Que fait votre application ? ................. 15
Faire le minimum ................................ 16
Version = contrle ............................... 17
Partie II Les activits ......................... 19
4. Cration dun squelette dapplication 21
Terminaux virtuels et cibles ............... 21
Commencer par le dbut ..................... 23
Lactivit ............................................. 24
Dissection de lactivit ....................... 25
Compiler et lancer lactivit ............... 27
5. Utilisation des layouts XML ............. 29
Quest-ce quun positionnement XML ? 29
Pourquoi utiliser des layouts XML ? .. 30
Contenu dun chier layout ................ 31
Identiants des widgets ...................... 32
Utilisation des widgets
dans le code Java ................................ 32
Fin de lhistoire .................................. 33
6. Utilisation des widgets de base ......... 35
Labels ................................................. 35
Boutons ............................................... 36
Images ................................................ 37
Champs de saisie ................................ 38
Cases cocher .................................... 40
Boutons radio ..................................... 42
Rsum ............................................... 43


7. Conteneurs ......................................... 45
Penser de faon linaire ..................... 46
Tout est relatif .................................... 52
Tabula Rasa ........................................ 57
ScrollView .......................................... 61
8. Widgets de slection .......................... 65
Sadapter aux circonstances ............... 66
Listes des bons et des mchants ......... 67
Contrle du Spinner ......................... 71
Mettez vos lions en cage .................... 73
Champs : conomisez 35 % de la frappe ! 77
Galeries .............................................. 81
9. Samuser avec les listes ..................... 83
Premires tapes ................................. 83
Prsentation dynamique ..................... 85
Mieux, plus robuste et plus rapide ..... 88
Crer une liste... ................................. 94
Et la vrier deux fois ................... 99
Adapter dautres adaptateurs .............. 105
10. Utiliser de jolis widgets
et de beaux conteneurs .................... 107
Choisir ................................................ 107
Le temps scoule comme un euve .. 111
Mesurer la progression ....................... 112
Utilisation donglets ........................... 113
Tout faire basculer .............................. 120
Fouiller dans les tiroirs ....................... 125
Autres conteneurs intressants ........... 128
11. Utilisation des menus ...................... 129
Variantes de menus ............................ 130
Les menus doptions .......................... 130
Menus contextuels ............................. 131
Illustration rapide ............................... 132
Encore de lination ........................... 137
12. Polices de caractres ....................... 141
Sachez apprcier ce que vous avez .... 141
Le problme des glyphes ................... 144
13. Intgrer le navigateur
de WebKit ......................................... 147
Un navigateur, et en vitesse ! ............. 147
Chargement immdiat ......................... 150
Navigation au long cours .................... 151
Amuser le client .................................. 151
Rglages, prfrences et options ........ 153
14. Afchage de messages surgissant ... 155
Les toasts ............................................ 156
Les alertes ........................................... 156
Mise en uvre .................................... 157
15. Utilisation des threads ..................... 161
Les handlers ........................................ 162
Excution sur place ............................. 165
O est pass le thread de mon interface
utilisateur ? ......................................... 165
Dsynchronisation .............................. 166
viter les piges .................................. 172
16. Gestion des vnements du cycle
de vie dune activit .......................... 173
Lactivit de Schroedinger .................. 174
Vie et mort dune activit ................... 174
Partie III Stockage de donnes,
services rseaux et API .......................... 177
17. Utilisation des prfrences ................ 179
Obtenir ce que vous voulez ................. 179
Dnir vos prfrences ....................... 180
Un mot sur le framework .................... 180
Laisser les utilisateurs choisir ............. 181
Ajouter un peu de structure ................ 185
Botes de dialogue .............................. 187
18. Accs aux chiers ............................. 191
Allons-y ! ............................................ 191
Lire et crire ....................................... 195
19. Utilisation des ressources ................ 199
Les diffrents types de ressources ...... 199
Thorie des chanes ............................ 200
Vous voulez gagner une image ? ........ 205
Les ressources XML ........................... 207
Valeurs diverses .................................. 210
Grer la diffrence .............................. 212


20. Accs et gestion des bases
de donnes locales ............................. 217
Prsentation rapide de SQLite ............ 218
Commencer par le dbut ..................... 219
Mettre la table ..................................... 219
Ajouter des donnes ............................ 220
Le retour de vos requtes .................... 221
Des donnes, des donnes,
encore des donnes ............................. 224
21. Tirer le meilleur parti
des bibliothques Java ...................... 227
Limites extrieures .............................. 228
Ant et JAR .......................................... 228
Suivre le script .................................... 229
Tout fonctionne... enn, presque ........ 233
Relecture des scripts ........................... 233
22. Communiquer via Internet ............. 235
REST et relaxation .............................. 236
Partie IV Intentions (Intents) ............ 241
23. Cration de ltres dintentions ....... 243
Quelle est votre intention ? ................. 244
Dclarer vos intentions ....................... 245
Rcepteurs dintention ........................ 247
Attention la pause ............................ 247
24. Lancement dactivits
et de sous-activits ............................. 249
Activits paires et sous-activits ......... 250
Dmarrage .......................................... 250
Navigation avec onglets ...................... 255
25. Trouver les actions possibles grce
lintrospection ................................ 259
Faites votre choix ................................ 260
Prfrez-vous le menu ? ...................... 263
Demander lentourage ...................... 264
26. Gestion de la rotation ...................... 265
Philosophie de la destruction .............. 265
Tout est pareil, juste diffrent ............. 266
Il ny a pas de petites conomies ! ...... 270
Rotation maison .................................. 272
Forcer le destin ................................... 274
Tout comprendre ................................. 276
Partie V Fournisseurs de contenus
et services ................................................ 277
27. Utilisation dun fournisseur de contenu
(content provider) .............................. 279
Composantes dune Uri .................... 280
Obtention dun descripteur ................. 280
Cration des requtes ......................... 281
Sadapter aux circonstances ............... 282
Gestion manuelle des curseurs ........... 283
Insertions et suppressions ................... 284
Attention aux BLOB ! ........................ 285
28. Construction dun
fournisseur de contenu ..................... 287
Dabord, une petite dissection ............ 288
Puis un peu de saisie ........................... 288
tape n 1 : crer une classe Provider 289
tape n 2 : fournir une Uri .............. 294
tape n 3 : dclarer les proprits ..... 295
tape n 4 : modier le manifeste ...... 295
Avertissements en cas de modications 296
29. Demander et exiger des permissions 297
Mre, puis-je ? .................................... 298
Halte ! Qui va l ? ............................... 299
Vos papiers, sil vous plat ! ............... 301
30. Cration dun service ...................... 303
Service avec classe ............................. 304
Il ne peut en rester quun ! ................. 305
Destine du manifeste ........................ 306
Sauter la clture .................................. 306
31. Appel dun service ........................... 309
Transmission manuelle ....................... 310
Capture de lintention ......................... 311
32. Alerter les utilisateurs
avec des notications ........................ 313
Types davertissements ....................... 313
Les avertissements en action .............. 315


Partie VI Autres fonctionnalits
dAndroid ............................................... 319
33. Accs aux services
de localisation ................................... 321
Fournisseurs de localisation :
ils savent o vous vous cachez ........... 322
Se trouver soi-mme .......................... 322
Se dplacer ......................................... 324
Est-on dj arriv ? Est-on dj arriv ?
Est-on dj arriv ? ............................ 325
Tester... Tester... .................................. 326
34. Cartographie avec MapView
et MapActivity ................................. 327
Termes dutilisation ........................... 328
Empilements ...................................... 328
Les composants essentiels ................. 328
Testez votre contrle .......................... 330
Terrain accident ................................ 331
Couches sur couches .......................... 332
Moi et MyLocationOverlay .............. 334
La cl de tout ...................................... 335
35. Gestion des appels tlphoniques ... 337
Le Manager ......................................... 338
Appeler ............................................... 338
36. Recherches avec SearchManager ... 341
La chasse est ouverte .......................... 342
Recherches personnelles ..................... 343
Effectuer une recherche ...................... 349
37. Outils de dveloppement ................. 351
Gestion hirarchique ........................... 351
DDMS (Dalvik Debug Monitor Service) 356
Gestion des cartes amovibles .............. 362
38. Pour aller plus loin .......................... 363
Questions avec, parfois, des rponses . 363
Aller la source .................................. 364
Lire les journaux ................................. 365
Index ....................................................... 367


propos de lauteur
Mark Murphy est le fondateur de CommonsWare et lauteur de The Busy Coders Guide
to Android Development. Son exprience professionnelle va du conseil pour les dvelop-
pements open-source et collaboratifs de trs grosses socits au dveloppement dappli-
cations sur peu prs tout ce qui est plus petit quun mainframe. Il programme depuis
plus de vingt-cinq ans et a travaill sur des plates-formes allant du TRS-80 aux derniers
modles de terminaux mobiles. En tant quorateur averti, Mark intervient galement sur
un grand nombre de sujets dans de nombreuses confrences et sessions de formation
internationales.
Par ailleurs, Mark est le rdacteur des rubriques "Building Droids" dAndroidGuys et
"Android Angle" de NetworkWorld.
En dehors de CommonsWare, Mark sintresse beaucoup au rle que joue Internet dans
limplication des citoyens dans la politique. ce titre, il publie des articles dans la collection
Rebooting America et son blog personnel contient de nombreux articles sur la "dmocratie
cooprative".


Remerciements
Je voudrais remercier lquipe dAndroid ; non seulement pour avoir cr un bon produit,
mais galement pour laide inestimable quelle fournit dans les groupes Google consacrs
ce systme. Merci notamment Romain Guy, Justin Mattson, Dianne Hackborn,
Jean-Baptiste Queru, Jeff Sharkey et Xavier Ducrohet.
Les icnes utilises dans les exemples de ce livre proviennent du jeu dicnes Nuvola
1
.
1. http://www.icon-king.com/?p=15.


Prface
ldition franaise
Novembre 2007, Google, le gant amricain de l'Internet annonce qu'il vient de crer un
nouveau systme d'exploitation pour appareil mobile. Nomm Android, ce systme est
totalement gratuit et open-source, pour les dveloppeurs d'applications, mais galement
pour les constructeurs d'appareils mobiles (tlphones, smartphones, MID [Multimedia
Interface Device], GPS...).
Un an auparavant, Apple avait lanc un pav dans la mare en s'introduisant avec brio dans
le march des systmes d'exploitation mobiles, dtenus plus de 50 % par Symbian, grce
l'apport de nombreuses fonctionnalits multimdia et de localisation dans une interface
utilisateur trs intuitive.
Android apporte galement son lot de nouvelles fonctionnalits, mais avec des atouts
supplmentaires : gratuit, code ouvert, langage de programmation approuv et connu de
millions de dveloppeurs travers le monde. Autour d'une alliance d'une quarantaine
de constructeurs, diteurs logiciels et socits spcialises dans les applications mobiles
(Open Handset Alliance), Google a de quoi inquiter les systmes d'exploitation propri-
taires tels que Windows, Palm, Samsung, RIM, Symbian.
Certains dveloppeurs ont immdiatement cru Android, ds son annonce par Google, et
des blogs, des sites Internet et des tutoriaux ont rapidement vu le jour de par le monde. Ce
phnomne a t encourag par un concours de dveloppement l'initiative de Google
avec plus de 1 million de dollars de lots. Le but tait bien entendu de faire parler de son
nouveau systme : pari russi ! En 4 mois, alors que la documentation de la plateforme
tait quasiment inexistante, les juges du concours ont reu pas moins de 1 700 applications !
Ceux qui ont cru en Android ne se sont pas tromps sur son potentiel ni sur celui des
nouveaux services mobiles mergents ! Un an plus tard, tous les grands constructeurs de
tlphonie mobile annonaient un ou plusieurs smartphones quips de cette plateforme,


sans compter sur l'arrive de nouveaux constructeurs. Aujourd'hui, le portail de vente de
Google ddi aux applications Android (Android Market) dispose d'environ 7 000 applica-
tions (alors mme qu'il n'est toujours pas disponible sur tous les continents). Enn, plus
d'une vingtaine d'appareils quips d'Android sont attendus avant la n de l'anne 2009
(Sony, Samsung, Motorola, HTC...), et davantage encore en 2010.
Mark Murphy, auteur du prsent ouvrage, est l'un de ces dveloppeurs qui a cru Android
ds ses premires heures. Prsent sur les groupes de discussions Google, il apportait son
aide aux programmeurs en qute d'informations. C'est d'ailleurs dans ce contexte que j'ai
fait sa connaissance. Convaincu du potentiel de la plateforme, il s'est attel la rdaction
d'un livre de rfrence sur le dveloppement Android. Le faisant voluer au fur et mesure
des nouvelles versions (4 en 2008), Mark a rellement construit un ouvrage de qualit.
Toutes les bases de la programmation sous Android y sont dcrites, mais pas uniquement.
Cet ouvrage regorge d'astuces et de conseils qui vous permettront de pouvoir raliser vos
premires applications Android mais aussi, pour des dveloppements avancs, de trouver
facilement le bout de code qui vous intresse. Un ouvrage ddi aux dbutants comme aux
dveloppeurs plus avancs.
Arnaud Farine
eXpertise @ndroid
http://www.expertiseandroid.com/


Introduction
Bienvenue !
Merci de votre intrt pour le dveloppement dapplications Android ! De plus en plus de
personnes accdent dsormais aux services Internet via des moyens "non traditionnels"
comme les terminaux mobiles, et ce nombre ne peut que crotre. Bien quAndroid soit
rcent ses premiers terminaux sont apparus la n de 2008 , il ne fait aucun doute quil
se rpandra rapidement grce linuence et limportance de lOpen Handset Alliance.
Merci surtout de votre intrt pour ce livre ! Jespre sincrement que vous le trouverez
utile, voire divertissant par moments.
Prrequis
Pour programmer des applications Android, vous devez au moins connatre les bases de
Java. En effet, la programmation Android utilise la syntaxe de ce langage, plus une biblio-
thque de classes sapparentant un sous-ensemble de la bibliothque de Java SE (avec
des extensions spciques). Si vous navez jamais programm en Java, initiez-vous ce
langage avant de vous plonger dans la programmation Android.
Ce livre nexplique pas comment tlcharger ou installer les outils de dveloppement
Android, que ce soit le plugin Eclipse ou les outils ddis. Vous trouverez toutes ces infor-
mations sur le site web dAndroid
1
. Tout ce qui est expliqu dans cet ouvrage ne tient pas
compte de lenvironnement que vous utilisez et fonctionnera dans tous les cas. Nous vous
conseillons de tlcharger, dinstaller et de tester ces outils de dveloppement avant
dessayer les exemples de ce livre.
1. http://code.google.com/android/index.html.


ditions de ce livre
Cet ouvrage est le fruit dune collaboration entre Apress et CommonsWare. La version que
vous tes en train de lire est la traduction de ldition dApress, qui est disponible sous
forme imprime ou numrique.
De son ct, CommonsWare met continuellement jour le contenu original et le met
disposition des membres de son programme Warescription sous le titre The Busy Coders
Guide to Android Development.
La page http://commonsware.com/apress contient une FAQ concernant ce partenariat
avec Apress.
Termes dutilisation du code source
Le code source des exemples de ce livre est disponible partir du site web de Pearson
(www.pearson.fr), sur la page consacre cet ouvrage. Tous les projets Android sont
placs sous les termes de la licence Apache 2.0
1
, que nous vous invitons lire avant de
rutiliser ces codes.
1. http://www.apache.org/licenses/LICENSE-2.0.html.


Partie I
Concepts de base
CHAPITRE 1. Tour dhorizon
CHAPITRE 2. Structure dun projet
CHAPITRE 3. Contenu du manifeste


1
Tour dhorizon
Les terminaux Android seront essentiellement des tlphones mobiles, bien quil soit
question dutiliser cette technologie sur dautres plates-formes (comme les tablettes PC).
Pour les dveloppeurs, ceci a des avantages et des inconvnients.
Du ct des avantages, les tlphones Android qui arrivent sur le march sont assez jolis.
Si loffre de services Internet pour les mobiles remonte au milieu des annes 1990, avec
HDML (Handheld Device Markup Language), ce nest que depuis ces dernires annes
que lon dispose de tlphones capables dexploiter pleinement laccs Internet. Grce
aux SMS et des produits comme liPhone dApple, les tlphones qui peuvent servir de
terminaux Internet deviennent de plus en plus courants. Travailler sur des applications
Android permet donc dacqurir une exprience non ngligeable dans une technologie
moderne (Android) et dans un segment de march qui crot rapidement (celui des terminaux
mobiles pour Internet).
Le problme intervient lorsquil sagit de programmer. Quiconque a dj crit des
programmes pour des PDA (Personal Digital Assistant) ou des tlphones portables sest
heurt aux inconvnients de leur miniaturisation :
Les crans sont sous-dimensionns.
Les claviers, quand ils existent, sont minuscules.
Les dispositifs de pointage, quand il y en a, sont peu pratiques (qui na pas dj perdu
son stylet ?) ou imprcis (les gros doigts se marient mal avec les crans tactiles).


La vitesse du processeur et la taille de la mmoire sont ridicules par rapport celles
des machines de bureau et des serveurs auxquels nous sommes habitus.
On peut utiliser le langage de programmation et le framework de dveloppement que
lon souhaite, condition que ce soit celui qua choisi le constructeur du terminal.
Etc.
En outre, les applications qui sexcutent sur un tlphone portable doivent grer le fait
quil sagit justement dun tlphone.
Les utilisateurs sont gnralement assez irrits lorsque leur mobile ne fonctionne pas, et
cest la raison pour laquelle la campagne publicitaire "Can you hear me now?" de Verizon
a si bien fonctionn ces dernires annes. De mme, ces mmes personnes seront trs
mcontentes si votre programme perturbe leur tlphone, pour les raisons suivantes :
Il occupe tellement le processeur quelles ne peuvent plus recevoir dappels.
Il ne sintgre pas correctement au systme dexploitation de leur mobile, de sorte que
lapplication ne passe pas en arrire-plan lorsquelles reoivent ou doivent effectuer un
appel.
Il provoque un plantage de leur tlphone cause dune fuite de mmoire.
Le dveloppement de programmes pour un tlphone portable est donc diffrent de lcri-
ture dapplications pour des machines de bureau, du dveloppement de sites web ou de la
cration de programmes serveurs. Vous nirez par utiliser des outils et des frameworks
diffrents et vos programmes auront des limites auxquelles vous ntes pas habitu.
Android essaie de vous faciliter les choses :
Il fournit un langage de programmation connu (Java), avec des bibliothques relative-
ment classiques (certaines API dApache, par exemple), ainsi quun support pour les
outils auxquels vous tes peut-tre habitu (Eclipse, notamment).
Il vous offre un framework sufsamment rigide et tanche pour que vos programmes
sexcutent "correctement" sur le tlphone, sans interfrer avec les autres applications
ou le systme dexploitation lui-mme.
Comme vous vous en doutez srement, lessentiel de ce livre sintresse ce framework et
la faon dcrire des programmes qui fonctionnent dans son cadre et tirent parti de ses
possibilits.
Contenu dun programme Android
Le dveloppeur dune application classique est "le seul matre bord". Il peut ouvrir la
fentre principale de son programme, ses fentres lles les botes de dialogue, par exem-
ple comme il le souhaite. De son point de vue, il est seul au monde ; il tire parti des fonc-
tionnalits du systme dexploitation mais ne soccupe pas des autres programmes


susceptibles de sexcuter en mme temps que son programme. Sil doit interagir avec
dautres applications, il passe gnralement par une API, comme JDBC (ou les frame-
works qui reposent sur lui) pour communiquer avec MySQL ou un autre SGBDR.
Android utilise les mmes concepts, mais proposs de faon diffrente, avec une structure
permettant de mieux protger le fonctionnement des tlphones.
Activity (Activit)
La brique de base de linterface utilisateur sappelle activity (activit). Vous pouvez la
considrer comme lquivalent Android de la fentre ou de la bote de dialogue dune
application classique.
Bien que des activits puissent ne pas avoir dinterface utilisateur, un code "invisible" sera
dlivr le plus souvent sous la forme de fournisseurs de contenus (content provider) ou de
services.
Content providers (fournisseurs de contenus)
Les fournisseurs de contenus offrent un niveau dabstraction pour toutes les donnes stoc-
kes sur le terminal et accessibles aux diffrentes applications. Le modle de dveloppement
Android encourage la mise disposition de ses propres donnes aux autres programmes
construire un content provider permet dobtenir ce rsultat tout en gardant un contrle
total sur la faon dont on accdera aux donnes.
Intents (intentions)
Les intentions sont des messages systme. Elles sont mises par le terminal pour prvenir
les applications de la survenue de diffrents vnements, que ce soit une modication
matrielle (comme linsertion dune carte SD) ou larrive de donnes (telle la rception
dun SMS), en passant par les vnements des applications elles-mmes (votre activit a
t lance partir du menu principal du terminal, par exemple). Vous pouvez non seule-
ment rpondre aux intentions, mais galement crer les vtres an de lancer dautres acti-
vits ou pour vous prvenir quune situation particulire a lieu (vous pouvez, par exemple,
mettre lintention X lorsque lutilisateur est moins de 100 mtres dun emplacement Y).
Services
Les activits, les fournisseurs de contenus et les rcepteurs dintentions ont une dure de
vie limite et peuvent tre teints tout moment. Les services sont en revanche conus
pour durer et, si ncessaire, indpendamment de toute activit. Vous pouvez, par exemple,
utiliser un service pour vrier les mises jour dun ux RSS ou pour jouer de la musique,
mme si lactivit de contrle nest plus en cours dexcution.


Fonctionnalits votre disposition
Android fournit un certain nombre de fonctionnalits pour vous aider dvelopper des
applications.
Stockage
Vous pouvez empaqueter (packager) des chiers de donnes dans une application, pour y
stocker ce qui ne changera jamais les icnes ou les chiers daide, par exemple. Vous
pouvez galement rserver un petit emplacement sur le terminal lui-mme, pour y stocker
une base de donnes ou des chiers contenant des informations ncessaires votre appli-
cation et saisies par lutilisateur ou rcupres partir dune autre source. Si lutilisateur
fournit un espace de stockage comme une carte SD, celui-ci peut galement tre lu et crit
en fonction des besoins.
Rseau
Les terminaux Android sont gnralement conus pour tre utiliss avec Internet, via un
support de communication quelconque. Vous pouvez tirer parti de cet accs Internet
nimporte quel niveau, des sockets brutes de Java un widget de navigateur web intgr
que vous pouvez intgrer dans votre application.
Multimdia
Les terminaux Android permettent denregistrer et de jouer de la musique et de la vido.
Bien que les caractristiques spciques varient en fonction des modles, vous pouvez
connatre celles qui sont disponibles et tirer parti des fonctionnalits multimdias offertes,
que ce soit pour couter de la musique, prendre des photos ou enregistrer des mmos
vocaux.
GPS
Les fournisseurs de positionnement, comme GPS, permettent dindiquer aux applications
o se trouve le terminal. Il vous est alors possible dafcher des cartes ou dutiliser ces
donnes gographiques pour retrouver la trace du terminal sil a t vol, par exemple.
Services tlphoniques
videmment, les terminaux Android sont gnralement des tlphones, ce qui permet
vos programmes de passer des appels, denvoyer et de recevoir des SMS et de raliser tout
ce que vous tes en droit dattendre dune technologie tlphonique moderne.


2
Structure dun projet
Le systme de construction dun programme Android est organis sous la forme dune
arborescence de rpertoires spcique un projet, exactement comme nimporte quel
projet Java. Les dtails, cependant, sont spciques Android et sa prparation de
lapplication qui sexcutera sur le terminal ou lmulateur. Voici un rapide tour dhorizon
de la structure dun projet, qui vous aidera mieux comprendre les exemples de code
utiliss dans ce livre et que vous pouvez tlcharger sur le site web de Pearson,
www.pearson.fr, sur la page consacre cet ouvrage.
Contenu de la racine
La cration dun projet Android (avec la commande android create project ou via un
environnement de programmation adapt Android) place plusieurs lments dans le
rpertoire racine du projet :
AndroidManifest.xml est un chier XML qui dcrit lapplication construire et les
composants activits, services, etc. fournis par celle-ci.


build.xml est un script Ant
1
permettant de compiler lapplication et de linstaller sur
le terminal (ce chier nest pas prsent avec un environnement de programmation
adapt, tel Eclipse).
default.properties et local.properties sont des chiers de proprits utiliss
par le script prcdent.
bin/ contient lapplication compile.
gen/ contient le code source produit par les outils de compilation dAndroid.
libs/ contient les chiers JAR extrieurs ncessaires lapplication.
src/ contient le code source Java de lapplication.
res/ contient les ressources icnes, descriptions des lments de linterface graphique
(layouts), etc. empaquetes avec le code Java compil.
tests/ contient un projet Android entirement distinct, utilis pour tester celui que
vous avez cr.
assets/ contient les autres chiers statiques fournis avec lapplication pour son
dploiement sur le terminal.
la sueur de votre front
Lors de la cration dun projet Android (avec android create project, par exemple),
vous devez fournir le nom de classe ainsi que le chemin complet (paquetage) de lactivit
"principale" de lapplication (com.commonsware.android.UneDemo, par exemple). Vous
constaterez alors que larborescence src/ de ce projet contient la hirarchie des rpertoires
dnis par le paquetage ainsi quun squelette dune sous-classe dActivity reprsentant
lactivit principale (src/com/commonsware/android/UneDemo.java). Vous pouvez bien
sr modier ce chier et en ajouter dautres larborescence src/ selon les besoins de
votre application.
La premire fois que vous compilerez le projet (avec ant, par exemple), la chane de
production dAndroid crera le chier R.java dans le paquetage de lactivit "principale".
Ce chier contient un certain nombre de dnitions de constantes lies aux diffrentes
ressources de larborescence res/. Il est dconseill de le modier manuellement : laissez
les outils dAndroid sen occuper. Vous rencontrerez de nombreuses rfrences R.java
dans les exemples de ce livre (par exemple, on dsignera lidentiant dun layout par
R.layout.main).
1. http://ant.apache.org/.


La suite de lhistoire
Comme on la dj indiqu, larborescence res/ contient les ressources, cest--dire des
chiers statiques fournis avec lapplication, soit sous leur forme initiale soit, parfois, sous
une forme prtraite. Parmi les sous-rpertoires de res/, citons :
res/drawable/ pour les images (PNG, JPEG, etc.) ;
res/layout/ pour les descriptions XML de la composition de linterface graphique ;
res/menu/ pour les descriptions XML des menus ;
res/raw/ pour les chiers gnraux (un chier CSV contenant les informations dun
compte, par exemple) ;
res/values/ pour les messages, les dimensions, etc. ;
res/xml/ pour les autres chiers XML gnraux que vous souhaitez fournir.
Nous prsenterons tous ces rpertoires, et bien dautres, dans la suite de ce livre, notamment
au Chapitre 19.
Le fruit de votre travail
Lorsque vous compilez un projet (avec ant ou un IDE), le rsultat est plac dans le rper-
toire bin/, sous la racine de larborescence du projet :
bin/classes/ contient les classes Java compiles.
bin/classes.dex contient lexcutable cr partir de ces classes compiles.
bin/votreapp.ap (o votreapp est le nom de lapplication) contient les ressources
de celle-ci, sous la forme dun chier ZIP.
bin/votreapp debug.apk ou bin/votreapp unsigned.apk est la vritable application
Android.
Le chier .apk est une archive ZIP contenant le chier .dex, la version compile des
ressources (resources.arsc), les ventuelles ressources non compiles (celles qui se
trouvent sous res/raw/, par exemple) et le chier AndroidManifest.xml. Cette archive
est signe : la partie debug du nom de chier indique quelle la t laide dune cl de
dbogage qui fonctionne avec lmulateur alors que unsigned prcise que lapplication a
t construite pour tre dploye (ant release) : larchive APK doit alors tre signe
laide de jarsigner et dune cl ofcielle.


3
Contenu du manifeste
Le point de dpart de toute application Android est son chier manifeste, AndroidMani
fest.xml, qui se trouve la racine du projet. Cest dans ce chier que lon dclare ce que
contiendra lapplication les activits, les services, etc. On y indique galement la faon
dont ces composants seront relis au systme Android lui-mme en prcisant, par exem-
ple, lactivit (ou les activits) qui doivent apparatre dans le menu principal du terminal
(ce menu est galement appel "lanceur" ou "launcher").
La cration dune application produit automatiquement un manifeste de base. Pour une
application simple, qui offre uniquement une activit, ce manifeste automatique convien-
dra srement ou ncessitera ventuellement quelques modications mineures. En revan-
che, le manifeste de lapplication de dmonstration Android fournie contient plus de
1 000 lignes. Vos applications se situeront probablement entre ces deux extrmits.
La plupart des parties intressantes du manifeste seront dcrites en dtail dans les chapi-
tres consacrs aux fonctionnalits dAndroid qui leur sont associes llment service,
par exemple, est dtaill au Chapitre 30. Pour le moment, il vous suft de comprendre le
rle de ce chier et la faon dont il est construit.


Au dbut, il y avait la racine
Tous les chiers manifestes ont pour racine un lment manifest :
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.search">
...
</manifest>
Cet lment comprend la dclaration de lespace de noms android. Curieusement, les
manifestes produits nappliquent cet espace quaux attributs, pas aux lments (on crit
donc manifest, pas android:manifest). Nous vous conseillons de conserver cette
convention, sauf si Android la modie dans une version future.
Lessentiel des informations que vous devez fournir cet lment est lattribut
package (qui nutilise pas non plus lespace de noms). Cest l que vous pouvez
indiquer le nom du paquetage Java qui sera considr comme la "base" de
votre application. Dans la suite du chier, vous pourrez alors simplement
utiliser le symbole point pour dsigner ce paquetage : si vous devez, par exem-
ple, faire rfrence com.commonsware.android.search.Snicklefritz
dans le manifeste de cet exemple, il sufra dcrire .Snicklefritz puisque
com.commonsware.android.search est dni comme le paquetage de lapplication.
Permissions, instrumentations et applications
Sous llment manifest, vous trouverez les lments suivants :
Des lments uses permission indiquant les permissions dont a besoin votre application
pour fonctionner correctement (voir Chapitre 19 pour plus de dtails).
Des lments permission dclarant les permissions que les activits ou les services
peuvent exiger des autres applications pour utiliser les donnes et le code de lapplication
(voir galement Chapitre 19).
Des lments instrumentation qui indiquent le code qui devrait tre appel pour les
vnements systme essentiels, comme le lancement des activits. Ces lments sont
utiliss pour la journalisation ou la surveillance.
Des lments uses library pour relier les composants facultatifs dAndroid, comme
les services de golocalisation.
ventuellement, un lment uses sdk indiquant la version du SDK Android avec
laquelle a t construite lapplication.
Un lment application qui dnit le cur de lapplication dcrite par le manifeste.
In
f
o


Le manifeste de lexemple qui suit contient des lments uses permission qui indiquent
certaines fonctionnalits du terminal dont lapplication a besoin ici, lapplication doit
avoir le droit de dterminer sa position gographique courante. Llment application
dcrit les activits, les services et tout ce qui constitue lapplication elle-mme.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android">
<uses-permission
android:name="android.permission.ACCESS_LOCATION" />
<uses-permission
android:name="android.permission.ACCESS_GPS" />
<uses-permission
android:name="android.permission.ACCESS_ASSISTED_GPS" />
<uses-permission
android:name="android.permission.ACCESS_CELL_ID" />
<application>
...
</application>
</manifest>
Que fait votre application ?
Le plat de rsistance du chier manifeste est dcrit par les ls de llment application.
Par dfaut, la cration dun nouveau projet Android nindique quun seul lment acti
vity :
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.skeleton">
<application>
<activity android:name=".Now" android:label="Now">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Cet lment fournit android:name pour la classe qui implmente lactivit,
android:label pour le nom afch de lactivit et (souvent) un lment ls intent
filter dcrivant les conditions sous lesquelles cette activit safchera. Llment acti
vity de base congure votre activit pour quelle apparaisse dans le lanceur et que les
utilisateurs puissent lexcuter. Comme nous le verrons plus tard, un mme projet peut
dnir plusieurs activits.


Il peut galement y avoir un ou plusieurs lments receiver dcrivant les non-activits
qui devraient se dclencher sous certaines conditions la rception dun SMS, par exem-
ple. On les appelle rcepteurs dintentions (intent receivers) et ils sont dcrits au Chapi-
tre 23.
De mme, un ou plusieurs lments provider peuvent tre prsents an dindiquer les
fournisseurs de contenus (content providers) les composants qui fournissent les donnes
vos activits et, avec votre permission, aux activits dautres applications du terminal.
Ces lments enveloppent les bases de donnes ou les autres stockages de donnes en une
API unique que toute application peut ensuite utiliser. Nous verrons plus loin comment
crer des fournisseurs de contenus et comment utiliser les fournisseurs que vous ou
dautres ont crs.
Enn, il peut y avoir un ou plusieurs lments service dcrivant les services, cest--dire
les parties de code qui peuvent fonctionner indpendamment de toute activit et en perma-
nence. Lexemple classique est celui du lecteur MP3, qui permet de continuer couter de
la musique, mme si lutilisateur ouvre dautres activits et que linterface utilisateur nest
pas afche au premier plan. La cration et lutilisation des services sont dcrites aux
Chapitres 30 et 31.
Faire le minimum
Android, comme la plupart des systmes dexploitation, est rgulirement amlior, ce qui
donne lieu de nouvelles versions du systme. Certains de ces changements affectent le
SDK car de nouveaux paramtres, classes ou mthodes apparaissent, qui nexistaient pas
dans les versions prcdentes.
Pour tre sr que votre application ne sexcutera que pour une version prcise (ou sup-
rieure) dun environnement Android, ajoutez un lment uses sdk comme ls de
llment manifest du chier AndroidManifest.xml. uses sdk na quun seul attribut,
minSdkVersion, qui indique la version minimale du SDK exige par votre application :
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.search">
<uses-sdk minSdkVersion="2" />
...
</manifest>
lheure o ce livre est crit, minSdkVersion peut prendre trois valeurs :
1 pour indiquer le premier SDK dAndroid, la version 1.0 ;
2 pour indiquer la version 1.1 du SDK dAndroid ;
3 pour indiquer la version 1.5.


Labsence dun lment uses sdk revient utiliser 1 comme valeur de minSdkVersion.
Cela dit, la boutique Android semble tenir ce que lon indique explicitement la valeur de
minSdkVersion : faites en sorte dutiliser un lment uses sdk si vous comptez distribuer
votre programme par ce biais.
Si cet lment est prsent, lapplication ne pourra sinstaller que sur les terminaux compa-
tibles. Vous navez pas besoin de prciser la dernire version du SDK mais, si vous en
choisissez une plus ancienne, cest vous de vrier que votre application fonctionnera
sur toutes les versions avec lesquelles elle prtend tre compatible. Si, par exemple, vous
omettez uses sdk, cela revient annoncer que votre application fonctionnera sur toutes
les versions existantes du SDK et vous devrez videmment tester que cest bien le cas.
Version = contrle
Si vous comptez distribuer votre application via la boutique Android ou dautres supports,
vous devrez srement ajouter les attributs android:versionCode et android:version
Name llment manifest an de faciliter le processus de mise jour des applications.
android:versionName est une chane lisible reprsentant le nom ou le numro de version
de votre application. Vous pouvez, par exemple, utiliser des valeurs comme "3.0" ou
"System V", en fonction de vos prfrences.
android:versionCode est un entier cens reprsenter le numro de version de lapplica-
tion. Le systme lutilise pour savoir si votre version est plus rcente quune autre
"rcent" tant dni par "la valeur dandroid:versionCode est plus leve". Pour obtenir
cette valeur, vous pouvez convertir le contenu dandroid:versionName en nombre ou
simplement incrmenter la valeur chaque nouvelle version.


Partie II
Les activits
CHAPITRE 4. Cration dun squelette dapplication
CHAPITRE 5. Utilisation des layouts XML
CHAPITRE 6. Utilisation des widgets de base
CHAPITRE 7. Conteneurs
CHAPITRE 8. Widgets de slection
CHAPITRE 9. Samuser avec les listes
CHAPITRE 10. Utiliser de jolis widgets et de beaux conteneurs
CHAPITRE 11. Utilisation des menus
CHAPITRE 12. Polices de caractres
CHAPITRE 13. Intgrer le navigateur de WebKit
CHAPITRE 14. Afchage de messages surgissant
CHAPITRE 15. Utilisation des threads
CHAPITRE 16. Gestion des vnements du cycle de vie dune activit


4
Cration dun squelette
dapplication
Tous les livres consacrs un langage ou un environnement de programmation commen-
cent par prsenter un programme de dmonstration de type "Bonjour tous !" : il permet
de montrer que lon peut construire quelque chose tout en restant sufsamment concis
pour ne pas noyer le lecteur dans les dtails. Cependant, le programme "Bonjour tous !"
classique nest absolument pas interactif (il se contente dcrire ces mots sur la console) et
est donc assez peu stimulant. Ce chapitre prsentera donc un projet qui utilisera malgr
tout un bouton et lheure courante pour montrer le fonctionnement dune activit Android
simple.
Terminaux virtuels et cibles
Pour construire les projets, nous supposons que vous avez tlcharg le SDK (et, ventuel-
lement, le plugin ADT dEclipse). Avant de commencer, nous devons prsenter la notion
de "cible" car elle risque de vous surprendre et elle est relativement importante dans le
processus de dveloppement.


Comme son nom lindique, un AVD (Android Virtual Device), est un terminal virtuel
par opposition aux vrais terminaux Android comme le G1 ou le Magic de HTC. Les
AVD sont utiliss par lmulateur fourni avec le SDK et vous permettent de tester vos
programmes avant de les dployer sur les vritables terminaux. Vous devez indiquer
lmulateur un terminal virtuel an quil puisse prtendre quil est bien le terminal dcrit
par cet AVD.
Cration dun AVD
Pour crer un AVD, vous pouvez lancer la commande android create avd ou utiliser
Eclipse ; dans les deux cas, vous devez indiquer une cible qui prcise la classe de termi-
naux qui sera simule par lAVD. lheure o cette dition est publie, il existe trois
cibles :
une qui dsigne un terminal Android 1.1, comme un G1 HTC de base qui naurait pas
t mis jour ;
une deuxime qui dsigne un terminal Android 1.5, sans le support de Google Maps ;
une troisime qui dsigne un terminal Android 1.5 disposant du support de Google
Maps, ce qui est le cas de la majorit des terminaux Android actuels.
Si vous dveloppez des applications utilisant Google Maps, vous devez donc utiliser un
ADV ayant la cible 3. Sinon la cible 2 conviendra parfaitement. Actuellement, la plupart
des G1 ayant t mis jour avec Android 1.5, la cible 1 nest plus trs utile.
Vous pouvez crer autant dAVD que vous le souhaitez du moment que vous avez
assez despace disque disponible sur votre environnement de dveloppement : si vous
avez besoin dun pour chacune des trois cibles, libre vous ! Noubliez pas, cependant,
que linstallation dune application sur un AVD naffecte pas les autres AVD que vous avez
crs.
Choix dune cible
Lorsque vous crez un projet (avec android create project ou partir dEclipse), vous
devez galement indiquer la classe de terminal vise par celui-ci. Les valeurs possibles
tant les mmes que ci-dessus, crer un projet avec une cible 3 donne les indications
suivantes :
Vous avez besoin dAndroid 1.5.
Vous avez besoin de Google Maps.
Lapplication nale ne sinstallera donc pas sur les terminaux qui ne correspondent pas
ces critres.


Voici quelques rgles pour vous aider grer les cibles :
Demandez-vous ce dont vous avez rellement besoin. Ne crez un projet avec une
cible 3 que si vous utilisez Google Maps, notamment. Sinon une cible 2 est prfrable
vous exigerez toujours Android 1.5, mais votre application pourra sexcuter sur des
terminaux qui ne disposent pas de Google Maps.
Testez autant de cibles que possible. Vous pourriez tre tent par la cible 1 pour
viser le plus grand nombre de terminaux Android ; cependant, vous devrez alors
tester votre application sur un AVD ayant une cible 1 et un AVD ayant une cible 2
(et il serait galement souhaitable de la tester avec un AVD ayant une cible 3, au cas
o).
Vriez quune nouvelle cible na pas t ajoute par une nouvelle version
dAndroid. Il devrait y avoir quelques nouvelles valeurs avec chaque version majeure
(2.0 ou 1.6, par exemple), voire pour les versions intermdiaires (1.5r1 ou 1.5r2).
Assurez-vous de tester votre application sur ces nouvelles cibles chaque fois que
cela est possible car certains peuvent utiliser ces nouvelles versions de terminaux ds
quelles sortent.
Le fait de tester avec des AVD, quelle que soit la cible, ne peut pas se substituer aux
tests sur du vrai matriel. Les AVD sont conus pour vous fournir des "environnements
jetables", permettant de tester un grand nombre denvironnements, mme ceux qui
nexistent pas encore rellement. Cependant, vous devez mettre votre application
lpreuve dau moins un terminal Android. En outre, la vitesse de votre mulateur peut
ne pas correspondre celle du terminal selon votre systme, elle peut tre plus rapide
ou plus lente.
Commencer par le dbut
Avec Android, tout commence par la cration dun projet. En Java classique, vous pouvez,
si vous le souhaitez, vous contenter dcrire un programme sous la forme dun unique
chier, le compiler avec javac puis lexcuter avec Java. Android est plus complexe mais,
pour faciliter les choses, Google a fourni des outils daide la cration dun projet. Si vous
utilisez un IDE compatible avec Android, comme Eclipse et le plugin Android (fourni avec
le SDK), vous pouvez crer un projet directement partir de cet IDE (menu Fichier >
Nouveau > Projet, puis choisissez Android > Android Project).
Si vous vous servez doutils non Android, vous pouvez utiliser le script android, qui se
trouve dans le rpertoire tools/ de linstallation du SDK en lui indiquant que vous
souhaitez crer un projet (create project). Il faut alors lui indiquer la version de la
cible, le rpertoire racine du projet, le nom de lactivit et celui du paquetage o tout devra
se trouver :


android create project \
--target 2 \
--path chemin/vers/mon/projet \
--activity Now \
--package com.commonsware.android.skeleton
Cette commande cre automatiquement les chiers que nous avons dcrits au Chapitre 2 et
que nous utiliserons dans le reste de ce chapitre.
Vous pouvez galement tlcharger les rpertoires des projets exemples de ce livre sous la
forme de chiers ZIP partir du site web de Pearson
1
. Ces projets sont prts tre utiliss :
vous navez donc pas besoin dutiliser android create project lorsque vous aurez
dcompress ces exemples.
Lactivit
Le rpertoire src/ de votre projet contient une arborescence de rpertoires Java classique,
cre daprs le paquetage Java que vous avez utilis pour crer le projet
(com.commonsware.android.skeleton produit donc src/com/commonsware/android/
skeleton). Dans le rpertoire le plus bas, vous trouverez un chier source nomm
Now.java, dans lequel sera stock le code de votre premire activit. Celle-ci sera consti-
tue dun unique bouton qui afchera lheure laquelle on a appuy dessus pour le
dernire fois (ou lheure de lancement de lapplication si on na pas encore appuy sur ce
bouton).
Ouvrez Now.java dans un diteur de texte et copiez-y le code suivant :
package com.commonsware.android.skeleton;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import java.util.Date;
public class Now extends Activity implements View.OnClickListener {
Button btn;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
btn = new Button(this);
btn.setOnClickListener(this);
updateTime();
setContentView(btn);
}
1. http://pearson.fr.


public void onClick(View view) {
updateTime();
}
private void updateTime() {
btn.setText(new Date().toString());
}
}
Si vous avez tlcharg les chiers partir du site web de Pearson, vous pouvez vous
contenter dutiliser directement le projet Skeleton/Now.
Examinons maintenant chacune des parties de ce code.
Dissection de lactivit
La dclaration de paquetage doit tre identique celle que vous avez utilise pour crer le
projet.
Comme pour tout projet Java, vous devez importer les classes auxquelles vous faites rf-
rence. La plupart de celles qui sont spciques Android se trouvent dans le paquetage
android :
package com.commonsware.android.skeleton;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import java.util.Date;
Notez bien que toutes les classes de Java SE ne sont pas utilisables par les programmes
Android. Consultez le guide de rfrence des classes Android
1
pour savoir celles qui sont
disponibles et celles qui ne le sont pas.
Les activits sont des classes publiques hrites de la classe de base android.app.Acti
vity. Ici, lactivit contient un bouton (btn) :
public class Now extends Activity implements View.OnClickListener {
Button btn;
Un bouton, comme vous pouvez le constater daprs le nom du paquetage, est un
widget Android. Les widgets sont des lments dinterface graphique que vous
pouvez utiliser dans une application.
1. http://code.google.com/android/reference/packages.html.
In
f
o


Pour rester simple, nous voulons capturer tous les clics de bouton dans lactivit elle-
mme : cest la raison pour laquelle la classe de notre activit implmente galement
OnClickListener.
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
btn = new Button(this);
btn.setOnClickListener(this);
updateTime();
setContentView(btn);
}
La mthode onCreate() est appele lorsque lactivit est lance. La premire chose
faire est dtablir un chanage vers la superclasse an de raliser linitialisation de lacti-
vit Android de base.
Nous crons ensuite linstance du bouton (avec new Button(this)) et demandons
denvoyer tous les clics sur ce bouton linstance de lactivit (avec setOnClickLis
tener()). Nous appelons ensuite la mthode prive updateTime() (qui sera prsente
plus loin), puis nous congurons la vue du contenu de lactivit pour que ce soit le bouton
lui-mme (avec setContentView()).
Tous les widgets drivent de la classe de base View. Bien que lon construise gn-
ralement linterface graphique partir dune hirarchie de vues, nous nutiliserons
ici quune seule vue.
Nous prsenterons ce Bundle icicle magique au Chapitre 16. Pour linstant, consid-
rons-le comme un gestionnaire opaque, que toutes les activits reoivent lors de leur cration.
public void onClick(View view) {
updateTime();
}
Avec Swing, un clic sur un JButton dclenche un ActionEvent qui est transmis
lActionListener congur pour ce bouton. Avec Android, en revanche, un clic sur un
bouton provoque lappel de la mthode onClick() sur linstance OnClickListener
congure pour le bouton. Lcouteur reoit la vue qui a dclench le clic (ici, il sagit du
bouton). Dans notre cas, nous nous contentons dappeler la mthode prive update
Time() :
private void updateTime() {
btn.setText(new Date().toString());
}
In
f
o


Louverture de lactivit (onCreate()) ou un clic sur le bouton (onClick()) doit provo-
quer la mise jour du label du bouton avec la date courante. On utilise pour cela la
mthode setText(), qui fonctionne exactement comme avec les JButton de Swing.
Compiler et lancer lactivit
Pour compiler lactivit, utilisez loutil de cration de paquetage Android intgr votre
IDE ou lancez ant debug depuis le rpertoire racine de votre projet. Puis lancez lactivit
en suivant les tapes suivantes :
1. Lancez lmulateur (avec tools/emulator dans votre installation du SDK Android),
pour obtenir une gure analogue celle de la Figure 4.1. Noubliez pas de prciser un
AVD avec loption avd.
2. Installez le paquetage (avec tools/adb install /racine/projet/bin/Now debug
.apk).
3. Consultez la liste des applications installes sur lmulateur et recherchez celle qui
sappelle Now (voir Figure 4.2).
4. Ouvrez cette application.
Vous devriez voir apparatre un cran dactivit comme celui de la Figure 4.3.
Figure 4.1
Lcran daccueil
dAndroid.


En cliquant sur le bouton en dautres termes, quasiment nimporte o sur lcran du tl-
phone , lheure courante safchera sur le label du bouton.
Vous remarquerez que ce label est centr horizontalement et verticalement car cest le
style par dfaut. Nous verrons au Chapitre 6 que nous pouvons videmment contrler ce
formatage.
Une fois repu de cette technologie poustouante des boutons, vous pouvez cliquer sur le
bouton de retour en arrire de lmulateur pour revenir au lanceur.
Figure 4.2
Le lanceur dapplications
dAndroid.
Figure 4.3
Dmonstration
de lactivit Now.


5
Utilisation des layouts
XML
Bien quil soit techniquement possible de crer et dattacher des composants widgets une
activit en utilisant uniquement du code Java comme nous lavons fait au Chapitre 4,
on prfre gnralement employer un chier de positionnement (layout) cod en
XML. Linstanciation dynamique des widgets est rserve aux scnarios plus comple-
xes, o les widgets ne sont pas connus au moment de la compilation (lorsquil faut, par
exemple, remplir une colonne de boutons radio en fonction de donnes rcupres sur
Internet).
Examinons maintenant ce code XML pour savoir comment sont agences les vues des
activits Android lorsque lon utilise cette approche.
Quest-ce quun positionnement XML ?
Comme son nom lindique, un positionnement XML est une spcication des relations
existant entre les composants widgets et avec leurs conteneurs (voir Chapitre 7) expri-
me sous la forme dun document XML. Plus prcisment, Android considre les layouts
XML comme des ressources stockes dans le rpertoire res/layout du projet.


Chaque chier XML contient une arborescence dlments prcisant le layout des widgets
et les conteneurs qui composent une View. Les attributs de ces lments sont des proprits
qui dcrivent laspect dun widget ou le comportement dun conteneur. Un lment
Button avec un attribut android:textStyle = "bold", par exemple, signie que le texte
apparaissant sur ce bouton sera en gras.
Loutil aapt du SDK dAndroid utilise ces layouts ; il est appel automatiquement par la
chane de production du projet (que ce soit via Eclipse ou par le traitement du chier
build.xml de Ant). Cest aapt qui produit le chier source R.java du projet, qui vous
permet daccder directement aux layouts et leurs composants widgets depuis votre code
Java, comme nous le verrons bientt.
Pourquoi utiliser des layouts XML ?
La plupart de ce qui peut tre fait avec des chiers de positionnement XML peut gale-
ment tre ralis avec du code Java. Vous pourriez, par exemple, utiliser setTypeface()
pour quun bouton afche son texte en gras au lieu dutiliser une proprit dans un chier
XML. Ces chiers sajoutant tous ceux que vous devez dj grer, il faut donc quil y ait
une bonne raison de les utiliser.
La principale est probablement le fait quils permettent de crer des outils de dnition des
vues : le constructeur dinterfaces graphiques dun IDE comme Eclipse ou un assistant
ddi la cration des interfaces graphiques dAndroid, comme DroidDraw
1
, par exemple.
Ces logiciels pourraient, en principe, produire du code Java au lieu dun document XML,
mais il est bien plus simple de relire la dnition dune interface graphique an de la
modier lorsque cette dnition est exprime dans un format structur comme XML au
lieu dtre code dans un langage de programmation. En outre, sparer ces dnitions
XML du code Java rduit les risques quune modication du code source perturbe lappli-
cation. XML est un bon compromis entre les besoins des concepteurs doutils et ceux des
programmeurs.
En outre, lutilisation de XML pour la dnition des interfaces graphiques est devenue
monnaie courante. XAML
2
de Microsoft, Flex
3
dAdobe et XUL
4
de Mozilla utilisent
toutes une approche quivalente de celle dAndroid : placer les dtails des positionne-
ments dans un chier XML en permettant de les manipuler partir des codes sources (
laide de JavaScript pour XUL, par exemple). De nombreux frameworks graphiques moins
connus, comme ZK
5
, utilisent galement XML pour la dnition de leurs vues. Bien que
1. http://droiddraw.org/.
2. http://windowssdk.msdn.microsoft.com/en-us/library/ms752059.aspx.
3. http://www.adobe.com/products/ex/.
4. http://www.mozilla.org/projects/xul/.
5. http://www.zkoss.org/.


"suivre le troupeau" ne soit pas toujours le meilleur choix, cette politique a lavantage de
faciliter la transition vers Android partir dun autre langage de description des vues
reposant sur XML.
Contenu dun chier layout
Voici le bouton de lapplication du chapitre prcdent, converti en un chier XML
que vous trouverez dans le rpertoire chap5/Layouts/NowRedux de larchive des
codes sources, tlchargeable sur le site www.pearson.fr, sur la page ddie cet
ouvrage. Pour faciliter la recherche des codes des exemples, cette archive est dcoupe
selon les chapitres du livre.
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/button"
android:text=""
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
Ici, le nom de llment XML est celui de la classe du widget, Button. Ce dernier tant
fourni par Android, il suft dutiliser simplement son nom de classe. Dans le cas dun
widget personnalis, driv dandroid.view.View, il faudrait utiliser un nom pleinement
quali, contenant le nom du paquetage (com.commonsware.android.MonWidget, par
exemple).
Llment racine doit dclarer lespace de noms XML dAndroid :
xmlns:android="http://schemas.android.com/apk/res/android"
Tous les autres lments sont des ls de la racine et hritent de cette dclaration.
Comme lon souhaite pouvoir faire rfrence ce bouton partir de notre code Java, il faut
lui associer un identiant avec lattribut android:id. Nous expliquerons plus prcisment
ce mcanisme plus loin dans ce chapitre.
Les autres attributs sont les proprits de cette instance de Button :
android:text prcise le texte qui sera afch au dpart sur le bouton (ici, il sagit
dune chane vide).
android:layout width et android:layout height prcisent que la largeur et la
hauteur du bouton rempliront le "parent", cest--dire ici lcran entier ces attributs
seront prsents plus en dtail au Chapitre 7.
Ce widget tant le seul contenu de notre activit, nous navons besoin que de cet lment.
Les vues plus complexes ncessitent une arborescence dlments, an de reprsenter les
widgets et les conteneurs qui contrlent leur positionnement. Dans la suite de ce livre,
nous utiliserons des positionnements XML chaque fois que cela est ncessaire : vous


trouverez donc des dizaines dexemples plus complexes que vous pourrez utiliser comme
point de dpart pour vos propres projets.
Identiants des widgets
De nombreux widgets et conteneurs ne peuvent apparatre que dans le chier de position-
nement et ne seront pas utiliss par votre code Java. Le plus souvent, un label statique
(TextView), par exemple, na besoin dtre dans le chier XML que pour indiquer
lemplacement o il doit apparatre dans linterface. Ce type dlment na donc pas
besoin dun attribut android:id pour lui donner un nom.
En revanche, tous les lments dont vous avez besoin dans votre source Java doivent
possder cet attribut.
La convention consiste utiliser le format @+id/nom unique comme valeur didentiant,
o nom unique reprsente le nom local du widget, qui doit tre unique. Dans lexemple de
la section prcdente, lidentiant du widget Button tait @+id/button.
Android utilise galement quelques valeurs android:id spciques, de la forme
@android:id/.... Nous les rencontrerons dans diffrents chapitres de ce livre, notamment
aux Chapitres 8 et 10.
Utilisation des widgets dans le code Java
Une fois que vous avez douloureusement congur les widgets et les conteneurs dans un
chier de positionnement XML nomm main.xml et que vous lavez plac dans le rper-
toire res/layout, vous navez plus qu ajouter une seule instruction dans la mthode
onCreate() de votre activit pour pouvoir utiliser ce positionnement :
setContentView(R.layout.main);
Il sagit du mme appel setContentView() que nous avions utilis prcdemment en lui
passant une instance dune sous-classe de View (un Button). La vue construite partir de
notre chier XML est dsormais accessible partir de la classe R. Tous les positionne-
ments dnis se trouvent sous R.layout, indiqus par le nom de base du chier
R.layout.main dsigne donc main.xml.
Pour accder nos widgets, nous utilisons ensuite la mthode findViewById(), en lui
passant lidentiant numrique du widget concern. Cet identiant a t produit par
Android dans la classe R et est de la forme R.id.qquechose (o qquechose est le widget
que vous recherchez). Ces widgets sont des sous-classes de View, exactement comme
linstance de Button que nous avions cre au Chapitre 4.


Fin de lhistoire
Dans le premier exemple Now, le texte du bouton afchait lheure laquelle on avait
appuy dessus pour la dernire fois (ou lheure laquelle lactivit avait t lance).
Lessentiel du code fonctionne encore, mme dans cette nouvelle version (que nous appel-
lerons NowRedux). Cependant, au lieu dinstancier le bouton dans la mthode onCreate(),
nous faisons rfrence celui qui est dcrit dans lagencement XML :
package com.commonsware.android.layouts;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import java.util.Date;
public class NowRedux extends Activity
implements View.OnClickListener {
Button btn;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);

setContentView(R.layout.main);
btn=(Button)findViewById(R.id.button);
btn.setOnClickListener(this);
updateTime();
}
public void onClick(View view) {
updateTime();
}
private void updateTime() {
btn.setText(new Date().toString());
}
}
La premire diffrence est quau lieu de crer la vue dans le code Java nous faisons rf-
rence au chier XML de positionnement (avec setContentView(R.layout.main)). Le
chier source R.java sera mis jour lorsque le projet sera recompil, an dinclure une
rfrence au chier de positionnement (main.xml du rpertoire res/layout).
La seconde diffrence est quil faut retrouver linstance de notre bouton en appelant la
mthode findViewById(). Comme lidentiant de ce bouton est @+id/button, nous
pouvons dsigner son identiant numrique par R.id.button. Il reste ensuite mettre en
place lcouteur dvnement et congurer son label.


La Figure 5.1 montre que le rsultat est le mme que celui de lexemple Now prcdent.
Figure 5.1
Lactivit NowRedux.


6
Utilisation des widgets
de base
Chaque kit de dveloppement graphique possde des widgets de base : champs de saisie,
labels, boutons, etc. Celui dAndroid ny fait pas exception et leur tude fournit une bonne
introduction au fonctionnement des widgets dans les activits Android.
Labels
Le label (TextView pour Android) est le widget le plus simple. Comme dans la plupart des
kits de dveloppement, les labels sont des chanes de textes non modiables par les utilisa-
teurs. Ils servent gnralement identier les widgets qui leur sont adjacents ("Nom : ",
par exemple, plac ct dun champ de saisie).
En Java, un label est une instance de la classe TextView. Cependant, ils seront le plus
souvent crs dans les chiers layout XML en ajoutant un lment TextView dot dune
proprit android:text pour dnir le texte qui lui est associ. Si vous devez changer
des labels en fonction dun certain critre linternationalisation, par exemple , vous
pouvez utiliser la place une rfrence de ressource dans le code XML, comme nous


lexpliquerons au Chapitre 9. Un lment TextView possde de nombreux autres attributs,
notamment :
android:typeface pour dnir le type de la police du label (monospace, par exemple) ;
android:textStyle pour indiquer si le texte doit tre en gras (bold), en italique
(italic) ou les deux (bold italic) ;
android:textColor pour dnir la couleur du texte du label, au format RGB hexa-
dcimal (#FF0000 pour un texte rouge, par exemple).
Voici le contenu du chier de positionnement du projet Basic/Label :
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Vous vous attendiez quelque chose de plus profond ?"
/>
Comme le montre la Figure 6.1, ce chier seul, avec le squelette Java fourni par la chane
de production dAndroid (android create project), produira lapplication voulue.
Boutons
Nous avons dj utilis le widget Button au Chapitre 4. Button tant une sous-classe de
TextView, tout ce qui a t dit dans la section prcdente concernant le formatage du texte
sapplique galement au texte dun bouton.
Figure 6.1
Lapplication LabelDemo.


Images
Android dispose de deux widgets permettant dintgrer des images dans les activits :
ImageView et ImageButton. Comme leur nom lindique, il sagit, respectivement, des
quivalents images de TextView et Button.
Chacun deux possde un attribut android:src permettant de prciser limage utilise.
Cet attribut dsigne gnralement une ressource graphique (voir le chapitre consacr aux
ressources). Vous pouvez galement congurer le contenu de limage en utilisant une URI
dun fournisseur de contenu, via un appel setImageURI().
ImageButton, une sous-classe dImageView, lui ajoute les comportements dun Button
standard pour rpondre aux clics et autres actions.
Examinons, par exemple, le contenu du chier main.xml du projet Basic/ImageView :
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/icon"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:adjustViewBounds="true"
android:src="@drawable/molecule"
/>
Le rsultat, qui utilise simplement lactivit produite automatiquement, est prsent la
Figure 6.2.
Figure 6.2
Lapplication
ImageViewDemo.


Champs de saisie
Outre les boutons et les labels, les champs de saisie forment le troisime pilier de la
plupart des outils de dveloppement graphiques. Avec Android, ils sont reprsents par le
widget EditText, qui est une sous-classe de TextView, dj vue pour les labels.
En plus des proprits standard de TextView (android:textStyle, par exemple), Edit
Text possde de nombreuses autres proprits ddies la construction des champs.
Parmi elles, citons :
android:autoText pour indiquer si le champ doit fournir une correction automatique
de lorthographe.
android:capitalize pour demander que le champ mette automatiquement en
majuscule la premire lettre de son contenu.
android:digits pour indiquer que le champ nacceptera que certains chiffres.
android:singleLine pour indiquer si la saisie ne seffectue que sur une seule ou
plusieurs lignes (autrement dit, <Enter> vous place-t-il sur le widget suivant ou ajoute-
t-il une nouvelle ligne ?).
Outre ces proprits, vous pouvez congurer les champs pour quils utilisent des mthodes
de saisie spcialises, avec les attributs android:numeric pour imposer une saisie unique-
ment numrique, android:password pour masquer la saisie dun mot de passe et
android:phoneNumber pour la saisie des numros de tlphone. Pour crer une mthode
de saisie particulire (an, par exemple, de saisir des codes postaux ou des numros de
scurit sociale), il faut implmenter linterface InputMethod puis congurer le champ
pour quil utilise cette mthode, laide de lattribut android:inputMethod.
Voici, par exemple, la description dun EditText tir du projet Basic/Field :
<?xml version="1.0" encoding="utf-8"?>
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/field"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:singleLine="false"
/>
Vous remarquerez que la valeur dandroid:singleLine est false, ce qui signie que les
utilisateurs pourront saisir plusieurs lignes de texte dans ce champ.
Le chier FieldDemo.java de ce projet remplit le champ de saisie avec un peu de prose :
package com.commonsware.android.basic;
import android.app.Activity;
import android.os.Bundle;


import android.widget.EditText;
public class FieldDemo extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

EditText fld=(EditText)findViewById(R.id.field);
fld.setText("Publie sous les termes de la licence Apache 2.0 " +
"(la \"Licence\") ; pour utiliser ce fichier " +
"vous devez respecter la Licence dont vous pouvez " +
"obtenir une copie a lURL " +
"http://www.apache.org/licenses/LICENSE-2.0");
}
}
La Figure 6.3 montre le rsultat obtenu.
Lmulateur dAndroid nautorise quune seule application dun mme paquetage
Java dans le lanceur (application Launcher). Comme tous les exemples de ce
chapitre appartiennent au paquetage com.commonsware.android.basic, il
napparatra quun seul exemple la fois dans le lanceur.
Certains champs offrent une compltion automatique an de permettre lutilisateur
dentrer des informations sans taper lintgralit du texte. Avec Android, ces champs sont
des widgets AutoCompleteTextView ; ils seront prsents au Chapitre 8.
Figure 6.3
Lapplication FieldDemo.
In
f
o


Cases cocher
La case cocher classique peut tre dans deux tats : coche ou dcoche. Un clic sur la
case inverse son tat pour indiquer un choix ("Livrer ma commande en urgence", par
exemple). Le widget CheckBox dAndroid permet dobtenir ce comportement. Comme il
drive de la classe TextView, les proprits de celles-ci comme android:textColor
permettent galement de formater ce widget.
Dans votre code Java, vous pouvez utiliser les mthodes suivantes :
isChecked() pour savoir si la case est coche ;
setChecked() pour forcer la case dans ltat coch ou dcoch ;
toggle() pour inverser ltat de la case, comme si lutilisateur avait cliqu dessus.
Vous pouvez galement enregistrer un objet couteur (il sagira, ici, dune instance
dOnCheckedChangeListener) pour tre prvenu des changements dtat de la case.
Voici la dclaration XML dune case cocher, tire du projet Basic/CheckBox :
<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cette case est: decochee" />
Le chier CheckBoxDemo.java correspondant rcupre cette case cocher et congure
son comportement :
public class CheckBoxDemo extends Activity
implements CompoundButton.OnCheckedChangeListener {
CheckBox cb;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

cb=(CheckBox)findViewById(R.id.check);
cb.setOnCheckedChangeListener(this);
}

public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
cb.setText("Cette case est cochee");
}
else {
cb.setText("Cette case est decochee");
}
}
}


Vous remarquerez que cest lactivit qui sert dcouteur pour les changements dtat de la
case cocher (avec cb.setOnCheckedChangeListener(this)) car elle implmente
linterface OnCheckedChangeListener.
La mthode de rappel de lcouteur est onCheckedChanged() : elle reoit la case qui a
chang dtat et son nouvel tat. Ici, on se contente de modier le texte de la case pour
reter son tat courant.
Cliquer sur la case modie donc immdiatement son texte, comme le montrent les
Figures 6.4 et 6.5.
Figure 6.4
Lapplication Check
BoxDemo avec la case
dcoche.
Figure 6.5
La mme application avec
la case coche.


Boutons radio
Comme dans les autres outils de dveloppement, les boutons radio dAndroid ont deux
tats, telles les cases cocher, mais peuvent tre regroups de sorte quun seul bouton
radio puisse tre coch par groupe un instant donn.
Comme CheckBox, RadioButton hrite de la classe CompoundButton, qui drive elle-
mme de TextView. Toutes les proprits standard de TextView pour la police, le style, la
couleur, etc. sappliquent donc galement aux boutons radio. Vous pouvez par consquent
appeler isChecked() sur un RadioButton pour savoir sil est coch, toggle() pour le
slectionner, etc. exactement comme avec une CheckBox.
La plupart du temps, les widgets RadioButton sont placs dans un conteneur RadioGroup
qui permet de lier les tats des boutons quil regroupe an quun seul puisse tre slec-
tionn un instant donn. En affectant un identiant android:id au RadioGroup dans le
chier de description XML, ce groupe devient accessible au code Java, qui peut alors lui
appliquer les mthodes suivantes :
check() pour tester un bouton radio partir de son identiant (avec groupe.check
(R.id.radio1)) ;
clearCheck() pour dcocher tous les boutons du groupe ;
getCheckedRadioButtonId() pour obtenir lidentiant du bouton radio actuellement
coch (cette mthode renvoie 1 si aucun bouton nest coch).
Voici, par exemple, une description XML dun groupe de boutons radio, tire de lexemple
Basic/RadioButton :
<?xml version="1.0" encoding="utf-8"?>
<RadioGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<RadioButton android:id="@+id/radio1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Caillou" />
<RadioButton android:id="@+id/radio2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ciseaux" />
<RadioButton android:id="@+id/radio3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Papier" />
</RadioGroup>


La Figure 6.6 montre le rsultat obtenu en utilisant le projet Java de base, fourni par
Android.
Vous remarquerez que, au dpart, aucun bouton du groupe nest coch. Pour que lapplica-
tion slectionne lun de ces boutons ds son lancement, il faut appeler soit la mthode
setChecked() sur le RadioButton concern, soit la mthode check() sur le RadioGroup
partir de la mthode onCreate() de lactivit.
Rsum
Tous les widgets que nous venons de prsenter drivent de la classe View et hritent donc
dun ensemble de proprits et de mthodes supplmentaires par rapport celles que nous
avons dj dcrites.
Proprits utiles
Parmi les proprits les plus utiles de View, citons :
Le contrle de la squence de focus :
android:nextFocusDown ;
android:nextFocusLeft ;
android:nextFocusRight ;
android:nextFocusUp.
Figure 6.6
Lapplication
RadioButtonDemo.


android:visibility, qui contrle la visibilit initiale du widget.
android:background, qui permet de fournir au widget une couleur de fond au format
RGB (#00FF00 pour vert, par exemple).
Mthodes utiles
La mthode setEnabled() permet de basculer entre ltat actif et inactif du widget, alors
que isEnabled() permet de tester si un widget est actif. On utilise souvent ces deux
mthodes pour dsactiver certains widgets en fonction de choix effectus laide de Check
Box ou de RadioButton.
La mthode requestFocus() donne le focus un widget et isFocused() permet de tester
sil a le focus. En utilisant les mthodes voques plus haut, on peut donc donner le focus
un widget prcis aprs une opration de dsactivation.
Les mthodes suivantes permettent de parcourir une arborescence de widgets et de conteneurs
composant la vue gnrale dune activit :
getParent() renvoie le widget ou le conteneur parent.
findViewById() permet de retrouver un widget ls daprs son identiant.
getRootView() renvoie la racine de larborescence (celle que vous avez fournie
lactivit via un appel setContentView()).


7
Conteneurs
Les conteneurs permettent de disposer un ensemble de widgets (et, ventuellement, des conte-
neurs ls) pour obtenir la prsentation de votre choix. Si, par exemple, vous prfrez placer
les labels gauche et les champs de saisie droite, vous aurez besoin dun conteneur. Si vous
voulez que les boutons OK et Annuler soient lun ct de lautre, en bas droite du formu-
laire, vous aurez galement besoin dun conteneur. Dun point de vue purement XML, si vous
manipulez plusieurs widgets (le cas des RadioButton dans un RadioGroup est particulier),
vous devrez utiliser un conteneur an de disposer dun lment racine dans lequel les placer.
La plupart des kits de dveloppement graphiques utilisent des gestionnaires de disposition
des widgets (layout managers) qui sont, le plus souvent, organiss sous forme de conte-
neurs. Java Swing, par exemple, dispose du gestionnaire BoxLayout, qui est utilis avec
certains conteneurs (comme Box). Dautres kits de dveloppement, comme XUL et Flex,
sen tiennent strictement au modle des botes, qui permet de crer nimporte quelle dispo-
sition via une combinaison adquate de botes imbriques.
Avec LinearLayout, Android offre galement un modle de disposition en botes, mais il
fournit aussi un grand nombre de conteneurs autorisant dautres systmes de composition.
Dans ce chapitre, nous tudierons trois conteneurs parmi les plus courants : Linear
Layout (le modle des botes), RelativeLayout (un modle de positionnement relatif)
et TableLayout (le modle en grille) ; nous prsenterons galement ScrollView, un
conteneur conu pour faciliter la mise en place des conteneurs avec barres de dlement.
Le chapitre suivant prsentera dautres conteneurs plus sotriques.


Penser de faon linaire
Comme on la dj mentionn, LinearLayout est un modle reposant sur des botes les
widgets ou les conteneurs ls sont aligns en colonnes ou en lignes, les uns aprs
les autres, exactement comme avec FlowLayout en Java Swing, et vbox et hbox en Flex et
XUL.
Avec Flex et XUL, la bote est lunit essentielle de disposition des widgets. Avec
Android, vous pouvez utiliser LinearLayout exactement de la mme faon, en vous
passant des autres conteneurs. Obtenir la disposition que vous souhaitez revient alors prin-
cipalement identier les imbrications et les proprits des diffrentes botes leur
alignement par rapport aux autres botes, par exemple.
Concepts et proprits
Pour congurer un LinearLayout, vous pouvez agir sur cinq paramtres : lorientation, le
modle de remplissage, le poids, la gravit et le remplissage.
Orientation
Lorientation prcise si le LinearLayout reprsente une ligne ou une colonne. Il suft
dajouter la proprit android:orientation llment LinearLayout du chier XML
en xant sa valeur horizontal pour une ligne ou vertical pour une colonne.
Cette orientation peut tre modie en cours dexcution en appelant la mthode set
Orientation()et en lui fournissant en paramtre la constante HORIZONTAL ou VERTICAL.
Modle de remplissage
Supposons que nous ayons une ligne de widgets une paire de boutons radio, par exem-
ple. Ces widgets ont une taille "naturelle" reposant sur celle de leur texte. Ces tailles
combines ne correspondent srement pas la largeur de lcran du terminal Android
notamment parce que les tailles des crans varient en fonction des modles. Il faut donc
savoir que faire de lespace restant.
Pour rsoudre ce problme, tous les widgets dun LinearLayout doivent fournir une
valeur pour les proprits android:layout width et android:layout height. Ces valeurs
peuvent sexprimer de trois faons diffrentes :
Une dimension prcise, comme 125 px, pour indiquer que le widget devra occuper
exactement 125 pixels.
wrap content, pour demander que le widget occupe sa place naturelle sauf sil est
trop gros, auquel cas Android coupera le texte entre les mots pour quil puisse tenir.
fill parent, pour demander que le widget occupe tout lespace disponible de son
conteneur aprs que les autres widgets eurent t placs.


Les valeurs les plus utilises sont les deux dernires, car elles sont indpendantes de la
taille de lcran ; Android peut donc ajuster la disposition pour quelle tienne dans
lespace disponible.
Poids
Que se passera-t-il si deux widgets doivent se partager lespace disponible ? Supposons,
par exemple, que nous ayons deux champs de saisie multilignes en colonne et que nous
voulions quils occupent tout lespace disponible de la colonne aprs le placement de tous
les autres widgets.
Pour ce faire, en plus dinitialiser android:layout width (pour les lignes) ou
android:layout height (pour les colonnes) avec fill parent, il faut galement
donner android:layout weight, une valeur qui indique la proportion despace libre
qui sera affecte au widget. Si cette valeur est la mme pour les deux widgets (1, par
exemple), lespace libre sera partag quitablement entre eux. Si la valeur est 1 pour un
widget et 2 pour lautre, le second utilisera deux fois plus despace libre que le premier,
etc.
Gravit
Par dfaut, les widgets salignent partir de la gauche et du haut. Si vous crez une ligne
avec un LinearLayout horizontal, cette ligne commencera donc se remplir partir du
bord gauche de lcran.
Si ce nest pas ce que vous souhaitez, vous devez indiquer une gravit laide de la
proprit android:layout gravity dun widget (ou en appelant la mthode setGra
vity() sur celui-ci) an dindiquer au widget et son conteneur comment laligner par
rapport lcran.
Pour une colonne de widgets, les gravits les plus courantes sont left, center hori
zontal et right pour, respectivement, aligner les widgets gauche, au centre ou droite.
Pour une ligne, le comportement par dfaut consiste placer les widgets de sorte que leur
texte soit align sur la ligne de base (la ligne invisible sur laquelle les lettres semblent
reposer), mais il est possible de prciser une gravit center vertical pour centrer verti-
calement les widgets dans la ligne.
Remplissage
Les widgets sont, par dfaut, serrs les uns contre les autres. Vous pouvez augmenter
lespace intercalaire laide de la proprit android:padding (ou en appelant la mthode
setPadding() de lobjet Java correspondant au widget).
La valeur de remplissage prcise lespace situ entre le contour de la "cellule" du widget et
son contenu rel. Elle est analogue aux marges dun document dans un traitement de texte


la taille de page peut tre de 21 29,7 cm, mais des marges de 2 cm connent le texte
dans une surface de 19 27,7 cm.
La proprit android:padding permet de prciser le mme remplissage pour les quatre
cts du widget ; son contenu tant alors centr dans la zone qui reste. Pour utiliser des
valeurs diffrentes en fonction des cts, utilisez les proprits android:paddingLeft,
android:paddingRight, android:paddingTop et android:paddingBottom (voir
Figure 7.1).
La valeur de ces proprits est une dimension, comme 5px pour demander un remplissage
de 5 pixels.
Exemple
Voici le chier de description XML de lexemple Containers/Linear, qui montre les
proprits de llment LinearLayout :
<?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"
>
<RadioGroup android:id="@+id/orientation"
android:orientation="horizontal"
android:layout_width="wrap_content"
Figure 7.1
Relations entre
un widget, sa cellule
et ses valeurs
de remplissage.


android:layout_height="wrap_content"
android:padding="5px">
<RadioButton
android:id="@+id/horizontal"
android:text="horizontal" />
<RadioButton
android:id="@+id/vertical"
android:text="vertical" />
</RadioGroup>
<RadioGroup android:id="@+id/gravity"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="5px">
<RadioButton
android:id="@+id/left"
android:text="gauche" />
<RadioButton
android:id="@+id/center"
android:text="centre" />
<RadioButton
android:id="@+id/right"
android:text="droite" />
</RadioGroup>
</LinearLayout>
Vous remarquerez que le conteneur LinearLayout enveloppe deux RadioGroup. Radio
Group tant une sous-classe de LinearLayout, notre exemple revient donc imbriquer
des conteneurs LinearLayout.
Le premier lment RadioGroup congure une ligne (android:orientation = "hori
zontal") de widgets RadioButton. Il utilise un remplissage de 5 pixels sur ses quatre
cts, an de le sparer de lautre RadioGroup. Sa largeur et sa hauteur valent toutes les
deux wrap content pour que les boutons radio noccupent que lespace dont ils ont
besoin.
Le deuxime RadioGroup est une colonne (android:orientation = "vertical") de
trois RadioButton. Il utilise galement un remplissage de 5 pixels sur tous ses cts
et sa hauteur est "naturelle" (android:layout height = "wrap content"). Cependant, sa
proprit android:layout width vaut fill parent, ce qui signie que la colonne
de boutons radio "rclamera" toute la largeur de lcran.
Pour ajuster ces valeurs en cours dexcution en fonction de la saisie de lutilisateur, il faut
utiliser un peu de code Java :
package com.commonsware.android.containers;
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;


import android.text.TextWatcher;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.EditText;
public class LinearLayoutDemo extends Activity
implements RadioGroup.OnCheckedChangeListener {
RadioGroup orientation;
RadioGroup gravity;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

orientation=(RadioGroup)findViewById(R.id.orientation);
orientation.setOnCheckedChangeListener(this);
gravity=(RadioGroup)findViewById(R.id.gravity);
gravity.setOnCheckedChangeListener(this);
}

public void onCheckedChanged(RadioGroup group, int checkedId) {
if (group==orientation) {
if (checkedId==R.id.horizontal) {
orientation.setOrientation(LinearLayout.HORIZONTAL);
}
else {
orientation.setOrientation(LinearLayout.VERTICAL);
}
}
else if (group==gravity) {
if (checkedId==R.id.left) {
gravity.setGravity(Gravity.LEFT);
}
else if (checkedId==R.id.center) {
gravity.setGravity(Gravity.CENTER_HORIZONTAL);
}
else if (checkedId==R.id.right) {
gravity.setGravity(Gravity.RIGHT);
}
}
}
}
Dans onCreate(), nous recherchons nos deux conteneurs RadioGroup et nous enregistrons
un couteur pour chacun deux an dtre prvenu du changement dtat des boutons radio
(setOnCheckedChangeListener(this)). Lactivit implmentant linterface OnChecked
ChangeListener, elle se comporte elle-mme comme un couteur.


Dans onCheckedChanged() (la mthode de rappel pour lcouteur), on recherche le
RadioGroup dont ltat a chang. Sil sagit du groupe orientation, on ajuste lorienta-
tion en fonction du choix de lutilisateur. Sil sagit du groupe gravity, on modie la
gravit.
La Figure 7.2 montre ce quafche lapplication lorsquelle est lance dans lmulateur.
Si lon clique sur le bouton vertical, le RadioGroup du haut sajuste en consquence
(voir Figure 7.3).
Figure 7.2
Lapplication Linear
LayoutDemo lors
de son lancement.
Figure 7.3
La mme application,
aprs avoir cliqu sur
le bouton vertical.


Si lon clique sur les boutons centre ou droite, le RadioGroup du bas sajuste galement
(voir Figures 7.4 et 7.5).
Tout est relatif
Comme son nom lindique, le RelativeLayout place les widgets relativement aux autres
widgets du conteneur et de son conteneur parent. Vous pouvez ainsi placer le widget X en
dessous et gauche du widget Y ou faire en sorte que le bord infrieur du widget Z soit
Figure 7.4
La mme application,
avec les boutons
vertical et centre
cochs.
Figure 7.5
La mme application,
avec les boutons
vertical et droite
cochs.


align avec le bord infrieur du conteneur, etc. Ce gestionnaire de placement ressemble
donc au conteneur RelativeLayout
1
de James Elliot pour Java Swing.
Concepts et proprits
Il faut pouvoir faire rfrence dautres widgets dans le chier de description XML et
disposer dun moyen dindiquer leurs positions relatives.
Positions relatives un conteneur
Les relations les plus simples mettre en place sont celles qui lient la position dun widget
celle de son conteneur :
android:layout alignParentTop prcise que le haut du widget doit tre align avec
celui du conteneur.
android:layout alignParentBottom prcise que le bas du widget doit tre align
avec celui du conteneur.
android:layout alignParentLeft prcise que le bord gauche du widget doit tre
align avec le bord gauche du conteneur.
android:layout alignParentRight prcise que le bord droit du widget doit tre
align avec le bord droit du conteneur.
android:layout centerHorizontal prcise que le widget doit tre centr horizon-
talement dans le conteneur.
android:layout centerVertical prcise que le widget doit tre centr verticalement
dans le conteneur.
android:layout centerInParent prcise que le widget doit tre centr horizontalement
et verticalement dans le conteneur.
Toutes ces proprits prennent soit la valeur true, soit la valeur false.
Le remplissage du widget est pris en compte lors de ces alignements. Ceux-ci repo-
sent sur la cellule globale du widget (cest--dire sur la combinaison de sa taille
naturelle et de son remplissage).
Notation relative dans les proprits
Les proprits restantes concernant RelativeLayout ont comme valeur lidentit dun
widget du conteneur. Pour ce faire :
1. Associez des identiants (attributs android:id) tous les lments que vous aurez
besoin de dsigner, sous la forme @+id/....
1. http://www.onjava.com/pub/a/onjava/2002/09/18/relativelayout.html.
In
f
o


2. Dsignez un widget en utilisant son identiant, priv du signe plus (@id/...).
Si, par exemple, le widget A est identi par @+id/widget a, le widget B peut le dsigner
dans lune de ses proprits par @id/widget a.
Positions relatives aux autres widgets
Quatre proprits permettent de contrler la position dun widget par rapport aux autres :
android:layout above indique que le widget doit tre plac au-dessus de celui qui
est dsign dans cette proprit.
android:layout below indique que le widget doit tre plac sous celui qui est dsign
dans cette proprit.
android:layout toLeftOf indique que le widget doit tre plac gauche de celui
qui est dsign dans cette proprit.
android:layout toRightOf indique que le widget doit tre plac droite de celui
qui est dsign dans cette proprit.
Cinq autres proprits permettent de contrler lalignement dun widget par rapport un
autre :
android:layout alignTop indique que le haut du widget doit tre align avec le haut
du widget dsign dans cette proprit.
android:layout alignBottom indique que le bas du widget doit tre align avec le
bas du widget dsign dans cette proprit.
android:layout alignLeft indique que le bord gauche du widget doit tre align
avec le bord gauche du widget dsign dans cette proprit.
android:layout alignRight indique que le bord droit du widget doit tre align
avec le bord droit du widget dsign dans cette proprit.
android:layout alignBaseline indique que les lignes de base des deux widgets
doivent tre alignes.
La dernire proprit de cette liste permet daligner des labels et des champs an que le
texte semble "naturel". En effet, les champs de saisie tant matrialiss par une bote,
contrairement aux labels, android:layout alignTop alignerait le haut de la bote du
champ avec le haut du label, ce qui ferait apparatre le texte du label plus haut dans lcran
que le texte saisi dans le champ.
Si lon souhaite que le widget B soit plac droite du widget A, llment XML du
widget B doit donc contenir android:layout toRight = "@id/widget a" (o @id/
widget a est lidentiant du widget A).


Ordre dvaluation
Lordre dvaluation complique encore les choses. En effet, Android ne lit quune seule
fois le chier XML et calcule donc en squence la taille et la position de chaque widget.
Ceci a deux consquences :
On ne peut pas faire rfrence un widget qui na pas t dni plus haut dans le chier.
Il faut vrier que lutilisation de la valeur fill parent pour android:layout width
ou android:layout height ne "consomme" pas tout lespace alors que lon na pas
encore dni tous les widgets.
Exemple
titre dexemple, tudions un "formulaire" classique, compos dun champ, dun label et
de deux boutons, OK et Annuler.
Voici le chier de disposition XML du projet Containers/Relative :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="5px">
<TextView android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="URL:"
android:paddingTop="15px"/>
<EditText
android:id="@+id/entry"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/label"
android:layout_alignBaseline="@id/label"/>
<Button
android:id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/entry"
android:layout_alignRight="@id/entry"
android:text="Ok" />
<Button
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ok"
android:layout_alignTop="@id/ok"
android:text="Annuler" />
</RelativeLayout>


Nous entrons dabord dans llment RelativeLayout. Ici, on veut utiliser toute la largeur
de lcran (android:layout width = "fill parent"), nutiliser que la hauteur nces-
saire (android:layout height = "wrap content"), avec un remplissage de 5 pixels
entre les limites du conteneur et son contenu (android:padding = "5px").
Puis nous dnissons un label assez basique, hormis son remplissage de 15 pixels
(android:padding = "15px"), que nous expliquerons plus loin.
Nous ajoutons ensuite le champ que nous voulons placer droite du label, avec sa ligne de
base aligne avec celle du label et nous faisons en sorte quil occupe le reste de cette
"ligne". Ces trois caractristiques sont gres par trois proprits :
android:layout toRight = "@id/label" ;
android:layout alignBaseline = "@id/label" ;
android:layout width = "fill parent".
Si nous navions pas utilis le remplissage de 15 pixels pour le label, le haut du champ
serait coup cause du remplissage de 5 pixels du conteneur lui-mme. En effet, la
proprit android: layout alignBaseline = "@id/label" se contente daligner
les lignes de base du label et du champ. Par dfaut, le haut du label est align avec le haut
de son parent, or il est plus petit que le champ puisque ce dernier est entour dune bote.
Le champ dpendant de la position du label qui a dj t dnie (puisquil apparat avant
lui dans le chier XML), le champ serait trop haut et son bord suprieur serait rogn par le
remplissage du conteneur.
Vous rencontrerez probablement ce genre de problme lorsque vous voudrez mettre en
place des RelativeLayout pour quils apparaissent exactement comme vous le souhaitez.
La solution ce casse-tte consiste, comme nous lavons vu, ajouter 15 pixels de remplis-
sage au-dessus du label, an de le pousser sufsamment vers le bas pour que le champ ne
soit pas coup.
Voici quelques "solutions" qui ne marchent pas :
Vous ne pouvez pas utiliser android:layout alignParentTop sur le champ car il
ne peut pas y avoir deux proprits qui tentent en mme temps de dnir la position
verticale du champ. Ici, android:layout alignParentTop entrerait en conit avec la
proprit android:layout alignBaseline = "@id/label", qui apparat plus bas, et
cest cette dernire qui lemporterait. Vous devez donc soit aligner le haut, soit aligner
les lignes de base, mais pas les deux.
Vous ne pouvez pas dabord dnir le champ puis placer le label sa gauche car on ne
peut pas faire de "rfrence anticipe" vers un widget il doit avoir t dni avant de
pouvoir y faire rfrence.
Revenons notre exemple. Le bouton OK est plac sous le champ (android:layout below
= "@id/entry") et son bord droit est align avec le bord droit du champ


(android:layout alignRight = "@id/entry"). Le bouton Annuler est plac gauche
du bouton OK (android:layout toLeft = "@id/ok") et son bord suprieur est align
avec celui de son voisin (android:layout alignTop = "@id/ok").
La Figure 7.6 montre le rsultat afch dans lmulateur lorsque lon se contente du code
Java produit automatiquement.
Tabula Rasa
Si vous aimez les tableaux HTML ou les feuilles de calcul, vous apprcierez le conteneur
TableLayout dAndroid car il vous permet de positionner les widgets dans une grille.
Vous pouvez ainsi dnir le nombre de lignes et de colonnes, les colonnes qui peuvent se
rduire ou sagrandir en fonction de leur contenu, etc.
TableLayout fonctionne de concert avec le conteneur TableRow. Alors que TableLayout
contrle le comportement global du conteneur, les widgets eux-mmes sont placs dans un
ou plusieurs TableRow, raison dun par ligne de la grille.
Concepts et proprits
Pour utiliser ce conteneur, il faut savoir grer les widgets en lignes et en colonnes, et traiter
ceux qui sont placs lextrieur des lignes.
Figure 7.6
Lapplication Relative
LayoutDemo.


Placement des cellules dans les lignes
Cest vous, le dveloppeur, qui dclarez les lignes en plaant les widgets comme des ls
dun lment TableRow, lui-mme ls dun TableLayout. Vous contrlez donc directement
la faon dont apparaissent les lignes dans le tableau.
Cest Android qui dtermine automatiquement le nombre de colonnes mais, en fait, vous
le contrlez de faon indirecte.
Il y aura au moins autant de colonnes quil y a de widgets dans la ligne la plus longue. Sil
y a trois lignes, par exemple une ligne avec deux widgets, une avec trois widgets et une
autre avec quatre widgets , il y aura donc au moins quatre colonnes.
Cependant, un widget peut occuper plusieurs colonnes si vous utilisez la proprit
android:layout span en lui prcisant le nombre de colonnes sur lesquelles doit sten-
dre le widget concern. Cette proprit ressemble donc lattribut colspan utilis dans les
tableaux HTML :
<TableRow>
<TextView android:text="URL:" />
<EditText
android:id="@+id/entry"
android:layout_span="3"/>
</TableRow>
Avec ce fragment XML, le champ stendra sur trois colonnes.
Gnralement, les widgets sont placs dans la premire colonne disponible. Dans lextrait
prcdent, par exemple, le label irait dans la premire colonne (la colonne 0 car leur num-
rotation commence 0) et le champ stendrait sur les trois colonnes suivantes (les colon-
nes 1 3). Vous pouvez galement placer un widget sur une colonne prcise en vous
servant de la proprit android:layout column et en lui indiquant le numro de colonne
voulu :
<TableRow>
<Button
android:id="@+id/cancel"
android:layout_column="2"
android:text="Annuler" />
<Button android:id="@+id/ok" android:text="Ok" />
</TableRow>
Avec cet extrait, le bouton Annuler sera plac dans la troisime colonne (la colonne 2) et le
bouton OK, dans la colonne disponible suivante, cest--dire la quatrime.


Fils de TableLayout qui ne sont pas des lignes
Gnralement, les seuls ls directs de TableLayout sont des lments TableRow. Cepen-
dant, vous pouvez galement placer des widgets entre les lignes. En ce cas, TableLayout
se comporte un peu comme un conteneur LinearLayout ayant une orientation verticale.
Les largeurs de ces widgets seront automatiquement xes fill parent pour remplir le
mme espace que la ligne la plus longue.
Un cas dutilisation de cette conguration consiste se servir dun widget View (<View
android:layout height = "2px" android:background = "#0000FF" />) pour crer
une barre de sparation bleue de 2 pixels et de la mme largeur que le tableau.
Rduire, tirer et refermer
Par dfaut, la taille de chaque colonne sera la taille "naturelle" de son widget le plus large
(en tenant compte des widgets qui stendent sur plusieurs colonnes). Parfois, cependant,
cela ne donne pas le rsultat escompt et il faut alors intervenir plus prcisment sur le
comportement de la colonne.
Pour ce faire, vous pouvez utiliser la proprit android:stretchColumns de llment
TableLayout, dont la valeur peut tre un seul numro de colonne (dbutant zro) ou une
liste de numros de colonnes spars par des virgules. Ces colonnes seront alors tires
pour occuper tout lespace disponible de la ligne, ce qui est utile lorsque votre contenu est
plus troit que lespace restant.
Inversement, la proprit android:shrinkColumns de TableLayout, qui prend les
mmes valeurs, permet de rduire la largeur effective des colonnes en dcoupant leur
contenu en plusieurs lignes (par dfaut, le contenu des widgets nest pas dcoup).
Cela permet dviter quun contenu trop long pousse certaines colonnes droite de
lcran.
Vous pouvez galement tirer parti de la proprit android:collapseColumns de Table
Layout, en indiquant l aussi un numro ou une liste de numros de colonnes. Celles-ci
seront alors initialement "refermes", ce qui signie quelles napparatront pas, bien
quelles fassent partie du tableau. partir de votre programme, vous pouvez refermer ou
rouvrir les colonnes laide de la mthode setColumnCollapsed()du widget Table
Layout. Ceci permet aux utilisateurs de contrler les colonnes importantes qui doivent
apparatre et celles qui peuvent tre caches car elles ne leur sont pas utiles.
En outre, les mthodes setColumnStretchable() et setColumnShrinkable() permettent
respectivement de contrler ltirement et la rduction des colonnes en cours dexcution.
Exemple
En combinant les fragments XML prsents dans cette section, on produit une grille de
widgets ayant la mme forme que celle de lexemple RelativeLayoutDemo, mais avec


une ligne de sparation entre la ligne label/champ et celle des boutons (ce projet se trouve
dans le rpertoire Containers/Table) :
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="1">
<TableRow>
<TextView
android:text="URL:" />
<EditText android:id="@+id/entry"
android:layout_span="3"/>
</TableRow>
<View
android:layout_height="2px"
android:background="#0000FF" />
<TableRow>
<Button android:id="@+id/cancel"
android:layout_column="2"
android:text="Annuler" />
<Button android:id="@+id/ok"
android:text="Ok" />
</TableRow>
</TableLayout>
On obtient alors le rsultat montr la Figure 7.7.
Figure 7.7
Lapplication
TableLayoutDemo.


ScrollView
Les crans des tlphones sont gnralement assez petits, ce qui oblige les dveloppeurs
employer quelques astuces pour prsenter beaucoup dinformations dans un espace rduit.
Lune de ces astuces consiste utiliser le dlement, an que seule une partie de linfor-
mation soit visible un instant donn, le reste tant disponible en faisant dler lcran
vers le haut ou vers le bas.
ScrollView est un conteneur qui fournit un dlement son contenu. Vous pouvez donc
utiliser un gestionnaire de disposition qui peut produire un rsultat trop grand pour
certains crans et lenvelopper dans un ScrollView tout en continuant dutiliser la logique
de ce gestionnaire. Lutilisateur ne verra alors quune partie de votre prsentation et aura
accs au reste via des barres de dlement.
Voici par exemple un lment ScrollView qui enveloppe un TableLayout (ce chier
XML est extrait du rpertoire Containers/Scroll) :
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TableLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="0">
<TableRow>
<View
android:layout_height="80px"
android:background="#000000"/>
<TextView android:text="#000000"
android:paddingLeft="4px"
android:layout_gravity="center_vertical" />
</TableRow>
<TableRow>
<View
android:layout_height="80px"
android:background="#440000" />
<TextView android:text="#440000"
android:paddingLeft="4px"
android:layout_gravity="center_vertical" />
</TableRow>
<TableRow>
<View
android:layout_height="80px"
android:background="#884400" />


<TextView android:text="#884400"
android:paddingLeft="4px"
android:layout_gravity="center_vertical" />
</TableRow>
<TableRow>
<View
android:layout_height="80px"
android:background="#aa8844" />
<TextView android:text="#aa8844"
android:paddingLeft="4px"
android:layout_gravity="center_vertical" />
</TableRow>
<TableRow>
<View
android:layout_height="80px"
android:background="#ffaa88" />
<TextView android:text="#ffaa88"
android:paddingLeft="4px"
android:layout_gravity="center_vertical" />
</TableRow>
<TableRow>
<View
android:layout_height="80px"
android:background="#ffffaa" />
<TextView android:text="#ffffaa"
android:paddingLeft="4px"
android:layout_gravity="center_vertical" />
</TableRow>
<TableRow>
<View
android:layout_height="80px"
android:background="#ffffff" />
<TextView android:text="#ffffff"
android:paddingLeft="4px"
android:layout_gravity="center_vertical" />
</TableRow>
</TableLayout>
</ScrollView>
Sans le ScrollView, la grille occuperait au moins 560 pixels (sept lignes de 80 pixels
chacune, selon la dnition de llment View). Certains terminaux peuvent avoir des
crans capables dafcher autant dinformations, mais la plupart seront plus petits. Le
ScrollView permet alors de conserver la grille tout en en prsentant quune partie la
fois.
La Figure 7.8 montre ce quafchera lmulateur dAndroid au lancement de lactivit.


Vous remarquerez que lon ne voit que cinq lignes, ainsi quune partie de la sixime. En
pressant le bouton bas du pad directionnel, vous pouvez faire dler lcran an de faire
apparatre les lignes restantes. Vous remarquerez galement que le bord droit du contenu
est masqu par la barre de dlement pour viter ce problme, vous pourriez ajouter des
pixels de remplissage sur ce ct.
Figure 7.8
Lapplication Scroll
ViewDemo.


8
Widgets de slection
Au Chapitre 6, nous avons vu que les champs pouvaient imposer des contraintes sur leur
contenu possible, an de forcer une saisie uniquement numrique ou pour obliger saisir
un numro de tlphone, par exemple. Ce type de contrainte aide lutilisateur "faire ce
quil faut" lorsquil entre des informations, notamment lorsquil sagit dun terminal
mobile avec un clavier exigu.
La contrainte de saisie ultime consiste, videmment, ne proposer quune option possible
parmi un ensemble de choix, ce qui peut tre ralis laide des boutons radio que nous
avons dj prsents. Les kits de dveloppement graphiques disposent galement de listes
droulantes, et Android leur ajoute des widgets particulirement adapts aux dispositifs
mobiles (Gallery, par exemple, permet dexaminer les photographies stockes sur le
terminal).
En outre, Android permet de connatre aisment les choix qui sont proposs dans ces
widgets. Plus prcisment, il dispose dun ensemble dadaptateurs permettant de fournir
une interface commune toutes les listes de choix, que ce soient des tableaux statiques ou
des bases de donnes. Les vues de slection les widgets pour prsenter les listes de choix
sont transmises un adaptateur pour fournir les choix possibles.


Sadapter aux circonstances
Dans labsolu, les adaptateurs offrent une interface commune pour diffrentes API. Plus
prcisment, dans le cas dAndroid, ils fournissent une interface commune au modle de
donnes sous-jacent dun widget de slection comme une liste droulante. Cette utilisation
des interfaces Java est assez classique (voir, par exemple, les adaptateurs de modles pour
JTable en Java/Swing), et Java nest pas le seul environnement fournir ce type
dabstraction (le framework XML de Flex accepte indiffremment du code XML en ligne
ou tlcharg partir dInternet).
Les adaptateurs dAndroid se chargent de fournir la liste des donnes dun widget de
slection et de convertir les diffrents lments en vues spciques pour quelles saf-
chent dans ce widget de slection. Ce dernier aspect des adaptateurs peut sembler un peu
curieux mais, en ralit, il nest pas si diffrent de ce que proposent les autres kits de dve-
loppement graphiques pour rednir lafchage par dfaut. En Java/Swing, par exemple,
si vous souhaitez quune liste implmente par une JList soit, en ralit, une liste
cocher (o les diffrentes lignes sont composes dune case cocher et dun label et o les
clics modient ltat de cette liste), vous nirez invitablement par appeler la mthode
setCellRenderer() pour disposer dun objet ListCellRenderer qui, son tour, permet
de convertir le contenu dune liste en widgets composites JCheckBox-plus-JLabel.
Utilisation dArrayAdapter
Ladaptateur le plus simple est ArrayAdapter puisquil suft denvelopper un tableau ou
une instance de java.util.List pour disposer dun adaptateur prt fonctionner :
String[] items = {"ceci", "est", "une",
"liste", "vraiment", "stupide"};
new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, items);
Le constructeur dArrayAdapter attend trois paramtres :
le contexte dutilisation (gnralement, il sagit de linstance de lactivit) ;
lidentiant de ressource de la vue utiliser ;
le tableau ou la liste dlments afcher.
Par dfaut, ArrayAdapter appellera la mthode toString() des objets de la liste et enve-
loppera chaque chane ainsi obtenue dans la vue dsigne par la ressource indique.
android.R.layout.simple list item 1 se contente de transformer ces chanes en
objets TextView qui, leur tour, safcheront dans la liste, le spinner ou tout widget qui
utilise cet ArrayAdapter. Vous pouvez confectionner vos propres vues en crant une sous-
classe dArrayAdapter pour rednir sa mthodegetView() :


public View getView(int position, View convertView,
ViewGroup parent) {
if (convertView==null) {
convertView=new TextView(this);
}
convertView.setText(buildStringFor(position));
return(convertView);
}
Ici, getView() reoit trois paramtres :
Lindice de llment du tableau que lon veut afcher dans la vue.
Une vue existante qui sera modie avec les donnes cette position (si ce paramtre
vaut null, vous devrez crer votre propre instance).
Le widget qui contiendra cette vue, sil faut linstancier.
Dans lexemple prcdent, ladaptateur renvoie quand mme un objet TextView mais
utilise un comportement diffrent pour savoir quelle chane sera place dans la vue.
Le Chapitre 9 prsentera des ListView plus labores.
Autres adaptateurs essentiels
Voici dautres adaptateurs dont vous aurez certainement besoin :
CursorAdapter convertit un Cursor, gnralement fourni par un content provider, en
un objet pouvant safcher dans une vue de slection.
SimpleAdapter convertit les donnes trouves dans les ressources XML.
ActivityAdapter et ActivityIconAdapter fournissent les noms ou les icnes des
activits qui peuvent tre appeles lors dune intention particulire.
Listes des bons et des mchants
Le widget classique dAndroid pour les listes sappelle ListView. Pour disposer dune
liste compltement fonctionnelle, il suft dinclure un objet ListView dans votre prsen-
tation, dappeler setAdapter() pour fournir les donnes et les vues lles, puis dattacher
un couteur via setOnItemSelectedListener() pour tre prvenu de toute modication
de la slection.
Cependant, si votre activit est pilote par une seule liste, il peut tre prfrable que cette
activit soit une sous-classe de ListActivity plutt que de la classe de base Activity
traditionnelle. Si votre vue principale est uniquement constitue de la liste, vous navez
mme pas besoin de fournir de layout ListActivity construira pour vous une liste qui
occupera tout lcran. Vous pouvez toutefois personnaliser cette prsentation condition
didentier cette ListView par @android:id/list, an que ListActivity sache quelle
est la liste principale de lactivit.


Voici, par exemple, le chier de disposition du projet Selection/List :
<?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" >
<TextView
android:id="@+id/selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="false"
/>
</LinearLayout>
Comme vous pouvez le constater, il sagit simplement dune liste surmonte dun label qui
devra afcher en permanence la slection courante.
Le code Java permettant de congurer cette liste et de la connecter au label est le suivant :
public class ListViewDemo extends ListActivity {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue", "purus"};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
items));
selection=(TextView)findViewById(R.id.selection);
}

public void onListItemClick(ListView parent, View v, int position,
long id) {
selection.setText(items[position]);
}
}


Vous pouvez congurer ladaptateur dune ListActivity par un appel setListAdapter()
ici, on fournit un ArrayAdapter qui enveloppe un tableau de chanes quelconques.
Pour tre prvenu des changements dans la liste de slection, on rednit onListItem
Click() pour quelle agisse de faon approprie en tenant compte de la vue lle et de la
position qui lui sont passes en paramtre (ici, elle crit dans le label le texte situ cette
position).
Le second paramtre de notre ArrayAdapter android.R.layout.simple list item 1
contrle laspect des lignes. La valeur utilise dans lexemple prcdent fournit une ligne
Android standard : grande police, remplissage important et texte en blanc.
Le rsultat est montr la Figure 8.1.
Modes de slection
Par dfaut, ListView est simplement congure pour recevoir les clics sur les entres
de la liste. Cependant, on a parfois besoin quune liste mmorise un ou plusieurs choix
de lutilisateur ; ListView permet galement de le faire, au prix de quelques modi-
cations.
Dans le code Java, vous devez dabord appeler la mthode setChoiceMode() de lobjet
ListView an de congurer le mode de slection en lui passant en paramtre la constante
CHOICE MODE SINGLE ou CHOICE MODE MULTIPLE (pour obtenir lobjet ListView, il
suft dappeler la mthode getListView() partir dune ListActivity).
Figure 8.1
Lapplication
ListViewDemo.


Puis, au lieu de passer en paramtre android.R.layout.simple list item 1 au
constructeur dArrayAdapter, vous devrez lui passer soit android.R.layout.sim
ple list item single choice, soit android.R.layout.simple list item mul
tiple choice pour mettre en place, respectivement, une liste choix unique ou
choix multiples.
Vous obtiendrez alors un rsultat comme celui des Figures 8.2 ou 8.3.
Figure 8.2
Liste choix unique.
Figure 8.3
Liste choix multiple.


Pour connatre les choix de lutilisateur, utilisez la mthode getCheckedItemPositions()
de la ListView.
Contrle du Spinner
Le Spinner dAndroid est lquivalent des botes droulantes que lon trouve dans
certains kits de dveloppement (JComboBox en Java/Swing, par exemple). Appuyer sur le
bouton central du pad du terminal fait surgir une bote de slection permettant lutilisa-
teur de faire son choix. On peut ainsi choisir dans une liste sans occuper tout lcran
comme avec une ListView, mais au prix dun clic supplmentaire ou dun pointage sur
lcran.
Comme pour ListView, on fournit ladaptateur pour les donnes et les vues lles via
setAdapter() et on accroche un couteur avec setOnItemSelectedListener().
Si lon souhaite personnaliser la vue dafchage de la bote droulante, il faut congurer
ladaptateur, pas le widget Spinner. Pour ce faire, on a donc besoin de la mthode
setDropDownViewResource() an de fournir lidentiant de la vue concerne.
Voici par exemple le chier de disposition du projet Selection/Spinner, qui permet de
mettre en place une vue simple contenant un Spinner :
<?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"
>
<TextView
android:id="@+id/selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Spinner android:id="@+id/spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
/>
</LinearLayout>
Il sagit de la mme vue que celle de la section prcdente, mais avec Spinner la place
de ListView. La proprit android:drawSelectorOnTop indique que la che permet-
tant de drouler la slection se trouvera droite du Spinner.


Voici le code Java permettant de remplir et dutiliser le Spinner :
public class SpinnerDemo extends Activity
implements AdapterView.OnItemSelectedListener {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue", "purus"};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
selection=(TextView)findViewById(R.id.selection);

Spinner spin=(Spinner)findViewById(R.id.spinner);
spin.setOnItemSelectedListener(this);

ArrayAdapter<String> aa=new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item,
items);

aa.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
spin.setAdapter(aa);
}

public void onItemSelected(AdapterView<?> parent,
View v, int position, long id) {
selection.setText(items[position]);
}

public void onNothingSelected(AdapterView<?> parent) {
selection.setText("");
}
}
Ici, cest lactivit elle-mme qui sert dcouteur de slection (spin.setOnItemSelectedLis
tener(this)), ce qui est possible car elle implmente linterface OnItemSelectedListener.
On congure ladaptateur non seulement avec une liste de mots quelconques mais gale-
ment avec une ressource spcique qui servira la vue droulante (via aa.setDropDown
ViewResource()). Vous remarquerez galement que lon utilise la vue prdnie
android.R.layout.simple spinner item pour afcher les lments du Spinner.
Enn, on implmente les mthodes de rappels ncessaires dOnItemSelectedListener
pour que le contenu du label volue en fonction du choix de lutilisateur.
On obtient ainsi le rsultat prsent aux Figures 8.4 et 8.5.


Mettez vos lions en cage
Comme son nom lindique, GridView vous offre une grille dans laquelle vous pouvez
disposer vos choix. Vous avez un contrle limit sur le nombre et la taille des colonnes ; le
nombre de lignes est dtermin dynamiquement en fonction du nombre dlments rendus
disponibles par ladaptateur fourni.
Figure 8.4
Lapplication
SpinnerDemo lors
de son lancement.
Figure 8.5
La mme application
avec afchage de la liste
droulante du Spinner.


Voici les proprits qui, combines, dterminent le nombre et la taille des colonnes :
android:numColumns indique le nombre de colonnes ; si sa valeur est auto fit,
Android calculera ce nombre en fonction de lespace disponible et de la valeur des
autres proprits.
android:verticalSpacing et son homologue android:horizontalSpacing prcisent
lespace sparant les lments de la grille.
android:columnWidth indique la largeur de chaque colonne en pixels.
android:stretchMode indique, pour les grilles dont la valeur dandroid:numColu
mns est auto fit, ce qui devra se passer lorsquil reste de lespace non occup par des
colonnes ou des espaces de sparation : si sa valeur est columnWidth, cet espace dispo-
nible sera pris par les colonnes ; si elle vaut spacingWidth, il sera absorb par lespa-
cement entre les colonnes. Supposons, par exemple, que lcran fasse 320 pixels de
large, que la valeur dandroid:columnWidth soit de 100px et celle dandroid:hori
zontalSpacing, de 5px : trois colonnes occuperaient donc 310 pixels (trois colonnes
de 100 pixels et deux sparations de 5 pixels). Si android:stretchMode vaut colum
nWidth, les trois colonnes slargiront de 3-4 pixels pour utiliser les 10 pixels restants ;
si android:stretchMode vaut spacingWidth, les deux espacements slargiront
chacun de 5 pixels pour absorber ces 10 pixels.
Pour le reste, GridView fonctionne exactement comme nimporte quel autre widget de
slection on utilise setAdapter() pour fournir les donnes et les vues lles, on appelle
setOnItemSelectedListener() pour enregistrer un couteur de choix, etc.
Voici, par exemple, le chier de disposition XML du projet Selection/Grid :
<?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"
>
<TextView
android:id="@+id/selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<GridView
android:id="@+id/grid"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:verticalSpacing="35px"
android:horizontalSpacing="5px"
android:numColumns="auto_fit"
android:columnWidth="100px"


android:stretchMode="columnWidth"
android:gravity="center"
/>
</LinearLayout>
Cette grille occupe tout lcran, sauf la partie rserve au label qui afche la slection
courante. Le nombre de colonnes est calcul par Android (android:numColumns =
"auto fit") partir dun espacement horizontal de 5 pixels (android:horizontalSpacing
= "5px") et dune largeur de colonne de 100 pixels (android:columnWidth = "100px"). Les
colonnes absorberont lespace restant disponible (android:stretchMode = "columnWidth").
Le code Java permettant de congurer cette grille est le suivant :
public class GridDemo extends Activity
implements AdapterView.OnItemSelectedListener {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue", "purus"};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
selection=(TextView)findViewById(R.id.selection);

GridView g=(GridView)findViewById(R.id.grid);
g.setAdapter(new FunnyLookingAdapter(this,
android.R.layout.simple_list_item_1,
items));
g.setOnItemSelectedListener(this);
}

public void onItemSelected(AdapterView<?> parent, View v,
int position, long id) {
selection.setText(items[position]);
}

public void onNothingSelected(AdapterView<?> parent) {
selection.setText("");
}

private class FunnyLookingAdapter extends ArrayAdapter {
Context ctxt;

FunnyLookingAdapter(Context ctxt, int resource,
String[] items) {
super(ctxt, resource, items);
this.ctxt=ctxt;
}



public View getView(int position, View convertView,
ViewGroup parent) {
TextView label=(TextView)convertView;

if (convertView==null) {
convertView=new TextView(ctxt);
label=(TextView)convertView;
}

label.setText(items[position]);

return(convertView);
}
}
}
Au lieu dutiliser des widgets TextView automatiques pour les cellules de la grille, comme
dans les sections prcdentes, on cre nos propres vues qui hritent dArrayAdapter et
rednissent getView(). Ici, on enveloppe les chanes dans nos propres widgets Text
View, juste pour changer un peu. Notre getView() se contente de rutiliser le texte du
TextView qui lui est pass en paramtre ; si ce dernier vaut null, il en cre un et le remplit.
Avec lespacement vertical de 35 pixels indiqu dans le chier de description
(android:verticalSpacing = "35"), la grille ne tiendra pas entirement dans lcran de
lmulateur (voir Figures 8.6 et 8.7).
Figure 8.6
Lapplication GridDemo
lors de son dmarrage.


Champs : conomisez 35 % de la frappe !
AutoCompleteTextView est une sorte dhybride dEditText (champ de saisie) et de
Spinner.
Avec lauto-compltion, le texte saisi par lutilisateur est trait comme un prxe de
ltrage : il est compar une liste de prxes candidats et les diffrentes correspondances
safchent dans une liste de choix qui ressemble un Spinner. Lutilisateur peut alors
continuer sa saisie (si le mot nest pas dans la liste) ou choisir une entre de celle-ci pour
quelle devienne la valeur du champ.
AutoCompleteTextView tant une sous-classe dEditText, vous pouvez utiliser toutes
les proprits de cette dernire pour contrler son aspect la police et la couleur du texte,
notamment.
En outre, la proprit android:completionThreshold dAutoCompleteTextView permet
dindiquer le nombre minimal de caractres entrer avant que la liste de propositions
napparaisse.
Vous pouvez fournir AutoCompleteTextView un adaptateur contenant la liste des
valeurs candidates laide de setAdapter() mais, comme lutilisateur peut trs bien saisir
un texte qui nest pas dans cette liste, AutoCompleteTextView ne permet pas dutiliser les
couteurs de slection. Il est donc prfrable denregistrer un TextWatcher, exactement
Figure 8.7
La mme application,
aprs dlement
vers le bas.


comme nimporte quel EditText, pour tre prvenu lorsque le texte a t modi. Ce type
dvnement est dclench par une saisie manuelle ou par une slection dans la liste des
propositions.
Voici, par exemple, le chier de description du projet Selection/AutoComplete :
<?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"
>
<TextView
android:id="@+id/selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<AutoCompleteTextView android:id="@+id/edit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:completionThreshold="3"/>
</LinearLayout>
Le code Java correspondant est le suivant :
public class AutoCompleteDemo extends Activity
implements TextWatcher {
TextView selection;
AutoCompleteTextView edit;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue", "purus"};
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
selection=(TextView)findViewById(R.id.selection);
edit=(AutoCompleteTextView)findViewById(R.id.edit);
edit.addTextChangedListener(this);



edit.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_dropdown_item_1line,
items));
}

public void onTextChanged(CharSequence s, int start, int before,
int count) {
selection.setText(edit.getText());
}

public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
// impose par linterface, mais inutilise
}

public void afterTextChanged(Editable s) {
// impose par linterface, mais inutilise
}
}
Cette fois-ci, notre activit implmente linterface TextWatcher, ce qui signie que nos
mthodes de rappel doivent se nommer onTextChanged() et beforeTextChanged(). Ici,
seule la premire nous intresse : elle modie le label de slection pour quil rete le
choix courant du champ AutoCompleteTextView.
Les Figures 8.8, 8.9 et 8.10 montrent ce quafche cette application.
Figure 8.8
Lapplication Auto
CompleteDemo aprs
son dmarrage.


Figure 8.9
La mme application
aprs avoir saisi quelques
lettres. La liste des propo-
sitions apparat dans une
liste droulante.
Figure 8.10
La mme application,
aprs avoir choisi le texte
suggr.


Galeries
Le widget Gallery nexiste gnralement pas dans les autres kits de dveloppement
graphiques. En ralit, il sagit dune liste horizontale, o chaque choix dle selon laxe
horizontal et o llment slectionn est mis en surbrillance. Sur un terminal Android,
lutilisateur peut parcourir les diffrents choix laide des boutons gauche et droit du pad.
Gallery prend moins de place lcran que ListView, tout en montrant plusieurs choix
la fois (pour autant quils soient sufsamment courts). Par rapport Spinner, Gallery
montre galement plusieurs choix simultanment.
Lexemple canonique dutilisation de Gallery consiste parcourir une galerie de photos
lutilisateur peut ainsi prvisualiser les imagettes correspondant une collection de photos
ou dicnes an den choisir une.
Du point de vue du code, un objet Gallery fonctionne quasiment comme un Spinner ou
un GridView. Il dispose de plusieurs proprits :
android:spacing indique le nombre de pixels sparant les diffrents lments de la
liste.
android:spinnerSelector prcise ce qui indiquera une slection il peut sagir
dune rfrence un objet Drawable (voir le chapitre sur les ressources) ou une valeur
RGB de la forme #AARRGGBB ou quivalente.
android:drawSelectorOnTop indique si la barre de slection (ou le Drawable) doit
tre dessine avant (false) ou aprs (true) le dessin du ls slectionn. Si cette
proprit vaut true, assurez-vous que le slecteur soit sufsamment transparent pour
que lon puisse apercevoir le ls derrire lui ; sinon les utilisateurs ne pourront pas voir
ce quils ont choisi.


9
Samuser avec les listes
Lhumble ListView est lun des widgets les plus importants et les plus utiliss dAndroid.
Que lon choisisse un contact tlphonique, un courrier faire suivre ou un ebook lire,
cest de ce widget dont on se servira le plus souvent. Mais il serait videmment plus agrable
dnumrer autre chose que du texte simple.
La bonne nouvelle est que les listes peuvent tre aussi amusantes quon le souhaite... dans
les limites de lcran dun mobile, videmment. Cependant, cette dcoration implique un
peu de travail et met en uvre certaines fonctionnalits dAndroid que nous prsenterons
dans ce chapitre.
Premires tapes
Gnralement, un widget ListView dAndroid est une simple liste de texte robuste mais
austre. Cela est d au fait que nous nous contentons de lui fournir un tableau de mots et
que nous demandons Android dutiliser une disposition simple pour afcher ces mots
sous forme de liste.
Cependant, vous pouvez galement crer une liste dicnes, dicnes et de texte, de cases
cocher et de texte, etc. Tout cela dpend des donnes que vous fournissez ladaptateur et
de laide que vous lui apportez pour crer un ensemble plus riche dobjets View pour
chaque ligne.


Supposons, par exemple, que vous vouliez produire une liste dont chaque ligne est consti-
tue dune icne suivie dun texte. Vous pourriez utiliser une disposition de ligne comme
celle du projet FancyLists/Static :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/icon"
android:layout_width="22px"
android:paddingLeft="2px"
android:paddingRight="2px"
android:paddingTop="2px"
android:layout_height="wrap_content"
android:src="@drawable/ok"
/>
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="44sp"
/>
</LinearLayout>
On utilise ici un conteneur LinearLayout pour crer une ligne contenant une icne
gauche et un texte (utilisant une grande police agrable lire) droite.
Cependant, par dfaut, Android ne sait pas que vous souhaitez utiliser cette disposition
avec votre ListView. Pour tablir cette connexion, vous devez donc indiquer ladaptateur
lidentiant de ressource de cette disposition personnalise :
public class StaticDemo extends ListActivity {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue",
"purus"};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new ArrayAdapter<String>(this,
R.layout.row, R.id.label,
items));


selection=(TextView)findViewById(R.id.selection);
}

public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(items[position]);
}
}
On peut remarquer que cette structure gnrale est identique celle du projet Selection/
List du Chapitre 8.
Le point essentiel de cet exemple est que lon a indiqu ArrayAdapter que lon voulait
utiliser notre propre disposition de ligne (R.layout.row) et que le TextView contenant le
mot est dsign par R.id.label dans cette disposition. Noubliez pas que, pour dsigner
une disposition (row.xml), il faut prxer le nom de base du chier de description par
R.layout (R.layout.row).
On obtient ainsi une liste avec des icnes droite. Ici, comme le montre la Figure 9.1,
toutes les icnes sont les mmes.
Prsentation dynamique
Cette technique fournir une disposition personnalise pour les lignes permet de traiter
trs lgamment les cas simples, mais elle ne suft plus pour les scnarios plus compliqus
comme ceux qui suivent :
Chaque ligne utilise une disposition diffrente (certaines ont une seule ligne de texte,
dautres deux, par exemple).
Figure 9.1
Lapplication
StaticDemo.


Vous devez congurer chaque ligne diffremment (par exemple pour mettre des icnes
diffrentes en fonction des cas).
Dans ces situations, la meilleure solution consiste crer une sous-classe de lAdapter
voulu, rednir getView() et construire soi-mme les lignes. La mthode
getView() doit renvoyer un objet View reprsentant la ligne situe la position fournie
par ladaptateur.
Reprenons par exemple le code prcdent pour obtenir, grce getView(), des icnes
diffrentes en fonction des lignes une icne pour les mots courts, une autre pour les mots
longs. Ce projet se trouve dans le rpertoire FancyLists/Dynamic des exemples :
public class DynamicDemo extends ListActivity {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue",
"purus"};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new IconicAdapter(this));
selection=(TextView)findViewById(R.id.selection);
}

public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(items[position]);
}

class IconicAdapter extends ArrayAdapter {
Activity context;

IconicAdapter(Activity context) {
super(context, R.layout.row, items);

this.context=context;
}

public View getView(int position, View convertView,
ViewGroup parent) {
LayoutInflater inflater=context.getLayoutInflater();
View row=inflater.inflate(R.layout.row, null);
TextView label=(TextView)row.findViewById(R.id.label);

label.setText(items[position]);


if (items[position].length()>4) {
ImageView icon=(ImageView)row.findViewById(R.id.icon);

icon.setImageResource(R.drawable.delete);
}

return(row);
}
}
}
Le principe consiste rednir getView() pour quelle renvoie une ligne dpendant de
lobjet afcher, qui est indiqu par lindice position dans lAdapter. Si vous examinez
le code de cette implmentation, vous remarquerez que lon utilise un objet LayoutInflater,
ce qui mrite une petite explication.
Quelques mots sur lination
Dans notre cas, "ination" dsigne le fait de convertir une description XML dans larbo-
rescence dobjets View quelle reprsente. Il sagit indubitablement dune partie de code
assez ennuyeuse : on prend un lment, on cre une instance de la classe View approprie ;
on examine tous les attributs pour les convertir en proprits, on parcourt tous les lments
ls et on recommence.
Heureusement, lquipe qui a cr Android a encapsul ce lourd traitement dans la classe
LayoutInflater. Pour nos listes personnalises, par exemple, nous voulons obtenir des
Views pour chaque ligne de la liste et nous pouvons donc utiliser la notation XML pour
dcrire laspect des lignes.
Dans lexemple prcdent, nous transformons la description R.layout.row que nous
avions cre dans la section prcdente. Cela nous donne un objet View qui, en ralit,
nest autre que notre LinearLayout contenant un ImageView et un TextView, exactement
comme cela est spci par R.layout.row. Cependant, au lieu de crer nous-mmes tous
ces objets et de les lier ensemble, le code XML et la classe LayoutInflater grent pour
nous les "dtails scabreux".
Revenons nos moutons
Nous avons donc utilis LayoutInflater pour obtenir un objet View reprsentant la ligne.
Cette ligne est "vide" car le chier de description statique ne sait pas quelles sont les
donnes quelle recevra. Il vous appartient donc de la personnaliser et de la remplir
comme vous le souhaitez avant de la renvoyer. Cest la raison pour laquelle :
On place le texte du label dans notre widget label en utilisant le mot situ la position
passe en paramtre la mthode.


On regarde si ce mot fait plus de quatre caractres, auquel cas on recherche le widget
ImageView de licne et on remplace sa ressource de base par une autre.
On dispose dsormais dune liste ListView contenant des icnes diffrentes, variant selon
les entres correspondantes de la liste (voir Figure 9.2).
Il sagit bien sr dun exemple assez articiel, mais cette technique peut servir personna-
liser les lignes en fonction de nimporte quel critre le contenu des colonnes dun
Cursor, par exemple.
Mieux, plus robuste et plus rapide
Limplmentation de getView() que nous venons de prsenter fonctionne, mais elle est
peu efcace. En effet, chaque fois que lutilisateur fait dler lcran, on doit crer tout
un lot de nouveaux objets View pour les nouvelles lignes qui safchent. Le framework
dAndroid ne mettant pas automatiquement en cache les objets View existants, il faut en
recrer de nouveaux, mme pour des lignes que lon avait cres trs peu de temps aupa-
ravant. Ce nest donc pas trs efcace, ni du point de vue de lutilisateur, qui risque de
constater que la liste est lente, ni du point de vue de la batterie chaque action du CPU
consomme de lnergie. Ce traitement supplmentaire est, par ailleurs, aggrav par la
charge que lon impose au ramasse-miettes (garbage collector) puisque celui-ci doit
dtruire tous les objets que lon cre. Par consquent, moins le code est efcace, plus la
batterie du tlphone se dcharge vite et moins lutilisateur est content. On doit donc
passer par quelques astuces pour viter ces dfauts.
Figure 9.2
Lapplication
DynamicDemo.


Utilisation de convertView
La mthode getView() reoit en paramtre un objet View nomm, par convention,
convertView. Parfois, cet objet est null, auquel cas vous devez crer une nouvelle View
pour la ligne (par ination), comme nous lavons expliqu plus haut.
Si convertView nest pas null, en revanche, il sagit en fait de lune des View que vous avez
dj cres. Ce sera notamment le cas lorsque lutilisateur fait dler la ListView : mesure
que de nouvelles lignes apparaissent, Android tentera de rutiliser les vues des lignes qui
ont disparu lautre extrmit, vous vitant ainsi de devoir les reconstruire totalement.
En supposant que chaque ligne ait la mme structure de base, vous pouvez utiliser find
ViewById() pour accder aux diffrents widgets qui composent la ligne, modier leur
contenu, puis renvoyer convertView partir de getView() au lieu de crer une ligne tota-
lement nouvelle.
Voici, par exemple, une criture optimise de limplmentation prcdente de getView(),
extraite du projet FancyLists/Recycling :
public class RecyclingDemo extends ListActivity {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue",
"purus"};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new IconicAdapter(this));
selection=(TextView)findViewById(R.id.selection);
}

public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(items[position]);
}

class IconicAdapter extends ArrayAdapter {
Activity context;

IconicAdapter(Activity context) {
super(context, R.layout.row, items);

this.context=context;
}



public View getView(int position, View convertView,
ViewGroup parent) {
View row=convertView;

if (row==null) {
LayoutInflater inflater=context.getLayoutInflater();

row=inflater.inflate(R.layout.row, null);
}

TextView label=(TextView)row.findViewById(R.id.label);

label.setText(items[position]);
ImageView icon=(ImageView)row.findViewById(R.id.icon);

if (items[position].length()>4) {
icon.setImageResource(R.drawable.delete);
}
else {
icon.setImageResource(R.drawable.ok);
}

return(row);
}
}
}
Si convertView est null, nous crons une ligne par ination ; dans le cas contraire, nous
nous contentons de la rutiliser. Le code pour remplir les contenus (image de licne, texte
du label) est identique dans les deux cas. On vite ainsi une tape dination potentiellement
coteuse lorsque convertView nest pas null.
Cependant, cette approche ne fonctionne pas toujours. Si, par exemple, une ListView
comprend des lignes ne contenant quune seule ligne de texte et dautres en contenant
plusieurs, la rutilisation des lignes existantes devient problmatique car les layouts
risquent dtre trs diffrents. Si lon doit crer une View pour une ligne qui compte deux
lignes de texte, par exemple, on ne peut pas se contenter de rutiliser une View avec une
seule ligne : il faut soit modier les dtails internes de cette View, soit lignorer et en crer
une nouvelle.
Il existe bien entendu des moyens de grer ce type de problme, comme rendre la seconde
ligne de texte visible ou non en fonction des besoins, mais noubliez pas que chaque milli-
seconde dutilisation du CPU dun tlphone est prcieuse pour la uidit de linterface,
mais surtout pour la batterie.
Ceci tant dit, surtout si vous dbutez avec Android, intressez-vous dabord obtenir la
fonctionnalit que vous dsirez et essayez ensuite doptimiser les performances lors dun


second examen de votre code. Ne tentez pas de tout rgler dun coup, sous peine de vous
noyer dans un ocan de View.
Utilisation du patron de conception "support"
Lappel de findViewById() est galement coteux : cette mthode plonge dans les lignes
de la liste pour en extraire les widgets en fonction de leurs identiants, an que lon puisse
en personnaliser le contenu (pour modier le texte dun TextView, changer licne dun
ImageView, par exemple).
findViewById() pouvant trouver nimporte quel widget dans larbre des ls de la View
racine de la ligne, cet appel peut demander un certain nombre dinstructions pour
sexcuter, notamment si lon doit retrouver nouveau des widgets que lon a dj trouvs
auparavant.
Certains kits de dveloppement graphiques vitent ce problme en dclarant les View
composites, comme nos lignes, dans le code du programme (en Java, ici). Laccs aux
diffrents widgets ne consiste plus, alors, qu appeler une mthode daccs ou lire un
champ. Nous pourrions bien sr faire de mme avec Android, mais cela alourdirait le code.
Nous prfrons trouver un moyen de continuer utiliser le chier de description XML tout
en mettant en cache les widgets ls essentiels de notre ligne, an de ne devoir les rechercher
quune seule fois.
Cest l quentre en jeu le patron de conception "support", qui est implment par une
classe que nous appellerons ViewWrapper.
Tous les objets View disposent des mthodes getTag() et setTag(), qui permettent
dassocier un objet quelconque au widget. Le patron "support" utilise ce "marqueur" pour
dtenir un objet qui, son tour, dtient chaque widget ls intressant. En attachant le
support lobjet View de la ligne, on a accs immdiatement aux widgets ls qui nous
intressent chaque fois que lon utilise cette ligne, sans devoir appeler nouveau find
ViewById().
Examinons lune de ces classes support (extrait du projet FancyLists/ViewWrapper) :
class ViewWrapper {
View base;
TextView label=null;
ImageView icon=null;

ViewWrapper(View base) {
this.base=base;
}

TextView getLabel() {
if (label==null) {
label=(TextView)base.findViewById(R.id.label);
}



return(label);
}

ImageView getIcon() {
if (icon==null) {
icon=(ImageView)base.findViewById(R.id.icon);
}

return(icon);
}
}
ViewWrapper ne dtient pas seulement les widgets ls : elle les recherche uniquement si
elle ne les dtient pas dj. Si vous crez un wrapper et que vous nayez jamais besoin
dun ls prcis, il ny aura donc jamais aucun appel de findViewById() pour le retrouver
et vous naurez jamais payer le prix de ces cycles CPU inutiles.
Le patron "support" permet galement deffectuer les traitements suivants :
Il regroupe au mme endroit le transtypage de tous nos widgets, au lieu de le dissminer
dans chaque appel findViewById().
Il permet de mmoriser dautres informations sur les lignes, comme leur tat, que nous
ne voulons pas insrer dans le modle sous-jacent.
Lutilisation de ViewWrapper consiste simplement crer une instance de cette classe
chaque fois que lon cre une ligne par ination et attacher cette instance la vue de la
ligne via setTag(), comme dans cette version de getView() :
public class ViewWrapperDemo extends ListActivity {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue",
"purus"};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new IconicAdapter(this));
selection=(TextView)findViewById(R.id.selection);
}

private String getModel(int position) {
return(((IconicAdapter)getListAdapter()).getItem(position));
}



public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(getModel(position));
}

class IconicAdapter extends ArrayAdapter<String> {
Activity context;

IconicAdapter(Activity context) {
super(context, R.layout.row, items);

this.context=context;
}

public View getView(int position, View convertView,
ViewGroup parent) {
View row=convertView;
ViewWrapper wrapper=null;

if (row==null) {
LayoutInflater inflater=context.getLayoutInflater();

row=inflater.inflate(R.layout.row, null);
wrapper=new ViewWrapper(row);
row.setTag(wrapper);
}
else {
wrapper=(ViewWrapper)row.getTag();
}

wrapper.getLabel().setText(getModel(position));

if (getModel(position).length()>4) {
wrapper.getIcon().setImageResource(R.drawable.delete);
}
else {
wrapper.getIcon().setImageResource(R.drawable.ok);
}

return(row);
}
}
}
On teste si convertView est null pour crer au besoin les View de la ligne et lon rcupre
(ou lon cre) galement le ViewWrapper de celle-ci. Accder ensuite aux widgets ls
consiste simplement appeler les mthodes appropries du wrapper.


Crer une liste...
Les listes avec de belles icnes sont jolies, mais ne pourrions-nous pas crer des widgets
ListView dont les lignes contiendraient des widgets ls interactifs, et non plus passifs
comme TextView et ImageView ? Pourrions-nous, par exemple, combiner une RatingBar
avec du texte an de permettre lutilisateur de faire dler une liste de chansons et de les
valuer directement dans cette liste ?
Il y a une bonne et une mauvaise nouvelle.
La bonne est que lon peut mettre des widgets interactifs dans les lignes ; la mauvaise est
que cest un peu compliqu, notamment lorsquil faut intervenir parce que ltat du widget
interactif a chang (une valeur a t tape dans un champ, par exemple). Il faut en effet
stocker cet tat quelque part puisque notre widget RatingBar sera recycl lors du dle-
ment de la ListView. On doit pouvoir congurer ltat de la RatingBar en fonction du
mot qui est visible lorsque la RatingBar est recycle et sauvegarder cet tat, an de le
restaurer plus tard lorsque cette ligne prcise redeviendra visible.
Par dfaut, la RatingBar na absolument aucune ide de la manire dont les donnes de
lArrayAdapter doivent tre afches. Aprs tout, une RatingBar nest quun widget
utilis dans une ligne dune ListView. Nous devons donc apprendre aux lignes quels sont
les modles quelles afchent, an quelles sachent quel tat de modle modier lorsque
leurs barres dvaluation sont coches.
tudions lactivit du projet FancyLists/RateList. On utilisera ici les mmes classes de
base que celles de lexemple prcdent on afche une liste de mots quelconques que lon
pourra valuer. Les mots ayant la note maximale apparatront tout en majuscules.
public class RateListDemo extends ListActivity {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue",
"purus"};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

ArrayList<RowModel> list=new ArrayList<RowModel>();

for (String s : items) {
list.add(new RowModel(s));
}



setListAdapter(new CheckAdapter(this, list));
selection=(TextView)findViewById(R.id.selection);
}

private RowModel getModel(int position) {
return(((CheckAdapter)getListAdapter()).getItem(position));
}

public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(getModel(position).toString());
}

class CheckAdapter extends ArrayAdapter<RowModel> {
Activity context;

CheckAdapter(Activity context, ArrayList<RowModel> list) {
super(context, R.layout.row, list);

this.context=context;
}

public View getView(int position, View convertView,
ViewGroup parent) {
View row=convertView;
ViewWrapper wrapper;
RatingBar rate;

if (row==null) {
LayoutInflater inflater=context.getLayoutInflater();

row=inflater.inflate(R.layout.row, null);
wrapper=new ViewWrapper(row);
row.setTag(wrapper);
rate=wrapper.getRatingBar();

RatingBar.OnRatingBarChangeListener l=
new RatingBar.OnRatingBarChangeListener() {
public void onRatingChanged(RatingBar ratingBar,
float rating,
boolean fromTouch) {
Integer myPosition=(Integer)ratingBar.getTag();
RowModel model=getModel(myPosition);

model.rating=rating;



LinearLayout parent=(LinearLayout)ratingBar.getParent();
TextView label=(TextView)parent.findViewById(R.id.label);

label.setText(model.toString());
}
};

rate.setOnRatingBarChangeListener(l);
}
else {
wrapper=(ViewWrapper)row.getTag();
rate=wrapper.getRatingBar();
}
RowModel model=getModel(position);

wrapper.getLabel().setText(model.toString());
rate.setTag(new Integer(position));
rate.setRating(model.rating);

return(row);
}
}

class RowModel {
String label;
float rating=2.0f;

RowModel(String label) {
this.label=label;
}

public String toString() {
if (rating>=3.0) {
return(label.toUpperCase());
}

return(label);
}
}
}
Les diffrences entre cette activit et la prcdente sont les suivantes :
Bien que nous utilisions toujours un tableau de String pour stocker la liste des mots,
on le transforme en liste dobjets RowModel au lieu de le fournir un ArrayAdapter.
Ici, le RowModel est un ersatz de modle mutable car il se contente de combiner un mot


et son tat de slection. Dans un vrai systme, il pourrait sagir dobjets remplis partir
dun Cursor et les proprits auraient des signications mtier plus importantes.
Les mthodes utilitaires comme onListItemClick() doivent tre modies pour
reter les diffrences entre un modle String pur et lutilisation dun RowModel.
Dans getView(), la sous-classe dArrayAdapter (CheckAdapter) teste si conver
tView est null, auquel cas elle cre une nouvelle ligne par ination dun layout simple
(voir le code qui suit) et lui attache un ViewWrapper. Pour la RatingBar de la ligne, on
ajoute un couteur onRatingChanged() anonyme qui examine le marqueur de la ligne
(getTag()) et le convertit en entier reprsentant la position dans lArrayAdapter de ce
quafche cette ligne. La barre dvaluation peut alors obtenir le bon RowModel pour la
ligne et mettre jour le modle en fonction de son nouvel tat. Elle modie galement
le texte situ ct de la barre pour quil corresponde ltat de celle-ci.
On sassure toujours que la RatingBar a le bon contenu et dispose dun marqueur (via
setTag()) pointant vers la position de la ligne dans ladaptateur.
La disposition de la ligne est trs simple : elle contient une RatingBar et un label
TextView placs dans un conteneur LinearLayout :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<RatingBar
android:id="@+id/rate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numStars="3"
android:stepSize="1"
android:rating="2" />
<TextView
android:id="@+id/label"
android:paddingLeft="2px"
android:paddingRight="2px"
android:paddingTop="2px"
android:textSize="40sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Le ViewWrapper est tout aussi simple, car il se contente dextraire ces deux widgets de la
View de la ligne :
class ViewWrapper {
View base;


RatingBar rate=null;
TextView label=null;

ViewWrapper(View base) {
this.base=base;
}

RatingBar getRatingBar() {
if (rate==null) {
rate=(RatingBar)base.findViewById(R.id.rate);
}

return(rate);
}

TextView getLabel() {
if (label==null) {
label=(TextView)base.findViewById(R.id.label);
}

return(label);
}
}
Les Figures 9.3 et 9.4 montrent ce quafche cette application.
Figure 9.3
Lapplication
RateListDemo lors
de son dmarrage.


Et la vrier deux fois
La liste dvaluation de la section prcdente fonctionne, mais son implmentation est trs
lourde. Pire, lessentiel de ce code ennuyeux ne sera pas rutilisable, sauf dans des
circonstances trs limites.
Nous pouvons mieux faire.
En fait, nous voudrions pouvoir crer un layout comme celui-ci :
<?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" >
<TextView
android:id="@+id/selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<com.commonsware.android.fancylists.seven.RateListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="false"
/>
</LinearLayout>
Figure 9.4
La mme application,
montrant un mot avec
la note maximale.


o toute la logique du code qui utilisait une ListView auparavant "fonctionnerait" avec la
RateListView du layout :
public class RateListViewDemo extends ListActivity {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue",
"purus"};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
items));
selection=(TextView)findViewById(R.id.selection);
}

public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(items[position]);
}
}
Les choses se compliquent un tout petit peu lorsquon ralise que, jusqu maintenant, les
codes de ce chapitre nont jamais rellement modi la ListView elle-mme. Nous
navons fait que travailler sur les adaptateurs, en rednissant getView(), en crant nos
propres lignes par ination, etc.
Si lon souhaite que RateListView prenne nimporte quel ListAdapter et fonctionne
"comme il faut", en plaant les barres dvaluation sur les lignes comme il se doit, nous
devons faire un peu de gymnastique. Plus prcisment, nous devons envelopper le List
Adapter "brut" dans un autre, qui sait comment placer les barres dans les lignes et mmo-
riser ltat de ces barres.
Nous devons dabord tablir le motif de conception dans lequel un ListAdapter en
augmente un autre. Voici le code dAdapterWrapper, qui prend en charge un ListAdapter et
dlgue toutes les mthodes de linterface delegate. Vous retrouverez ce code dans le
projet FancyLists/RateListView :
public class AdapterWrapper implements ListAdapter {
ListAdapter delegate=null;

public AdapterWrapper(ListAdapter delegate) {
this.delegate=delegate;
}



public int getCount() {
return(delegate.getCount());
}

public Object getItem(int position) {
return(delegate.getItem(position));
}

public long getItemId(int position) {
return(delegate.getItemId(position));
}

public View getView(int position, View convertView,
ViewGroup parent) {
return(delegate.getView(position, convertView, parent));
}

public void registerDataSetObserver(DataSetObserver observer) {
delegate.registerDataSetObserver(observer);
}

public boolean hasStableIds() {
return(delegate.hasStableIds());
}

public boolean isEmpty() {
return(delegate.isEmpty());
}

public int getViewTypeCount() {
return(delegate.getViewTypeCount());
}

public int getItemViewType(int position) {
return(delegate.getItemViewType(position));
}

public void unregisterDataSetObserver(DataSetObserver observer) {
delegate.unregisterDataSetObserver(observer);
}

public boolean areAllItemsEnabled() {
return(delegate.areAllItemsEnabled());
}

public boolean isEnabled(int position) {
return(delegate.isEnabled(position));
}
}


Nous pouvons alors hriter dAdapterWrapper pour crer RateableWrapper, qui rednit
la mthode getView() tout en laissant le ListAdapter dlgu faire "le vrai travail" :
public class RateableWrapper extends AdapterWrapper {
Context ctxt=null;
float[] rates=null;

public RateableWrapper(Context ctxt, ListAdapter delegate) {
super(delegate);

this.ctxt=ctxt;
this.rates=new float[delegate.getCount()];

for (int i=0;i<delegate.getCount();i++) {
this.rates[i]=2.0f;
}
}

public View getView(int position, View convertView,
ViewGroup parent) {
ViewWrapper wrap=null;
View row=convertView;

if (convertView==null) {
LinearLayout layout=new LinearLayout(ctxt);
RatingBar rate=new RatingBar(ctxt);

rate.setNumStars(3);
rate.setStepSize(1.0f);

View guts=delegate.getView(position, null, parent);

layout.setOrientation(LinearLayout.HORIZONTAL);

rate.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.FILL_PARENT));
guts.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.FILL_PARENT));

RatingBar.OnRatingBarChangeListener l=
new RatingBar.OnRatingBarChangeListener() {
public void onRatingChanged(RatingBar ratingBar,
float rating,
boolean fromTouch) {
rates[(Integer)ratingBar.getTag()]=rating;
}
};



rate.setOnRatingBarChangeListener(l);

layout.addView(rate);
layout.addView(guts);

wrap=new ViewWrapper(layout);
wrap.setGuts(guts);
layout.setTag(wrap);

rate.setTag(new Integer(position));
rate.setRating(rates[position]);

row=layout;
}
else {
wrap=(ViewWrapper)convertView.getTag();
wrap.setGuts(delegate.getView(position, wrap.getGuts(),
parent));
wrap.getRatingBar().setTag(new Integer(position));
wrap.getRatingBar().setRating(rates[position]);
}

return(row);
}
}
Lessentiel du traitement de notre liste dvaluation rside dans RateableWrapper. Cette
classe place les barres dvaluation sur les lignes et mmorise leurs tats lorsquelles sont
modies par lutilisateur. Pour stocker ces tats, on utilise un tableau de float dont la
taille correspond au nombre de lignes que delegate rapporte pour cette liste.
Limplmentation de la mthode getView() de RateableWrapper rappelle celle de Rate
ListDemo, sauf quau lieu dutiliser LayoutInflater on doit construire manuellement un
conteneur LinearLayout pour y placer notre RatingBar et les "tripes" (cest--dire toutes
les vues cres par delegate et que lon dcore avec une barre dvaluation). LayoutIn
flater est conue pour construire une View partir de widgets bruts or, ici, nous ne
savons pas par avance quoi ressembleront les lignes, hormis le fait quil faudra leur
ajouter une barre dvaluation. Le reste du code est semblable celui de RateListDemo :
class ViewWrapper {
ViewGroup base;
View guts=null;
RatingBar rate=null;

ViewWrapper(ViewGroup base) {
this.base=base;
}



RatingBar getRatingBar() {
if (rate==null) {
rate=(RatingBar)base.getChildAt(0);
}

return(rate);
}

void setRatingBar(RatingBar rate) {
this.rate=rate;
}

View getGuts() {
if (guts==null) {
guts=base.getChildAt(1);
}

return(guts);
}

void setGuts(View guts) {
this.guts=guts;
}
}
Lorsque tout ceci est en place, le code de RateListView devient assez simple :
public class RateListView extends ListView {
public RateListView(Context context) {
super(context);
}

public RateListView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public RateListView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}

public void setAdapter(ListAdapter adapter) {
super.setAdapter(new RateableWrapper(getContext(), adapter));
}
}
On hrite simplement de ListView et on rednit setAdapter() pour pouvoir envelopper
dans notre propre RateableWrapper le ListAdapter fourni en paramtre.


Comme le montre la Figure 9.5, les rsultats sont identiques ceux de RateListDemo,
sauf que les mots ayant la note maximale napparaissent plus en majuscules.
La diffrence est la rutilisabilit. Nous pourrions crer un paquetage JAR de RateListView
et linsrer dans nimporte quel projet Android qui en a besoin. Par consquent, bien que
RateListView soit un peu plus complique crire, il ne faut plus le faire quune seule
fois et le reste du code de lapplication est merveilleusement simple.
Cette classe RateListView pourrait, bien sr, proposer des fonctionnalits supplmen-
taires, comme la possibilit de modier par programmation ltat des barres (en mettant
jour le tableau de float et la RatingBar elle-mme), autoriser lappel dun autre code
lorsque ltat dune barre est modi (via une fonction de rappel), etc. Nous les laissons en
exercice au lecteur.
Adapter dautres adaptateurs
Toutes les classes adaptateurs peuvent suivre le modle de conception consistant rednir
getView() pour crer les lignes de la liste.
Cependant, CursorAdapter et ses sous-classes fournissent une implmentation par dfaut
de getView(), qui inspecte la View qui lui est passe en paramtre pour la recycler et
appelle newView() si elle est null, ou bindView() dans le cas contraire. Si vous tendez
CursorAdapter, qui sert afcher le rsultat dune requte adresse une base de
donnes ou un fournisseur de contenu, il est donc prfrable de rednir newView() et
bindView() plutt que getView().
Figure 9.5
Lapplication
RateListViewDemo.


Pour cela, il suft de supprimer le test if de getView() et de placer chaque branche de ce
test dans deux mthodes spares, comme celles-ci :
public View newView(Context context, Cursor cursor,
ViewGroup parent) {
LayoutInflater inflater=context.getLayoutInflater();
View row=inflater.inflate(R.layout.row, null);
ViewWrapper wrapper=new ViewWrapper(row);
row.setTag(wrapper);
bindView(row, context, cursor);

return(row);
}
public void bindView(View row, Context context, Cursor cursor) {
ViewWrapper wrapper=(ViewWrapper)row.getTag();
// Code pour remplir la ligne partir du Cursor
}
Lutilisation de Cursor sera dcrite au Chapitre 20.


10
Utiliser de jolis widgets
et de beaux conteneurs
Les widgets et les conteneurs que nous avons prsents jusqu maintenant ne se trouvent
pas seulement dans la plupart des kits de dveloppement graphiques (sous une forme ou
sous une autre), mais sont galement trs utiliss dans le dveloppement des applications
graphiques, quil sagisse dapplications web, pour les PC ou pour les tlphones. Nous
allons maintenant nous intresser des widgets et des conteneurs un peu moins
frquents, mais nanmoins trs utiles.
Choisir
Avec des terminaux ayant des capacits de saisie limites comme les tlphones, il est
trs utile de disposer de widgets et de botes de dialogue capables danticiper ce que
lutilisateur veut taper. Cela minimise le nombre de frappes au clavier et de touches
lcran et rduit les risques derreur (la saisie dune lettre la place dun chiffre, par
exemple).


Comme on la mentionn prcdemment, EditText peut forcer la saisie de nombres, de
numros de tlphone, etc. Android dispose galement de widgets (DatePicker, Time
Picker) et de dialogues (DatePickerDialog, TimePickerDialog) facilitant la saisie des
dates et des heures.
DatePicker et DatePickerDialog permettent de xer une date de dpart, sous la forme
dune anne, dun mois et dun jour. Les mois vont de 0 (janvier) 11 (dcembre). Vous
pouvez galement prciser un couteur (OnDateChangedListener ou OnDateSetListener)
qui sera inform lorsquune nouvelle date a t choisie. Il vous appartient de stocker cette date
quelque part, notamment si vous utilisez la bote de dialogue, car vous naurez pas dautre
moyen dobtenir ensuite la date choisie.
De mme, TimePicker et TimePickerDialog permettent dagir comme suit :
Fixer lheure initiale que lutilisateur peut ensuite ajuster, sous la forme dune heure
(de 0 23) et de minutes (de 0 59).
Indiquer si le format de la date choisi utilise le mode sur 12 heures avec un indicateur
AM/PM ou le mode 24 heures (ce qui, aux tats-Unis, est appel "temps militaire" et
qui est le mode utilis partout ailleurs dans le monde).
Fournir un couteur (OnTimeChangedListener ou OnTimeSetListener) pour tre
prvenu du choix dune nouvelle heure, qui vous sera fournie sous la forme dune
heure et de minutes.
Le projet Fancy/Chrono utilise une disposition trs simple, forme dun label et de deux
boutons ceux-ci font surgir les botes de dialogue pour choisir une date et une heure :
<?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"
>
<TextView android:id="@+id/dateAndTime"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Button android:id="@+id/dateBtn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Choisir une date"
/>
<Button android:id="@+id/timeBtn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Choisir une heure"
/>
</LinearLayout>


La partie intressante se trouve dans le code Java :
public class ChronoDemo extends Activity {
DateFormat fmtDateAndTime=DateFormat.getDateTimeInstance();
TextView dateAndTimeLabel;
Calendar dateAndTime=Calendar.getInstance();
DatePickerDialog.OnDateSetListener d=new DatePickerDialog.OnDateSetListener()
{
public void onDateSet(DatePicker view, int year, int monthOfYear,
int dayOfMonth) {
dateAndTime.set(Calendar.YEAR, year);
dateAndTime.set(Calendar.MONTH, monthOfYear);
dateAndTime.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateLabel();
}
};
TimePickerDialog.OnTimeSetListener t=new TimePickerDialog.OnTimeSetListener()
{
public void onTimeSet(TimePicker view, int hourOfDay,
int minute) {
dateAndTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
dateAndTime.set(Calendar.MINUTE, minute);
updateLabel();
}
};

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

Button btn=(Button)findViewById(R.id.dateBtn);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
new DatePickerDialog(ChronoDemo.this,
d,
dateAndTime.get(Calendar.YEAR),
dateAndTime.get(Calendar.MONTH),
dateAndTime.get(Calendar.DAY_OF_MONTH)).show();
}
});

btn=(Button)findViewById(R.id.timeBtn);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
new TimePickerDialog(ChronoDemo.this,
t,
dateAndTime.get(Calendar.HOUR_OF_DAY),
dateAndTime.get(Calendar.MINUTE),
true).show();


}
});

dateAndTimeLabel=(TextView)findViewById(R.id.dateAndTime);

updateLabel();
}

private void updateLabel() {
dateAndTimeLabel.setText(fmtDateAndTime
.format(dateAndTime.getTime()));
}
}
Le "modle" de cette activit est simplement une instance de Calendar initialise avec la
date et lheure courantes et place dans la vue via un formateur DateFormat. La mthode
updateLabel() prend le Calendar courant, le formate et le place dans le TextView
correspondant au label.
Chaque bouton est associ un couteur OnClickListener qui se charge dafcher une
bote de dialogue DatePickerDialog ou TimePickerDialog selon le bouton sur lequel
on clique. On passe un couteur OnDateSetListener DatePickerDialog pour mettre
jour le Calendar avec la nouvelle date (anne, mois, jour). On lui passe galement la
dernire date choisie, en rcuprant les valeurs qui se trouvent dans le Calendar. Time
PickerDialog, quant lui, reoit un couteur OnTimeSetListener pour mettre jour la
portion horaire du Calendar ; on lui passe galement la dernire heure choisie et true
pour indiquer que lon veut un format sur 24 heures.
Le rsultat de cette activit est prsent aux Figures 10.1, 10.2 et 10.3.
Figure 10.1
Lapplication
ChronoDemo lors
de son lancement.


Le temps scoule comme un euve
Les widgets DigitalClock ou AnalogClock permettent dafcher lheure sans autoriser
les utilisateurs la modier. Il suft simplement de les placer dans votre layout et de les
laisser travailler.
Le chier main.xml du projet Fancy/Clocks contient ces deux widgets :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
Figure 10.2
La mme application,
montrant le dialogue
de choix de la date.
Figure 10.3
La mme application,
montrant le dialogue
de choix de lheure.


android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<AnalogClock android:id="@+id/analog"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentTop="true"
/>
<DigitalClock android:id="@+id/digital"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/analog"
/>
</RelativeLayout>
Sans avoir besoin de modier quoi que ce soit au squelette de code Java produit par
android create project, on obtient lapplication prsente la Figure 10.4.
Mesurer la progression
Si une opration doit durer un certain temps, vos utilisateurs doivent pouvoir :
utiliser un thread en arrire-plan (voir Chapitre 15) ;
tre tenus au courant de la progression de lopration, sous peine de penser que lacti-
vit a un problme.
Figure 10.4
Lapplication
ClocksDemo.


Lapproche classique pour tenir les utilisateurs informs dune progression consiste utili-
ser une barre de progression ou un "disque tournant" (pensez lanimation qui apparat en
haut droite de la plupart des navigateurs web). Android dispose pour cela du widget
ProgressBar.
Une ProgressBar mmorise la progression, dnie par un entier allant de 0 (aucune
progression) une valeur maximale dnie par setMax() qui indique que lopration sest
termine. Par dfaut, une barre de progression part de la valeur 0, mais vous pouvez choisir
une autre valeur de dpart via un appel sa mthode setProgress().
Si vous prfrez que la barre soit indtermine, passez la valeur true sa mthode set
Indeterminate().
Dans le code Java, vous pouvez xer le montant de progression effectue (via setPro
gress()) ou incrmenter la progression courante dune valeur dtermine (via incre
mentProgressBy()). La mthode getProgress(), quant elle, permet de connatre la
progression dj effectue.
Le widget ProgressBar tant intimement li lutilisation des threads un thread en
arrire-plan effectue le traitement et informe de sa progression le thread qui gre linter-
face , nous prfrons reporter la dmonstration de ce widget jusquau Chapitre 15.
Utilisation donglets
La philosophie gnrale dAndroid consiste faire en sorte que les activits soient courtes
et agrables. Lorsquil y a plus dinformations que ne peut raisonnablement en contenir
lcran (bien que lon puisse utiliser des barres de dlement), il peut tre prfrable de
produire ces informations supplmentaires par une autre activit, lance via une Intent,
comme on lexplique au Chapitre 24. Cependant, ceci peut tre assez compliqu mettre
en place. En outre, il arrive parfois que lon doive recueillir et traiter beaucoup dinfor-
mations en une seule opration.
Dans une interface graphique classique, nous pourrions utiliser des onglets cette n,
comme un JTabbedPane en Java/Swing. Avec Android, on dispose dsormais dun conte-
neur TabHost qui fonctionne exactement de la mme faon une portion de ce quafche
lactivit est lie des onglets qui, lorsquon clique dessus, permettent de passer dune
partie de la vue une autre. Une activit utilisera par exemple un onglet pour la saisie dun
emplacement et un autre pour afcher cet emplacement sur une carte.
Certains kits de dveloppement considrent simplement les onglets comme des objets
cliquables, permettant de passer dune vue lautre. Dautres les considrent comme une
combinaison de llment cliquable et du contenu qui apparat lorsquon clique dessus.
Avec Android, les boutons et les contenus des onglets tant des entits distinctes, nous
emploierons les termes "bouton de longlet" et "contenu de longlet" dans cette section.


Composants
Pour mettre en place les onglets dans une vue, vous avez besoin des widgets et des conteneurs
suivants :
TabHost est le conteneur gnral pour les boutons et les contenus des onglets.
TabWidget implmente la ligne des boutons des onglets, qui contient les labels et,
ventuellement, des icnes.
FrameLayout est le conteneur des contenus des onglets : chaque contenu donglet est
un ls du FrameLayout.
Cette approche ressemble celle de XUL, utilise par Mozilla : les lments tabbox, tabs et
tabpanels de XUL correspondent respectivement TabHost, TabWidget et FrameLayout.
Idiosyncrasies
La version actuelle dAndroid exige de respecter les rgles suivantes pour que ces trois
composants puissent fonctionner de concert :
Lidentiant android:id du TabWidget doit tre @android:id/tabs.
Vous devez rserver un espace de remplissage dans le FrameLayout pour les boutons
des onglets.
Si vous souhaitez utiliser TabActivity, lidentiant android:id du TabHost doit tre
@android:id/tabhost.
TabActivity, comme ListActivity, enveloppe un motif dinterface graphique classique
(une activit compose entirement donglets) dans une sous-classe dactivit. Vous nen
avez pas ncessairement besoin une activit classique peut trs bien utiliser galement
des onglets.
Pour une raison que jignore, TabWidget ne semble pas allouer son espace dans le conte-
neur TabHost ; en dautres termes, quelle que soit la valeur de la proprit
android:layout height de TabWidget, FrameLayout lignore et le place au-dessus du
TabHost, provoquant ainsi le masquage des boutons des onglets par leurs contenus. Par
consquent, vous devez rserver assez despace dans FrameLayout (avec
android:paddingTop) pour "pousser" le contenu des onglets en dessous des boutons.
En outre, TabWidget semble toujours se dessiner en laissant de la place pour des icnes, mme
si lon nen utilise pas. Avec cette version du kit de dveloppement, vous devez donc rser-
ver au moins 62 pixels ou ventuellement plus en fonction des icnes que vous utilisez.
Voici le chier de description dune activit utilisant des onglets, tir du projet Fancy/Tab :
<?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">
<TabHost android:id="@+id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TabWidget android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<FrameLayout android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingTop="62px">
<AnalogClock android:id="@+id/tab1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_centerHorizontal="true"
/>
<Button android:id="@+id/tab2"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Bouton semi-aleatoire"
/>
</FrameLayout>
</TabHost>
</LinearLayout>
Vous remarquerez que les lments TabWidget et FrameLayout sont des ls directs de
TabHost et que llment FrameLayout a lui-mme un ls reprsentant les diffrents
onglets. Ici, il y en a deux : une horloge et un bouton. Dans un scnario plus compliqu,
les onglets seraient regroups dans un conteneur (un LinearLayout, par exemple) avec
leurs propres contenus.
Code Java
Le code Java doit indiquer au TabHost quelles sont les vues qui reprsentent les contenus
des onglets et quoi doivent ressembler les boutons de ces onglets. Tout ceci est encapsul
dans des objets TabSpec. On rcupre une instance de TabSpec via la mthode newTab
Spec() du TabHost, on la remplit puis on lajoute au TabHost dans le bon ordre.
Les deux mthodes essentielles de TabSpec sont les suivantes :
setContent(), qui permet dindiquer le contenu de cet onglet. Gnralement, il sagit
de lidentiant android:id de la vue que lon veut montrer lorsque longlet est choisi.
setIndicator(), qui permet de fournir le titre du bouton de longlet. Cette mthode
est surcharge pour permettre de fournir galement un objet Drawable reprsentant
licne de longlet.


Notez que les "indicateurs" des onglets peuvent, en fait, tre eux-mmes des vues, ce qui
permet de faire mieux quun simple label et une icne facultative.
Notez galement que vous devez appeler la mthode setup() de lobjet TabHost avant de
congurer les objets TabSpec. Cet appel nest pas ncessaire si votre activit drive de la
classe de base TabActivity.
Voici, par exemple, le code Java permettant de faire fonctionner les onglets de la section
prcdente :
package com.commonsware.android.fancy;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TabHost;
public class TabDemo extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
TabHost tabs=(TabHost)findViewById(R.id.tabhost);

tabs.setup();

TabHost.TabSpec spec=tabs.newTabSpec("tag1");

spec.setContent(R.id.tab1);
spec.setIndicator("Horloge");
tabs.addTab(spec);

spec=tabs.newTabSpec("tag2");
spec.setContent(R.id.tab2);
spec.setIndicator("Bouton");
tabs.addTab(spec);

tabs.setCurrentTab(0);
}
}
On retrouve notre TabHost via un appel la mthode findViewById(), puis lon appelle
sa mthode setup().
Ensuite, on cre une instance de TabSpec via un appel newTabSpec() auquel on passe
un marqueur dont le but est encore inconnu. On appelle les mthodes setContent() et
setIndicator() de cette instance, puis la mthode addTab() de lobjet TabHost pour lui
ajouter le TabSpec. Enn, on choisit longlet qui safchera initialement, laide de la
mthode setCurrentTab() (la valeur 0 dsigne le premier onglet).
Les Figures 10.5 et 10.6 montrent ce quafche cette application.


Ajouts dynamiques
TabWidget est congur pour simplier la dnition des onglets au moment de la compi-
lation. Cependant, vous voudrez parfois crer des onglets au cours de lexcution de votre
activit. Imaginons, par exemple, un client de courrier o les diffrents e-mails souvrent
dans leurs propres onglets, an de faciliter le passage dun message lautre. Dans cette
situation, vous ne pouvez pas savoir lavance le nombre donglets ni leur contenu : vous
devez attendre que lutilisateur ouvre un message de courrier.
Heureusement, Android permet galement dajouter dynamiquement des onglets en cours
dexcution. Cet ajout fonctionne exactement comme on vient de le voir, sauf quil faut
Figure 10.5
Lapplication TabDemo
afchant son premier
onglet.
Figure 10.6
La mme application
afchant son second
onglet.


utiliser une autre variante de setContent() qui prend en paramtre une instance de
TabHost.TabContentFactory qui est simplement une mthode de rappel qui sera appele
automatiquement : il suft de fournir une implmentation de createTabContent() et de
lutiliser pour construire et renvoyer la vue qui deviendra le contenu de longlet.
Cette approche est prsente dans le projet Fancy/DynamicTab.
La description de linterface de lactivit met en place les onglets et nen dnit quun
seul, contenant un simple bouton :
<?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">
<TabHost android:id="@+id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TabWidget android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<FrameLayout android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingTop="62px">
<Button android:id="@+id/buttontab"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Bouton semi-aleatoire"
/>
</FrameLayout>
</TabHost>
</LinearLayout>
Nous voulons maintenant ajouter de nouveaux onglets mesure quon clique sur ce bouton,
ce qui se ralise en quelques lignes de code :
public class DynamicTabDemo extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
final TabHost tabs=(TabHost)findViewById(R.id.tabhost);

tabs.setup();

TabHost.TabSpec spec=tabs.newTabSpec("buttontab");
spec.setContent(R.id.buttontab);
spec.setIndicator("Bouton");
tabs.addTab(spec);


tabs.setCurrentTab(0);

Button btn=(Button)tabs.getCurrentView().findViewById(R.id.buttontab);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
TabHost.TabSpec spec=tabs.newTabSpec("tag1");

spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return(new AnalogClock(DynamicTabDemo.this));
}
});
spec.setIndicator("Horloge");
tabs.addTab(spec);
}
});
}
}
On cre un objet TabHost.TabSpec dans la mthode de rappel setOnClickListener()
de notre bouton en lui passant en paramtre une fabrique TabHost.TabContentFactory
anonyme. Cette fabrique, son tour, renvoie la vue qui sera utilise pour longlet ici une
horloge analogique. Le code de construction de cette vue pourrait tre bien plus labor et
utiliser, par exemple, un LayoutInflater pour crer une vue partir dun chier de
description XML.
La Figure 10.7 montre que lactivit nafche quun seul onglet lorsquelle est lance. La
Figure 10.8 montre plusieurs onglets, crs en cours dexcution.
Figure 10.7
Lapplication Dynamic
TabDemo avec son unique
onglet initial.


Intent et View
Dans les exemples prcdents, le contenu de chaque onglet tait une View : un Button, par
exemple. Ce type de conguration est simple mettre en place, mais ce nest pas le seul :
vous pouvez galement intgrer une autre activit partir de votre application via une
Intent.
Les Intent permettent de prciser ce que vous voulez raliser, puis de demander
Android de trouver un moyen de laccomplir, ce qui force souvent lactivit se multi-
plier. chaque fois que vous lancez une application partir du lanceur dapplications
dAndroid, par exemple, ce lanceur cre une Intent et laisse Android ouvrir lactivit
associe. Ce concept, ainsi que le placement des activits dans des onglets, sera dcrit au
Chapitre 25.
Tout faire basculer
Parfois, on souhaite bncier de lavantage des onglets (ne voir que certaines vues la
fois) sans pour autant utiliser leur prsentation graphique (parce que, par exemple, les
onglets prennent trop de place lcran). On peut ainsi prfrer passer dune vue lautre
par un mouvement du doigt sur lcran ou en secouant le terminal.
La bonne nouvelle est que le mcanisme interne des onglets pour basculer entre les vues
est disponible dans le conteneur ViewFlipper, qui peut tre utilis diffremment dun
onglet traditionnel.
Figure 10.8
La mme application,
aprs la cration
de trois onglets en cours
dexcution.


ViewFlipper hrite de FrameLayout, que nous avons utilis plus haut pour dcrire le
fonctionnement interne dun TabWidget. Cependant, il ne montre que la premire vue lle
au dpart : cest vous quil appartient de mettre en place le basculement entre les vues,
soit manuellement par une action de lutilisateur, soit automatiquement par un timer.
Voici, par exemple, le chier de description du projet Fancy/Flipper1, qui utilise un
Button et un ViewFlipper :
<?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"
>
<Button android:id="@+id/flip_me"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Basculemoi !"
/>
<ViewFlipper android:id="@+id/details"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textColor="#FF00FF00"
android:text="Premier panneau"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textColor="#FFFF0000"
android:text="Second panneau"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textColor="#FFFFFF00"
android:text="Troisieme panneau"
/>
</ViewFlipper>
</LinearLayout>
Ce layout dnit trois vues lles de ViewFlipper, chacune tant un TextView contenant
un simple message. Vous pourriez videmment choisir des vues plus complexes.


Pour basculer manuellement entre les vues, nous devons ajouter un couteur au bouton
pour que le basculement ait lieu lorsquon clique dessus :
public class FlipperDemo extends Activity {
ViewFlipper flipper;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

flipper=(ViewFlipper)findViewById(R.id.details);

Button btn=(Button)findViewById(R.id.flip_me);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
flipper.showNext();
}
});
}
}
Il sagit simplement dappeler la mthode showNext() de ViewFlipper, comme pour
nimporte quelle classe ViewAnimator.
Le rsultat est une activit trs simple : un clic sur le bouton fait apparatre le TextView suivant,
en rebouclant sur le premier lorsquils se sont tous afchs (voir Figures 10.9 et 10.10).
Figure 10.9
Lapplication Flipper1
montant le premier
panneau.


Nous pourrions bien sr grer tout cela plus simplement en utilisant un seul TextView et
en modiant son texte et sa couleur chaque clic. Cependant, vous pouvez imaginer que le
contenu du ViewFlipper pourrait tre bien plus compliqu inclure, par exemple, tout ce
que lon peut mettre dans un TabView.
Comme pour un TabWidget, le contenu dun ViewFlipper peut ne pas tre connu lors de
la compilation et, comme pour un TabWidget, il est relativement simple dajouter du contenu
la vole.
Voici, par exemple, le layout dune autre activit, celle du projet Fancy/Flipper2 :
<?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"
>
<ViewFlipper android:id="@+id/details"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</ViewFlipper>
</LinearLayout>
Vous remarquerez que llment ViewFlipper na aucun contenu au moment de la compi-
lation. Notez galement quil ny a pas de bouton pour basculer entre les contenus nous
reviendrons sur ce point dans un instant.
Figure 10.10
La mme application,
aprs basculement vers
le second panneau.


Pour le contenu du ViewFlipper, nous crerons de gros boutons contenant, chacun, un
ensemble de mots quelconques. Nous congurerons galement le ViewFlipper pour quil
boucle automatiquement sur ces widgets, en utilisant une animation pour la transition :
public class FlipperDemo2 extends Activity {
static String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit",
"morbi", "vel", "ligula", "vitae",
"arcu", "aliquet", "mollis", "etiam",
"vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque",
"augue", "purus"};
ViewFlipper flipper;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

flipper=(ViewFlipper)findViewById(R.id.details);

flipper.setInAnimation(AnimationUtils.loadAnimation(this,
R.anim.push_left_in));
flipper.setOutAnimation(AnimationUtils.loadAnimation(this,
R.anim.push_left_out));
for (String item : items) {
Button btn=new Button(this);

btn.setText(item);

flipper.addView(btn,
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT));
}

flipper.setFlipInterval(2000);
flipper.startFlipping();
}
}
Aprs avoir obtenu le widget ViewFlipper partir du chier de disposition, nous
commenons par congurer les animations dentre et de sortie. En termes Android, une
animation est une description de la sortie dun widget ("out") et de son entre ("in") dans
la zone visible de lcran. Les animations sont, toutefois, un sujet complexe, mritent leur
propre chapitre et ne seront donc pas dcrites ici. Pour le moment, il suft de savoir quil
sagit de ressources stockes dans le rpertoire res/anim/ du projet. Dans cet exemple,
nous utilisons deux exemples fournis par le SDK et publis sous les termes de la licence


Apache 2.0. Comme leur nom lindique, les widgets sont "pousss" gauche pour entrer
ou sortir de la zone visible.
Aprs avoir parcouru tous les mots en les transformant en autant de boutons ls de lobjet
ViewFlipper, nous congurons ce dernier pour quil bascule automatiquement entre ses ls
(flipper.setFlipInterval(2000);) et nous lanons le basculement (flipper.start
Flipping();).
Le rsultat est une suite sans n de boutons apparaissant puis disparaissant vers la gauche
au bout de 2 secondes, en tant remplacs chaque fois par le bouton suivant de la
squence. Lensemble revient au premier bouton aprs la disparition du dernier (voir
Figure 10.11).
Ce basculement automatique est utile pour les panneaux dinformation ou les autres situa-
tions dans lesquelles vous voulez afcher beaucoup dinformations dans un espace rduit.
Ces diffrentes vues basculant automatiquement de lune lautre, il serait risqu de
demander aux utilisateurs dinteragir avec elles une vue pourrait disparatre au milieu
dune interaction.
Fouiller dans les tiroirs
Depuis longtemps, les dveloppeurs Android rclamaient un conteneur de type tiroir, fonc-
tionnant comme celui de lcran daccueil, qui contient les icnes pour lancer les appli-
cations. Limplmentation ofcielle existait dans le code open-source mais ntait pas
Figure 10.11
Lapplication Flipper2,
avec une transition
anime.


intgre dans le SDK... jusqu Android 1.5, qui fournit dsormais le widget Sliding
Drawer.
la diffrence de la plupart des autres conteneurs, SlidingDrawer change daspect
puisquil passe dune position ferme une position ouverte. Cette caractristique impli-
que quelques restrictions sur le conteneur dans lequel peut se trouver le SlidingDrawer
puisquil doit permettre plusieurs widgets de cohabiter les uns au-dessus des autres.
RelativeLayout et FrameLayout satisfont cette exigence FrameLayout est un conte-
neur conu spcialement pour empiler les widgets les uns sur les autres. LinearLayout,
en revanche, ne permet pas dempiler des widgets (ils sont placs les uns aprs les autres,
en ligne ou en colonne) , cest la raison pour laquelle un SlidingDrawer ne doit pas tre
un ls direct dun lment LinearLayout.
Voici un exemple tir du projet Fancy/DrawerDemo, avec un SlidingDrawer plac dans
un FrameLayout :
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FF4444CC"
>
<SlidingDrawer
android:id="@+id/drawer"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:handle="@+id/handle"
android:content="@+id/content">
<ImageView
android:id="@id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/tray_handle_normal"
/>
<Button
android:id="@id/content"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="Im in here!"
/>
</SlidingDrawer>
</FrameLayout>
Le SlidingDrawer doit contenir :
une poigne le plus souvent un ImageView, comme ici ;
le contenu du tiroir lui-mme gnralement un conteneur.


En outre, SlidingDrawer doit connatre les valeurs android:id de la poigne et du
contenu en les stockant, respectivement, dans ses attributs android:handle et
android:content. Cela permet au tiroir de savoir comment sanimer lorsquil souvre ou
se ferme.
La Figure 10.12 montre laspect du tiroir ferm, avec la poigne quon lui a fournie. La
Figure 10.13 montre le tiroir ouvert, avec son contenu.
Figure 10.12
Lapplication
DrawerDemo avec
son tiroir ferm.
Figure 10.13
La mme application
avec le tiroir ouvert.


Comme on pourrait sy attendre, on peut ouvrir et refermer le tiroir en traitant les vne-
ments "touchs" partir du code Java. Il existe deux groupes de mthodes : les premires
agissent instantanment (open(), close() et toggle()), les autres utilisent une animation
(animateOpen(), animateClose() et animateToggle()). Le tiroir se verrouille avec
lock() et se dverrouille avec unlock() ; lorsquil est verrouill, le tiroir ne rpond pas
aux touchs sur lcran.
Vous pouvez galement, si vous le souhaitez, enregistrer trois types de mthodes de rappel :
un couteur qui sera appel lors de louverture du tiroir ;
un couteur qui sera appel lors de la fermeture du tiroir ;
un couteur qui sera appel lorsque le tiroir "dle" (cest--dire lorsque lutilisateur
tire ou repousse la poigne).
Le SlidingDrawer du lanceur, par exemple, change licne de sa poigne pour quelle
signie "ouvrir", "fermer" ou "supprimer" (lorsque lon touche pendant un certain temps
une icne du bureau). Pour ce faire, il utilise notamment des mthodes de rappel comme
celles que nous venons de citer.
SlidingDrawer peut tre vertical ou horizontal. Cependant, cette orientation reste identi-
que quelle que soit celle de lcran : en dautres termes, si vous faites pivoter le terminal
ou lmulateur pendant quil excute DrawerDemo, le tiroir souvrira toujours en partant du
bas il ne "colle" pas toujours au bord par rapport celui auquel il sest ouvert. Pour que
le tiroir souvre toujours du mme ct, comme le lanceur, vous aurez besoin de layouts
diffrents pour le mode portrait et le mode paysage un sujet que nous aborderons au
Chapitre 19.
Autres conteneurs intressants
Android fournit galement le conteneur AbsoluteLayout, dont le contenu est dispos en
fonction de coordonnes spciques on lui indique o placer un ls en prcisant ses
coordonnes X, Y, et Android le positionne cet endroit sans poser de question. Ceci a
lavantage de fournir un positionnement prcis ; en revanche, cela signie galement que
les vues nauront un aspect correct que sur des crans dune certaine dimension, moins
dcrire beaucoup de code pour ajuster les coordonnes en fonction de la taille de lcran.
Les crans Android pouvant avoir nimporte quelle taille et ces tailles voluant continuel-
lement, lutilisation dAbsoluteLayout risque de devenir assez problmatique.
Android dispose galement dune nouvelle variante de liste, ExpandableListView, qui
fournit une reprsentation arborescente simplie autorisant deux niveaux de profondeur :
les groupes et les ls. Les groupes contiennent les ls, qui sont les "feuilles" de larbre.
Cette liste ncessite un nouvel ensemble dadaptateurs car la famille ListAdapter ne
fournit aucune information de groupage pour les lments dune liste.


11
Utilisation des menus
Les activits Android peuvent comprendre des menus, comme les applications de bureau
et de certains systmes mobiles Palm OS et Windows Mobile, notamment. Certains
terminaux Android disposent mme dune touche ddie leur ouverture ; les autres
offrent dautres moyens pour les faire apparatre.
En outre, vous pouvez crer des menus contextuels, comme dans la plupart des kits de
dveloppement graphiques. Dans une interface utilisateur classique, ce type de menu
souvre en cliquant sur le bouton droit de la souris ; sur les terminaux mobiles, ils appa-
raissent gnralement lorsque lutilisateur touche un widget pendant un certain temps. Si
un TextView dispose dun menu contextuel et que le terminal a un cran tactile, par exem-
ple, vous pouvez toucher le label pendant une seconde ou deux : un menu surgira alors
pour vous permettre de choisir lune des entres du menu.
La construction des menus Android est diffrente de celle de la plupart des autres kits de
dveloppement. Bien que vous puissiez ajouter des lments un menu, vous navez pas
un contrle total sur son contenu ni sur le moment o il est construit. Une partie des menus
est, en effet, dnie par le systme et cette portion est gre par Android lui-mme.


Variantes de menus
Android distingue les "menus doptions" des "menus contextuels". Les premiers se
dclenchent en appuyant sur le bouton "Menu" du terminal, tandis que les seconds
souvrent lorsquon maintient une pression du doigt sur le widget qui dispose de ce menu.
En outre, les menus doption fonctionnent selon deux modes : icne et tendu. Lorsque
lutilisateur appuie sur le bouton Menu, le menu est en mode icne et nafche que les six
premiers choix sous la forme de gros boutons faciles slectionner, disposs en ligne en
bas de lcran. Si ce menu compte plus de six choix, le sixime bouton afche "Plus"
cliquer sur cette option fait passer le menu en mode tendu, qui afche tous les choix
restants. Lutilisateur peut bien sr faire dler le menu an deffectuer nimporte quel
choix.
Les menus doptions
Au lieu de construire le menu doptions de votre activit dans onCreate(), comme le
reste de votre interface graphique, vous devez implmenter la mthode onCreate
OptionsMenu() en lui passant une instance de la classe Menu.
La premire opration raliser consiste tablir un chanage vers la mthode de la super-
classe (via super.onCreateOptionsMenu(menu)), an quAndroid puisse lui ajouter les
choix ncessaires. Puis vous pouvez ajouter les vtres, comme on lexpliquera bientt.
Si vous devez modier le menu au cours de lactivit (pour, par exemple, dsactiver un
choix devenu inadquat), il suft de manipuler linstance de Menu que vous avez transmise
onCreateOptionsMenu() ou dimplmenter la mthode onPrepareOptionsMenu(), qui
est appele avant chaque afchage du menu.
Pour ajouter des choix au menu, utilisez la mthode add(). Celle-ci est surcharge pour
pouvoir recevoir des combinaisons des paramtres suivants :
Un identiant de groupe (un entier), qui doit tre NONE lorsque vous ne regroupez pas
un ensemble doptions de menu, utilisable avec setGroupCheckable().
Un identiant de choix (galement un entier) servant identier la slection dans la
mthode de rappel onOptionsItemSelected() lorsquune option du menu a t
choisie.
Un identiant dordre (encore un entier), indiquant lemplacement du choix dans le
menu lorsque ce dernier contient des options ajoutes par Android pour linstant,
contentez-vous dutiliser NONE.
Le texte du choix, sous la forme dune String ou dun identiant de ressource.
Toutes les mthodes add() renvoient une instance de MenuItem qui vous permet ensuite
de modier tous les rglages du choix concern (son texte, par exemple). Vous pouvez


galement mettre en place un raccourci pour un choix un mnmonique dun seul carac-
tre, permettant de slectionner ce choix lorsque le menu est visible. Android permet
dutiliser des raccourcis alphabtiques et numriques, mis en place respectivement par
setAlphabeticShortcut() et setNumericShortcut(). Le menu est plac en mode
raccourci alphabtique en passant le paramtre true sa mthode setQwertyMode().
Les identiants de choix et de groupe sont des cls servant dverrouiller certaines fonc-
tionnalits supplmentaires des menus :
Un appel MenuItem#setCheckable() avec un identiant de choix ajoute une case
cocher ct du titre de ce choix. La valeur de cette case bascule lorsque lutilisateur
slectionne ce choix.
Un appel Menu#setGroupCheckable() avec un identiant de groupe permet de
rendre un ensemble de choix mutuellement exclusifs en leur associant des boutons
radio, an de ne pouvoir cocher quune seule option du groupe un instant donn.
Vous pouvez galement appeler addIntentOptions() pour remplir le menu avec des
choix correspondant aux activits dun Intent (voir Chapitre 25).
Enn, il est possible de crer des sous-menus la vole, en appelant addSubMenu() avec
les mmes paramtres que ceux daddMenu(). Android appellera alors onCreatePanel
Menu() en lui passant lidentiant du choix du sous-menu, ainsi quune autre instance de
Menu reprsentant le sous-menu lui-mme. Comme avec onCreateOptionsMenu(), vous
devez tablir un chanage avec la mthode de la superclasse, puis ajouter les choix au
sous-menu. La seule restriction est que vous ne pouvez pas imbriquer sans n les sous-
menus : un menu peut avoir un sous-menu, mais ce dernier ne peut pas contenir lui-mme
de sous-sous-menu.
Votre activit sera prvenue dun choix de lutilisateur par la mthode de rappel onOp
tionsItemSelected(). Vous recevrez alors lobjet MenuItem correspondant ce choix.
Un motif de conception classique consiste utiliser une instruction switch() avec liden-
tiant du menu (item.getItemId()), an dexcuter laction approprie. Notez
quonOptionsItemSelected() est utilise indpendamment du fait que loption choisie
soit un choix du menu de base ou dun sous-menu.
Menus contextuels
Le fonctionnement des menus contextuels est quasiment identique celui des menus
doptions. Les deux diffrences principales concernent leur remplissage et la faon dont
vous serez inform des choix effectus par lutilisateur.
Vous devez dabord indiquer le ou les widgets de votre activit qui disposeront de menus
contextuels. Pour cela, il suft dappeler la mthode registerForContextMenu() partir
de lactivit, en lui passant en paramtre un objet View le widget qui a besoin dun menu
contextuel.


Puis vous devez implmenter la mthode onCreateContextMenu(), qui, entre autres
choses, reoit lobjet View que vous aviez fourni registerForContextMenu(). Si votre
activit doit construire plusieurs menus contextuels, ceci permet didentier le menu
concern.
La mthode onCreateContextMenu() reoit en paramtre le ContextMenu lui-mme, la
View laquelle est associ le menu contextuel et un objet ContextMenu.ContextMe
nuInfo, qui indique llment de la liste qui a t touch et maintenu par lutilisateur (au
cas o vous souhaiteriez personnaliser ce menu contextuel en fonction de cette information).
Il est galement important de remarquer quonCreateContextMenu() est appele
chaque fois que le menu contextuel est sollicit. la diffrence dun menu doptions (qui
nest construit quune seule fois par activit), les menus contextuels sont supprims aprs
utilisation. Par consquent, vous ne pouvez pas compter sur lobjet ContextMenu fourni ;
il faut reconstruire le menu pour quil corresponde aux besoins de votre activit.
Pour tre prvenu de la slection dun choix de menu contextuel, implmentez la mthode
onContextItemSelected() de lactivit. Dans cette mthode de rappel, vous nobtien-
drez que linstance du MenuItem qui a t choisi : si lactivit a plusieurs menus contex-
tuels, vous devez donc vous assurer que les identiants des lments de menus sont
uniques an de pouvoir les traiter de faon approprie. Vous pouvez galement appeler la
mthode getMenuInfo() du MenuItem an dobtenir lobjet ContextMenu.Context
MenuInfo que vous aviez reu dans onCreateContextMenu(). Pour le reste, cette mthode
de rappel se comporte comme onOptionsItemSelected(), que nous avons dcrite dans
la section prcdente.
Illustration rapide
Le projet Menus/Menus contient une version modie de Selection/List (voir Chapi-
tre 9) avec un menu associ. Les menus tant dnis dans le code Java, le chier de
description XML nest pas modi et ne sera donc pas reproduit ici.
Le code Java comprend en revanche quelques nouveauts :
public class MenuDemo extends ListActivity {
TextView selection;
String[] items={"lorem", "ipsum", "dolor", "sit", "amet",
"consectetuer", "adipiscing", "elit", "morbi", "vel",
"ligula", "vitae", "arcu", "aliquet", "mollis",
"etiam", "vel", "erat", "placerat", "ante",
"porttitor", "sodales", "pellentesque", "augue", "purus"};
public static final int EIGHT_ID = Menu.FIRST+1;
public static final int SIXTEEN_ID = Menu.FIRST+2;
public static final int TWENTY_FOUR_ID = Menu.FIRST+3;
public static final int TWO_ID = Menu.FIRST+4;
public static final int THIRTY_TWO_ID = Menu.FIRST+5;


public static final int FORTY_ID = Menu.FIRST+6;
public static final int ONE_ID = Menu.FIRST+7;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, items));
selection=(TextView)findViewById(R.id.selection);

registerForContextMenu(getListView());
}

public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(items[position]);
}

@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenu.ContextMenuInfo menuInfo) {
populateMenu(menu);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
populateMenu(menu);
return(super.onCreateOptionsMenu(menu));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return(applyMenuChoice(item) ||
super.onOptionsItemSelected(item));
}
@Override
public boolean onContextItemSelected(MenuItem item) {
return(applyMenuChoice(item) ||
super.onContextItemSelected(item));
}

private void populateMenu(Menu menu) {
menu.add(Menu.NONE, ONE_ID, Menu.NONE, "1 Pixel");
menu.add(Menu.NONE, TWO_ID, Menu.NONE, "2 Pixels");
menu.add(Menu.NONE, EIGHT_ID, Menu.NONE, "8 Pixels");
menu.add(Menu.NONE, SIXTEEN_ID, Menu.NONE, "16 Pixels");
menu.add(Menu.NONE, TWENTY_FOUR_ID, Menu.NONE, "24 Pixels");
menu.add(Menu.NONE, THIRTY_TWO_ID, Menu.NONE, "32 Pixels");
menu.add(Menu.NONE, FORTY_ID, Menu.NONE, "40 Pixels");
}



private boolean applyMenuChoice(MenuItem item) {
switch (item.getItemId()) {
case ONE_ID:
getListView().setDividerHeight(1);
return(true);

case EIGHT_ID:
getListView().setDividerHeight(8);
return(true);

case SIXTEEN_ID:
getListView().setDividerHeight(16);
return(true);

case TWENTY_FOUR_ID:
getListView().setDividerHeight(24);
return(true);

case TWO_ID:
getListView().setDividerHeight(2);
return(true);

case THIRTY_TWO_ID:
getListView().setDividerHeight(32);
return(true);

case FORTY_ID:
getListView().setDividerHeight(40);
return(true);
}
return(false);
}
}
Dans onCreate(), nous prcisons que le widget liste dispose dun menu contextuel que
nous remplissons laide de notre mthode prive populateMenu() par lintermdiaire
donCreateContextMenu(). Nous implmentons galement la mthode de rappel onCreate
OptionsMenu() pour signaler que notre activit dispose galement dun menu doptions
rempli, lui aussi, grce populateMenu().
Nos implmentations donOptionsItemSelected() (pour les choix dans le menu doptions)
et donContextItemSelected() (pour les choix dans le menu contextuel) dlguent
toutes les deux leur traitement une mthode applyMenuChoice() et appellent leur super-
classe respective si lutilisateur na slectionn aucun choix du menu.
populateMenu() ajoute sept choix de menus, chacun ayant un identiant unique. Comme
nous sommes paresseux, nous nutiliserons pas dicne.


Dans applyMenuChoice(), nous testons si lun des choix du menu a t slectionn,
auquel cas nous xons lpaisseur du sparateur de la liste la valeur correspondante.
Comme le montre la Figure 11.1, lactivit initiale a le mme aspect que ListDemo.
Si lon appuie sur le bouton Menu du terminal, on obtient le menu doptions prsent la
Figure 11.2.
Figure 11.1
Lapplication MenuDemo
lors de son lancement.
Figure 11.2
La mme application,
avec son menu doptions.


La Figure 11.3 montre que les deux choix restants du menu apparaissent lorsque lon
clique sur le bouton "Plus".
Si lon choisit une valeur (16 pixels, par exemple), lpaisseur des traits de sparation de la
liste est modie, comme le montre la Figure 11.4.
La Figure 11.5 montre que lon peut faire apparatre le menu contextuel en "touchant et
maintenant" un lment de la liste.
Figure 11.3
La mme application,
montrant les derniers
choix du menu.
Figure 11.4
La mme application
dans une version peu
esthtique.


L encore, le choix dune option modiera lpaisseur du trait de sparation.
Encore de lination
Nous avons vu au Chapitre 9 que lon pouvait dcrire les vues via des chiers XML et les
transformer par ination en objets View au moment de lexcution. Android permet de
faire de mme avec les menus, qui peuvent tre dcrits dans des chiers XML et trans-
forms en objets lorsquils sont appels. Cette approche permet de sparer la structure
des menus de leur implmentation et facilite le dveloppement des outils de conception de
menus.
Structure XML dun menu
Les chiers XML de description des menus sont placs dans le rpertoire res/menu/,
ct des autres types de ressources du projet. Comme pour les layouts, vous pouvez
utiliser plusieurs chiers de menus XML, chacun ayant son propre nom et lextension
.xml.
Voici, par exemple, le contenu du chier sample.xml, extrait du projet Menus/Inflation :
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/close"
android:title="Fermer"
android:orderInCategory="3"
Figure 11.5
La mme application,
avec son menu contextuel.


android:icon="@drawable/eject" />
<item android:id="@+id/no_icon"
android:orderInCategory="2"
android:title="Sans Icne" />
<item android:id="@+id/disabled"
android:orderInCategory="4"
android:enabled="false"
android:title="Inactif" />
<group android:id="@+id/other_stuff"
android:menuCategory="secondary"
android:visible="false">
<item android:id="@+id/later"
android:orderInCategory="0"
android:title="Avant-dernier" />
<item android:id="@+id/last"
android:orderInCategory="1"
android:title="Dernier" />
</group>
<item android:id="@+id/submenu"
android:orderInCategory="3"
android:title="Sous-menu">
<menu>
<item android:id="@+id/non_ghost"
android:title="Visible"
android:visible="true"
android:alphabeticShortcut="v" />
<item android:id="@+id/ghost"
android:title="Fantome"
android:visible="false"
android:alphabeticShortcut="f" />
</menu>
</item>
</menu>
Voici quelques remarques propos des dnitions des menus en XML :
Llment racine doit sappeler menu.
Les lments ls de menu sont des objets item et group, ces derniers reprsentant une
collection dobjets item pouvant tre manipuls comme un groupe.
Les sous-menus sont dclars en ajoutant un lment menu comme ls dun lment
item et en utilisant ce nouvel lment pour dcrire le contenu du sous-menu.
Pour dtecter quun choix a t slectionn ou faire rfrence un choix ou un
groupe partir du code Java, noubliez pas dutiliser lattribut android:id, exactement
comme pour les chiers XML des vues.


Options des menus et XML
Les lments item et group peuvent contenir plusieurs attributs correspondant aux mthodes
de Menu et MenuItem.
Titre
Le titre dun choix de menu est fourni par lattribut android:title dun lment item.
Ce titre peut tre une chane littrale ou une rfrence vers une ressource chane
(@string/truc, par exemple).
Icne
Les choix de menus peuvent possder des icnes qui sont fournies sous la forme dune
rfrence vers une ressource Drawable (@drawable/eject, par exemple) via lattribut
android:icon dun lment item.
Ordre
Par dfaut, lordre des choix du menu est dtermin par celui de leur apparition dans le
chier XML. Vous pouvez modier cet ordre laide de lattribut android:orderInCa
tegory de llment item. Sa valeur est un indice commenant 0, qui prcise son ordre
dapparition dans la catgorie courante. Les lments group peuvent utiliser lattribut
android:menuCategory pour indiquer une catgorie diffrente de celle par dfaut pour
leurs lments item.
Cependant, il est gnralement plus simple de placer les lments dans le bon ordre dans
le chier XML.
Activation
Les item et les group peuvent tre rendus actifs ou inactifs dans le chier XML, laide
de leur attribut android:enabled. Par dfaut, ils sont considrs comme actifs. Les objets
item et group inactifs apparaissent dans le menu mais ne peuvent pas tre slectionns.
Vous pouvez modier ltat dun item en cours dexcution grce la mthode setEnabled()
de MenuItem et celui dun group via la mthode setGroupEnabled() de Menu.
Visibilit
De mme, les objets item et group peuvent tre visibles ou non, selon la valeur de leur attribut
android:visible. Par dfaut, ils sont visibles ; les objets item et group invisibles napparais-
sent pas dans le menu. Vous pouvez modier cet tat en cours dexcution en appelant la
mthode setVisible() dun MenuItem ou la mthode setGroupVisible() dun Menu.
Dans le chier XML prsent plus haut, le groupe other stuff est invisible. Si nous le
rendons visible dans le code Java, les deux choix du groupe apparatront comme par
magie.


Raccourci
Les item dun menu peuvent avoir des raccourcis en appuyant sur une seule lettre
(android:alphabeticShortcut) ou un chiffre (android:numericShortcut), vous
pouvez slectionner cet item sans utiliser lcran tactile, le pad ou le curseur pour naviguer
dans le menu.
Crer un menu par ination
Lutilisation dun menu dni dans un chier XML est relativement simple puisquil suft
de crer un MenuInflater et de lui demander de crer lobjet Menu :
@Override
public boolean onCreateOptionsMenu(Menu menu) {
theMenu=menu;
new MenuInflater(getApplication())
.inflate(R.menu.sample, menu);
return(super.onCreateOptionsMenu(menu));
}


12
Polices de caractres
Invitablement, un dveloppeur dapplications doit rpondre la question : "Comment
changer cette police de caractres ?" La rponse dpend des polices fournies avec la plate-
forme, de la possibilit den ajouter dautres et du moyen de les appliquer au widget ou
la partie de lapplication concerne. Android ny fait pas exception car il est fourni avec
quelques polices et permet den ajouter dautres. Toutefois, comme avec tout nouvel envi-
ronnement, il faut savoir grer ses spcicits.
Sachez apprcier ce que vous avez
Nativement, Android connat trois polices sous les noms raccourcis "sans", "serif" et
"monospace". Elles forment la srie Droid, cre par Ascender
1
pour lOpen Handset
Alliance.
Pour utiliser ces polices, il suft de les dsigner dans le chier de description XML.
Le layout suivant, par exemple, est extrait du projet Fonts/FontSampler :
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
1. http://www.ascendercorp.com/oha.html.


android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="1">
<TableRow>
<TextView
android:text="sans :"
android:layout_marginRight="4px"
android:textSize="20sp"
/>
<TextView
android:id="@+id/sans"
android:text="Bonjour !"
android:typeface="sans"
android:textSize="20sp"
/>
</TableRow>
<TableRow>
<TextView
android:text="serif :"
android:layout_marginRight="4px"
android:textSize="20sp"
/>
<TextView
android:id="@+id/serif"
android:text="Bonjour !"
android:typeface="serif"
android:textSize="20sp"
/>
</TableRow>
<TableRow>
<TextView
android:text="monospace :"
android:layout_marginRight="4px"
android:textSize="20sp"
/>
<TextView
android:id="@+id/monospace"
android:text="Bonjour !"
android:typeface="monospace"
android:textSize="20sp"
/>
</TableRow>
<TableRow>
<TextView
android:text="Custom :"
android:layout_marginRight="4px"
android:textSize="20sp"
/>
<TextView
android:id="@+id/custom"


android:text="Bonjour !"
android:textSize="20sp"
/>
</TableRow>
</TableLayout>
Cette description construit un tableau montrant les noms courts de quatre polices. Vous
remarquerez que les trois premiers utilisent lattribut android:typeface, dont la valeur
est lune des trois polices prdnies ("sans", par exemple).
Ces trois polices sont parfaites. Cependant, un designer, un chef de projet ou un client peut
trs bien vouloir en utiliser une autre. Vous pouvez galement choisir dutiliser une police
spciale, comme "dingbats", la place dimages PNG.
Le moyen le plus simple dy parvenir consiste fournir la ou les polices concernes avec
votre application. Pour ce faire, il suft de crer un rpertoire assets/ dans larbores-
cence du projet et dy placer les polices TrueType (TTF). Vous pourriez, par exemple,
crer un rpertoire assets/polices/ et y placer les chiers TTF.
Il faut ensuite demander aux widgets dutiliser cette police mais, malheureusement, vous
ne pouvez pas le faire dans le chier de description XML car il ne connat pas celles que
vous avez pu placer dans les assets de lapplication. Cette modication doit donc avoir
lieu dans le code Java :
public class FontSampler extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

TextView tv=(TextView)findViewById(R.id.custom);
Typeface face=Typeface.createFromAsset(getAssets(),
"polices/HandmadeTypewriter.ttf");

tv.setTypeface(face);
}
}
Ici, on rcupre le TextView pour notre exemple "Custom" et lon cre un objet Typeface
en appelant la mthode statique createFromAsset(), qui prend en paramtre lAsset
Manager de lapplication (obtenu via getAssets()) et un chemin relatif de la police
prsent dans le rpertoire assets/.
Ensuite, il suft dindiquer au TextView dutiliser cette police en appelant sa mthode
setTypeface() avec le Typeface que lon vient de crer. Ici, on utilise la police Hand-
made Typewriter
1
(voir Figure 12.1).
1. http://moorstation.org/typoasis/designers/klein07/text01/handmade.htm.


Android semble ne pas apprcier toutes les fontes TrueType. Lorsquil naime pas
une fonte extrieure, Android lui substitue sans mot dire la police Droid Sans
("sans"). Par consquent, si vous essayez dutiliser une police et quelle semble ne
pas fonctionner, cest srement parce quelle est incompatible avec Android, pour
une raison ou pour une autre.
En outre, vous avez intrt mettre en minuscules les noms des chiers de polices,
an de respecter les conventions utilises par vos autres ressources.
Le problme des glyphes
Les polices TrueType peuvent tre assez lourdes, notamment lorsquelles fournissent un
sous-ensemble important des caractres Unicode. La police Handmade Typewriter que
nous avons utilise ci-dessus, par exemple, pse plus de 70 Ko et les polices libres DejaVu
peuvent atteindre 500 Ko chacune. Mme compresses, elles augmentent donc signicati-
vement la taille de votre application : ne vous laissez pas envahir par les polices suppl-
mentaires sous peine de produire une application qui prendra trop de place sur les
tlphones des utilisateurs.
Inversement, il faut savoir que les polices ne contiennent pas ncessairement tous les
glyphes dont vous avez besoin. Par exemple, la classe TextView dAndroid sait crer une
ellipse dun texte, en le tronquant et en ajoutant un caractre dellipse si le texte ne peut
pas tenir dans lespace disponible. Ce comportement peut tre obtenu via lattribut
Figure 12.1
Lapplication
FontSampler.
In
f
o


android:ellipsize, par exemple. Ceci fonctionne trs bien, au moins pour les textes
dune seule ligne.
Lellipse utilise par Android est non pas une simple suite de trois points, mais un vritable
caractre "ellipse", o les trois points sont contenus dans un unique glyphe. Par cons-
quent, les polices que vous utilisez auront besoin de ce glyphe pour utiliser la fonction-
nalit fournie par android:ellipsize.
En outre, Android complte les chanes qui safchent lcran pour que leur longueur (en
nombre de caractres) soit identique celles quelles avaient avant leur mise en ellipse.
Pour que cela puisse fonctionner, Android remplace un seul caractre par lellipse et tous
ceux qui seront supprims par le caractre Unicode ZERO WIDTH NO-BREAK
SPACE (U+FEFF). Ceci signie que les caractres "supplmentaires" aprs lellipse font
partie de la chane, bien quils napparaissent pas lcran.
Les polices que vous utilisez avec TextView et lattribut android:ellipsize doivent
donc galement disposer de ce caractre Unicode, ce qui nest pas toujours le cas. En son
absence, lafchage de la chane courte lcran prsentera des caractres curieux
(des X la n de la ligne, par exemple).
Bien entendu, le dploiement international dAndroid signie galement quune police
doit pouvoir grer nimporte quelle langue choisie par lutilisateur, avec ses caractres
particuliers (le du franais, par exemple).
Par consquent, bien quil soit tout fait possible dutiliser des polices personnalises
avec Android, cela induit de nombreux problmes potentiels et vous devez donc soigneu-
sement peser le pour et le contre.


13
Intgrer le navigateur
de WebKit
Les autres kits de dveloppement graphiques permettent dutiliser le HTML pour prsen-
ter les informations, que ce soit via des minimoteurs HTML (wxWidgets en Java/Swing,
par exemple) ou par lintgration dInternet Explorer dans les applications .NET. Android
fait de mme en vous permettant dintgrer un navigateur web dans vos activits an
dafcher du HTML ou de naviguer sur le Web. Ce navigateur repose sur WebKit, le
moteur de Safari (le navigateur dApple).
Ce navigateur est sufsamment complexe pour disposer de son propre paquetage Java
(android.webkit). La simplicit dutilisation du widget WebView, quant elle, dpend de
vos exigences.
Un navigateur, et en vitesse !
Pour les oprations simples, WebView nest pas trs diffrent des autres widgets dAndroid
on le fait surgir dans un layout, on lui indique dans le code Java lURL vers laquelle il
doit naviguer et cest ni.


Le layout du projet WebKit/Browser, par exemple, ne contient quun WebView :
<?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"
>
<WebView android:id="@+id/webkit"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>
Comme avec tous les autres widgets, vous devez lui indiquer comment remplir lespace
(ici, il occupe tout lespace restant).
Le code Java est tout aussi simple :
package com.commonsware.android.webkit;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
public class BrowserDemo1 extends Activity {
WebView browser;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
browser=(WebView)findViewById(R.id.webkit);

browser.loadUrl("http://commonsware.com");
}
}
La seule partie nouvelle de cette version donCreate() est lappel de la mthode
loadUrl() du widget WebView an de charger une page web.
Nous devons galement modier le chier AndroidManifest.xml, an de demander la
permission daccder Internet ; sinon le navigateur refusera de charger les pages :
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.webkit">
<uses-permission android:name="android.permission.INTERNET" />
<application>


<activity android:name=".BrowserDemo1" android:label="BrowserDemo1">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Comme le montre la Figure 13.1, lactivit obtenue ressemble un navigateur web sans
barre de dlement.
Comme avec le navigateur classique dAndroid, vous pouvez faire dler la page en la
tirant avec le doigt, et le pad directionnel permet de vous positionner successivement sur
tous ses lments actifs.
Il manque toutefois certaines parties pour en faire un vritable navigateur : une barre de
navigation, notamment.
Vous pourriez tre tent de remplacer lURL de ce code source par une autre, comme celle
de la page daccueil de Google ou toute autre page utilisant JavaScript. Cependant, par
dfaut, un widget WebView dsactive JavaScript : pour lactiver, vous devez appeler sa
mthode getSettings().setJavaScriptEnabled(true);. Nous reviendrons sur ce point
un peu plus loin dans ce chapitre.
Figure 13.1
Lapplication Browser1.


Chargement immdiat
Il existe deux moyens de faire entrer du contenu dans un widget WebView. Le premier,
comme nous lavons vu, consiste indiquer une URL au navigateur et lui faire afcher
cette page via loadUrl(). Le navigateur accdera Internet par les moyens mis disposi-
tion du terminal cet instant prcis (Wi, rseau cellulaire, partage de donnes par Bluetooth,
pigeons voyageurs bien entrans, etc.). Lautre solution consiste utiliser loadData() en
lui fournissant le code HTML que lon veut afcher dans le navigateur. Cette dernire
mthode est notamment utile pour :
afcher un manuel install sous forme de chier avec lapplication ;
afcher des extraits HTML rcuprs par un autre traitement la description dune
entre dun ux Atom, par exemple ;
produire une interface utilisateur entirement en HTML au lieu dutiliser les widgets
Android.
Il existe deux variantes de loadData(). La plus simple permet de fournir sous forme de
chanes un contenu, son type MIME et lencodage utilis. Pour un document HTML classique,
le type MIME sera gnralement text/html et lencodage, UTF-8.
Si vous remplacez lappel de loadUrl() par le code suivant dans lexemple prcdent,
vous obtiendrez le rsultat prsent la Figure 13.2.
browser.loadData("<html><body>Bonjour !</body></html>",
"text/html", "UTF-8");
Le code complet de cette application est disponible dans le projet WebKit/Browser2.
Figure 13.2
Lapplication Browser2.


Navigation au long cours
Comme on la dj mentionn, le widget WebView ne comprend pas de barre de naviga-
tion, ce qui permet de lutiliser des endroits o cette barre serait inutile et consommerait
inutilement de lespace lcran. Ceci tant dit, il est tout fait possible de lui ajouter des
fonctionnalits de navigation, condition de fournir linterface graphique ncessaire.
WebView dispose notamment des mthodes suivantes :
reload() permet de recharger la page courante.
goBack() permet de revenir un pas en arrire dans lhistorique du navigateur, tandis
que canGoBack() teste sil existe un historique pass.
goForward() permet davancer dun pas dans lhistorique du navigateur, tandis que
canGoForward() teste sil existe un historique futur.
goBackOrForward() permet de reculer ou davancer dans lhistorique en fonction de
son paramtre. Une valeur ngative reprsente le nombre de pas vers larrire, une
valeur positive, le nombre de pas vers lavant.
canGoBackOrForward() teste si le navigateur peut reculer ou avancer du nombre de
pas indiqu dans lhistorique (en suivant la mme convention positif/ngatif que pour
goBackOrForward()).
clearCache() vide le cache du navigateur et clearHistory() nettoie son historique.
Amuser le client
Si vous utilisez un widget WebView pour crer une interface utilisateur locale (et non pour
naviguer sur le Web), vous aurez besoin de le contrler certains moments, notamment
lorsque les utilisateurs cliqueront sur des liens. Vous devrez vous assurer que ces liens sont
correctement grs, soit en rechargeant votre propre contenu dans le WebView (en soumet-
tant un Intent Android pour quil ouvre lURL dans un vrai navigateur), soit par
dautres moyens (voir Chapitre 25).
La mthode setWebViewClient(), qui prend en paramtre une instance dune implmen-
tation de WebViewClient, permet de placer un hook dans lactivit WebView. Lobjet
fourni sera alors prvenu dun grand nombre dactivits, allant de la rcupration dune
partie dune page (onPageStarted(), etc.) la ncessit pour lapplication de prendre en
compte un vnement utilisateur ou circonstanciel (onTooManyRedirects(), onReceived
HttpAuthRequest(), etc.).
Un hook classique est shouldOverrideUrlLoading(), qui prend en paramtre une URL
et lobjet WebView lui-mme et qui doit renvoyer true si vous traitez la requte ou false
si vous prfrez utiliser le traitement par dfaut (qui consiste rcuprer la page web dsi-
gne par lURL). Pour une application de lecture de ux RSS, par exemple, le lecteur ne


comprendra srement pas un navigateur web complet, avec toutes les options de naviga-
tion possibles ; si lutilisateur clique sur une URL, vous utiliserez donc probablement un
Intent pour demander Android de charger cette page dans un vrai navigateur. Cepen-
dant, si vous avez insr une URL "bidon" dans le code HTML, reprsentant un lien vers
un contenu fourni par une activit, vous pouvez modier vous-mme le WebView.
Modions notre premier exemple pour quil devienne lquivalent de la premire applica-
tion de ce livre : un programme qui afche la date et lheure courantes lorsque lon clique
sur un bouton. Le code Java de ce projet WebKit/Browser3 est le suivant :
public class BrowserDemo3 extends Activity {
WebView browser;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
browser=(WebView)findViewById(R.id.webkit);
browser.setWebViewClient(new Callback());

loadTime();
}

void loadTime() {
String page="<html><body><a href=\"clock\">"
+new Date().toString()
+"</a></body></html>";

browser.loadDataWithBaseURL("x-data://base", page,
"text/html", "UTF-8",
null);
}
private class Callback extends WebViewClient {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
loadTime();

return(true);
}
}
}
On charge une simple page web dans le navigateur (via la mthode loadTime()). Cette
page contient la date et lheure courantes sous la forme dun lien vers lURL /clock. On
attache galement une instance dune sous-classe de WebViewClient en fournissant notre
implmentation de shouldOverrideUrlLoading(). Ici, peu importe lURL : nous voulons
simplement recharger le WebView avec loadTime().
Le rsultat de lexcution de cette activit est prsent la Figure 13.3.


En slectionnant le lien et en cliquant sur le bouton central du pad, ce lien sera considr
comme "cliqu", ce qui aura pour effet de reconstruire la page avec une nouvelle date/
heure.
Rglages, prfrences et options
Votre navigateur web favori contient un menu "Rglages", "Prfrences" ou "Options".
Ajout aux contrles de la barre doutils, ce menu vous permet dadapter de nombreux
aspects du comportement du navigateur, allant des polices quil utilise la gestion de
JavaScript.
Vous pouvez modier les rglages dun widget WebView en fonction de vos besoins, via
linstance de WebSettings qui est renvoye par la mthode getSettings() du widget.
Beaucoup doptions de WebSettings sont ainsi votre disposition. Bien que la plupart
semblent assez sotriques (setFantasyFontFamily(), par exemple), quelques-unes
peuvent vous tre plus utiles :
Les mthodes setDefaultFontSize() (pour une taille en points) ou setTextSize()
(qui utilise des constantes comme LARGER et SMALLEST pour exprimer une taille rela-
tive) permettent de modier la taille de la police.
setJavaScriptEnabled() et setJavaScriptCanOpenWindowsAutomatically()
permettent, respectivement, de dsactiver totalement JavaScript et de lempcher
douvrir des fentres popup.
Figure 13.3
Lapplication Browser3.


setUserAgent() permet de contrler le rendu dun site web. Si son paramtre vaut 0,
le WebView passera au site une chane user agent indiquant quil sagit dun naviga-
teur mobile alors que, sil vaut 1, le widget enverra une chane indiquant quil sagit
dun navigateur qui sexcute sur une machine de bureau.
Les modications que vous apportez ne sont pas persistantes : vous devez donc les stocker
quelque part (via le moteur de prfrences dAndroid, par exemple) si vous autorisez vos
utilisateurs modier ces rglages au lieu de les coder en dur dans votre application.


14
Afchage de messages
surgissant
Une activit (ou toute autre partie dune application Android) a parfois besoin de sexprimer.
Toutes les interactions avec les utilisateurs ne seront pas soignes, bien propres et conte-
nues dans des activits composes de vues. Les erreurs surgissent ; les tches en arrire-
plan peuvent mettre plus de temps que prvu pour se terminer ; parfois, les vnements,
comme les messages entrants, sont asynchrones. Dans ce type de situation, vous pouvez
avoir besoin de communiquer avec lutilisateur en vous affranchissant des limites de
linterface classique.
Ce nest videmment pas nouveau : les messages derreur prsents dans des botes de
dialogue existent depuis trs longtemps. Cependant, il existe galement des indicateurs
plus subtils, allant des icnes apparaissant dans une barre des tches (Windows) des
icnes animes dans un "dock" (Mac OS X), en passant par un tlphone qui vibre.
Android dispose de plusieurs moyens dalerter les utilisateurs par dautres systmes que
ceux des activits classiques. Les notications, par exemple, sont intimement lies aux
Intents et aux services et seront donc prsentes au Chapitre 32. Nous tudierons ici deux
mthodes permettant de faire surgir des messages : les toasts et les alertes.


Les toasts
Un toast est un message transitoire, ce qui signie quil safche et disparat de lui-mme,
sans intervention de lutilisateur. En outre, il ne modie pas le focus de lactivit
courante : si lutilisateur est en train dcrire le prochain trait fondamental sur lart de la
programmation, ses frappes au clavier ne seront donc pas captures par le message.
Un toast tant transitoire, vous navez aucun moyen de savoir si lutilisateur la remarqu.
Vous ne recevrez aucun accus de rception de sa part et le message ne restera pas afch
sufsamment longtemps pour ennuyer lutilisateur. Un Toast est donc essentiellement
conu pour diffuser des messages davertissement pour annoncer quune longue tche en
arrire-plan sest termine, que la batterie est presque vide, etc.
La cration dun toast est assez simple. La classe Toast fournit une mthode statique
makeText() qui prend un objet String (ou un identiant dune ressource textuelle) en
paramtre et qui renvoie une instance de Toast. Les autres paramtres de cette mthode
sont lactivit (ou tout autre contexte) et une dure valant LENGTH SHORT ou LENGTH LONG
pour exprimer la dure relative pendant laquelle le message restera visible.
Si vous prfrez crer votre Toast partir dune View au lieu dune simple chane
ennuyeuse, il suft de crer linstance via le constructeur (qui attend un Context) puis
dappeler setView() pour lui indiquer la vue utiliser et setDuration() pour xer sa
dure.
Lorsque le toast est congur, il suft dappeler sa mthode show() pour quil safche.
Les alertes
Si vous prfrez utiliser le style plus classique des botes de dialogue, choisissez plutt
AlertDialog. Comme toute bote de dialogue modale, un AlertDialog souvre, prend le
focus et reste afch tant que lutilisateur ne le ferme pas. Ce type dafchage convient
donc bien aux erreurs critiques, aux messages de validation qui ne peuvent pas tre af-
chs correctement dans linterface de base de lactivit ou toute autre information dont
vous voulez vous assurer la lecture immdiate par lutilisateur.
Pour crer un AlertDialog, le moyen le plus simple consiste utiliser la classe Builder,
qui offre un ensemble de mthodes permettant de congurer un AlertDialog. Chacune de
ces mthodes renvoie le Builder an de faciliter le chanage des appels. la n, il suft
dappeler la mthode show() de lobjet Builder pour afcher la bote de dialogue.
Voici les mthodes de conguration de Builder les plus utilises :
setMessage() permet de dnir le "corps" de la bote de dialogue partir dun simple
message de texte. Son paramtre est un objet String ou un identiant dune ressource
textuelle.


setTitle() et setIcon() permettent de congurer le texte et/ou licne qui apparatra
dans la barre de titre de la bote de dialogue.
setPositiveButton(), setNeutralButton() et setNegativeButton() permettent
dindiquer les boutons qui apparatront en bas de la bote de dialogue, leur emplace-
ment latral (respectivement, gauche, au centre ou droite), leur texte et le code qui
sera appel lorsquon clique sur un bouton (en plus de refermer la bote de dialogue).
Si vous devez faire dautres congurations que celles proposes par Builder, appelez la
mthode create() la place de show() : vous obtiendrez ainsi une instance dAlertDialog
partiellement construite que vous pourrez congurer avant dappeler lune des mthodes
show() de lobjet AlertDialog lui-mme.
Aprs lappel de show(), la bote de dialogue safche et attend une saisie de lutilisateur.
Mise en uvre
Voici le chier de description XML du projet Messages/Message :
<?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" >
<Button
android:id="@+id/alert"
android:text="Declencher une alerte"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/toast"
android:text="Lever un toast"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Et voici son code Java :
public class MessageDemo extends Activity implements View.OnClickListener {
Button alert;
Button toast;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);



setContentView(R.layout.main);

alert=(Button)findViewById(R.id.alert);
alert.setOnClickListener(this);
toast=(Button)findViewById(R.id.toast);
toast.setOnClickListener(this);
}

public void onClick(View view) {
if (view==alert) {
new AlertDialog.Builder(this)
.setTitle("MessageDemo")
.setMessage("Oups !")
.setNeutralButton("Fermeture",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dlg, int sumthin) {
// On ne fait rien la bote se fermera ellemme
}
})
.show();
}
else {
Toast
.makeText(this, "<Clac, Clac>", Toast.LENGTH_SHORT)
.show();
}
}
}
Ce layout est tout ce quil y a de plus classique il comprend simplement deux boutons
pour dclencher lalerte et le toast.
Lorsque nous cliquons sur le bouton "Dclencher une alerte", nous crons un Builder
(avec new Builder(this)) pour congurer le titre (setTitle("MessageDemo")), le
message (setMessage("Oups!!") et le bouton central (setNeutralButton("Ferme
ture", new OnClickListener() ) avant dafcher la bote de dialogue. Quand on
clique sur ce bouton, la mthode de rappel OnClickListener() ne fait rien : le seul fait
quon ait appuy sur le bouton provoque la fermeture de la bote de dialogue. Toutefois,
vous pourriez mettre jour des informations de votre activit en fonction de laction de
lutilisateur, notamment sil peut choisir entre plusieurs boutons. Le rsultat est une bote
de dialogue classique, comme celle de la Figure 14.1.
Lorsque lon clique sur le bouton "Lever un toast", la classe Toast cre un toast textuel
(avec makeText(this, "<Clac, Clac>", LENGTH SHORT)), puis lafche avec show().
Le rsultat est un message de courte dure qui ninterrompt pas lactivit (voir Figure 14.2).


Figure 14.1
Lapplication Message
Demo aprs avoir cliqu
sur le bouton "Dclencher
une alerte".
Figure 14.2
La mme application,
aprs avoir cliqu sur le
bouton "Lever un toast".


15
Utilisation des threads
Tout le monde souhaite que ses activits soient ractives. Rpondre rapidement un
utilisateur (en moins de 200 millisecondes) est un bon objectif. Au minimum, il faut
fournir une rponse en moins de 5 secondes ; sinon lActivityManager pourrait dci-
der de jouer le rle de la faucheuse et tuer votre activit car il considre quelle ne
rpond plus.
Mais, videmment, votre programme peut devoir accomplir une tche qui seffectue en un
temps non ngligeable. Il y a deux moyens de traiter ce problme :
raliser les oprations coteuses dans un service en arrire-plan en se servant de noti-
cations pour demander aux utilisateurs de revenir votre activit ;
effectuer le travail coteux dans un thread en arrire-plan.
Android dispose dune vritable panoplie de moyens pour mettre en place des threads en
arrire-plan tout en leur permettant dinteragir proprement avec linterface graphique, qui,
elle, sexcute dans un thread qui lui est ddi. Parmi eux, citons les objets Handler et le
postage dobjets Runnable destination de la vue.


Les handlers
Le moyen le plus souple de crer un thread en arrire-plan avec Android consiste crer
une instance dune sous-classe de Handler. Vous navez besoin que dun seul objet
Handler par activit et il nest pas ncessaire de lenregistrer manuellement ou quoi que
ce soit dautre la cration de linstance suft lenregistrer auprs du sous-systme de
gestion des threads.
Le thread en arrire-plan peut communiquer avec le Handler, qui effectuera tout son
travail dans le thread de linterface utilisateur de votre activit. Cest important car les
changements de cette interface la modication de ses widgets, par exemple ne doivent
intervenir que dans le thread de linterface de lactivit.
Vous avez deux possibilits pour communiquer avec le Handler : les messages et les
objets Runnable.
Les messages
Pour envoyer un Message un Handler, appelez dabord la mthode obtainMessage()
an dextraire lobjet Message du pool. Cette mthode est surcharge pour vous permettre
de crer un Message vide ou des messages contenant des identiants de messages et des
paramtres. Plus le traitement que doit effectuer le Handler est compliqu, plus il y a de
chances que vous deviez placer des donnes dans le Message pour aider le Handler
distinguer les diffrents vnements.
Puis envoyez le Message au Handler en passant par sa le dattente des messages, laide
de lune des mthodes de la famille sendMessage...() :
sendMessage() place immdiatement le message dans la le.
sendMessageAtFrontOfQueue() place immdiatement le message en tte de le (au
lieu de le mettre la n, ce qui est le comportement par dfaut). Votre message aura
donc priorit par rapport tous les autres.
sendMessageAtTime() place le message dans la le linstant indiqu, qui sexprime
en millisecondes par rapport luptime du systme (SystemClock.uptimeMillis()).
sendMessageDelayed() place le message dans la le aprs un certain dlai, exprim
en millisecondes.
Pour traiter ces messages, votre Handler doit implmenter la mthode handleMes
sage(), qui sera appele pour chaque message qui apparat dans la le dattente. Cest l
que le handler peut modier linterface utilisateur sil le souhaite. Cependant, il doit le
faire rapidement car les autres oprations de linterface sont suspendues tant quil na pas
termin.


Le projet Threads/Handler, par exemple, cre une ProgressBar et la modie via un
Handler. Voici son chier de description 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"
>
<ProgressBar android:id="@+id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
La ProgressBar, qui a une largeur et une hauteur normales, utilise galement la proprit
style, qui ne sera pas dcrite dans ce livre. Pour linstant, il suft de considrer quelle
indique que la barre de progression devra tre trace horizontalement, en montrant la
proportion de travail effectu.
Voici le code Java :
package com.commonsware.android.threads;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ProgressBar;
public class HandlerDemo extends Activity {
ProgressBar bar;
Handler handler=new Handler() {
@Override
public void handleMessage(Message msg) {
bar.incrementProgressBy(5);
}
};
boolean isRunning=false;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
bar=(ProgressBar)findViewById(R.id.progress);
}

public void onStart() {
super.onStart();
bar.setProgress(0);

Thread background=new Thread(new Runnable() {


public void run() {
try {
for (int i=0;i<20 && isRunning;i++) {
Thread.sleep(1000);
handler.sendMessage(handler.obtainMessage());
}
}
catch (Throwable t) {
// Termine le thread en arrireplan
}
}
});

isRunning=true;
background.start();
}

public void onStop() {
super.onStop();
isRunning=false;
}
}
Une partie de la construction de lactivit passe par la cration dune instance de Handler
contenant notre implmentation de handleMessage(). Pour chaque message reu, nous
nous contentons de faire avancer la barre de progression de 5 points.
Dans la mthode onStart(), nous congurons un thread en arrire-plan. Dans une vraie
application, celui-ci effectuerait une tche signicative mais, ici, nous nous mettons
simplement en pause pendant 1 seconde, nous postons un Message au Handler et nous
rptons ces deux oprations vingt fois. Combin avec la progression de 5 points de la
position de la ProgressBar, ce traitement fera donc avancer la barre travers tout lcran
puisque la valeur maximale par dfaut de la barre est 100. Vous pouvez ajuster ce maxi-
mum avec la mthode setMax() pour, par exemple, quil soit gal au nombre de lignes de
la base de donnes que vous tes en train de traiter et avancer de un la position pour
chaque ligne.
Notez que lon doit ensuite quitter onStart(). Cest un point crucial : la mthode
onStart() est appele dans le thread qui gre linterface utilisateur de lactivit an
quelle puisse modier les widgets et tout ce qui affecte cette interface la barre de titre,
par exemple. Ceci signie donc que lon doit sortir donStart(), la fois pour laisser le
Handler faire son travail et pour quAndroid ne pense pas que notre activit est bloque.
La Figure 15.1 montre que lactivit se contente dafcher une barre de progression hori-
zontale.


Les runnables
Si vous prfrez ne pas vous ennuyer avec les objets Message, vous pouvez galement
passer des objets Runnable au Handler, qui les excutera dans le thread de linterface
utilisateur. Handler fournit un ensemble de mthodes post...() pour faire passer les
objets Runnable an quils soient traits.
Excution sur place
Les mthodes post() et postDelayed() de Handler, qui permettent dajouter des objets
Runnable dans la le dattente des vnements, peuvent galement tre utilises avec les
vues. Ceci permet de simplier lgrement le code car vous pouvez alors vous passer dun
objet Handler. Toutefois, vous perdrez galement un peu de souplesse. En outre, la classe
Handler existe depuis longtemps dans Android et a probablement t mieux teste que
cette technique.
O est pass le thread de mon interface utilisateur ?
Parfois, vous pouvez ne pas savoir si vous tes en train dexcuter le thread de linterface
utilisateur de votre application. Si vous fournissez une partie de votre code sous la forme
dune archive JAR, par exemple, vous pouvez ne pas savoir si ce code est excut dans le
thread de linterface ou dans un thread en arrire-plan.
Pour rsoudre ce problme, la classe Activity fournit la mthode runOnUiThread(). Elle
fonctionne comme les mthodes post() de Handler et View car elle met dans une le un
Figure 15.1
Lapplication
HandlerDemo.


Runnable pour quil sexcute dans le thread de linterface si vous ny tes pas dj. Dans
le cas contraire, elle appelle immdiatement le Runnable. Vous disposez ainsi du meilleur
des deux mondes : aucun dlai si vous tes dans le thread de linterface et aucun problme
si vous ny tes pas.
Dsynchronisation
Avec AsyncTask, Android 1.5 a introduit une nouvelle faon de concevoir les oprations
en arrire-plan. Grce cette seule classe bien conue, Android soccupera du dcoupage
du travail entre le thread de linterface et un thread en arrire-plan. En outre, il allouera et
dsallouera lui-mme le thread en arrire-plan et grera une petite le de tches, ce qui
accentue encore le ct "prt lemploi" dAsyncTask.
La thorie
Il existe un dicton bien connu des vendeurs : "Lorsquun client achte un foret de 12 mm
dans un magasin, il ne veut pas un foret de 13 mm : il veut percer des trous de 13 mm."
Les magasins ne peuvent pas vendre les trous, ils vendent donc les outils (des perceuses et
des forets) qui facilitent la cration des trous.
De mme, les dveloppeurs Android qui se battent avec la gestion des threads en arrire-
plan ne veulent pas vraiment des threads en arrire-plan : ils souhaitent quun certain
travail seffectue en dehors du thread de linterface, an que les utilisateurs ne soient pas
bloqus et que les activits ne reoivent pas la redoutable erreur "application not respon-
ding" (ANR). Bien quAndroid ne puisse pas, par magie, faire en sorte quun traitement ne
consomme pas le temps allou au thread de linterface, il peut offrir des techniques
permettant de faciliter et de rendre plus transparentes ces oprations en arrire-plan.
AsyncTask en est un exemple.
Pour utiliser AsyncTask, il faut :
crer une sous-classe dAsyncTask, gnralement sous la forme dune classe interne
celle qui utilise la tche (une activit, par exemple) ;
rednir une ou plusieurs mthodes dAsyncTask pour raliser le travail en arrire-
plan ainsi que toute opration associe la tche et qui doit seffectuer dans le thread
de linterface (la mise jour de la progression, par exemple) ;
le moment venu, crer une instance de la sous-classe dAsyncTask et appeler
execute() pour quelle commence son travail.
Vous navez pas besoin :
de crer votre propre thread en arrire-plan ;
de terminer ce thread au moment voulu ;


dappeler des mthodes pour que des traitements seffectuent dans le thread de linter-
face.
AsyncTask, gnricit et paramtres variables
La cration dune sous-classe dAsyncTask nest pas aussi simple que, par exemple,
limplmentation de linterface Runnable car AsyncTask est une classe gnrique ; vous
devez donc lui indiquer trois types de donnes :
Le type de linformation qui est ncessaire pour le traitement de la tche (les URL
tlcharger, par exemple).
Le type de linformation qui est passe la tche pour indiquer sa progression.
Le type de linformation qui est passe au code aprs la tche lorsque celle-ci sest
termine.
En outre, les deux premiers types sont, en ralit, utiliss avec des paramtres variables, ce
qui signie que votre sous-classe dAsyncTask les utilise via des tableaux.
Tout cela devrait devenir plus clair ltude dun exemple.
Les tapes dAsyncTask
Pour parvenir vos ns, vous pouvez rednir quatre mthodes dAsyncTask.
La seule que vous devez rednir pour que votre classe soit utilisable sappelle doInBack
ground(). Elle sera appele par AsyncTask dans un thread en arrire-plan et peut
sexcuter aussi longtemps quil le faut pour accomplir lopration ncessaire cette tche
spcique. Cependant, noubliez pas que les tches doivent avoir une n il est dconseill
dutiliser AsyncTask pour raliser une boucle innie.
La mthode doInBackground() recevra en paramtre un tableau variable contenant des
lments du premier des trois types mentionns ci-dessus les type des donnes ncessai-
res au traitement de la tche. Si la mission de cette tche consiste, par exemple, tlchar-
ger un ensemble dURL, doInBackground() recevra donc un tableau contenant toutes
ces URL.
Cette mthode doit renvoyer une valeur du troisime type de donnes mentionn le
rsultat de lopration en arrire-plan.
Vous pouvez galement rednir onPreExecute(), qui est appele partir du thread de
linterface utilisateur avant que le thread en arrire-plan nexcute doInBackground().
Dans cette mthode, vous pourriez par exemple initialiser une ProgressBar ou tout autre
indicateur du dbut du traitement.
De mme, vous pouvez rednir onPostExecute(), qui est appele partir du thread de
linterface graphique lorsque doInBackground() sest termine. Cette mthode reoit en
paramtre la valeur renvoye par doInBackground() (un indicateur de succs ou dchec,


par exemple). Vous pouvez donc utiliser cette mthode pour supprimer la ProgressBar et
utiliser le travail effectu en arrire-plan pour mettre jour le contenu dune liste.
onProgressUpdate() est la quatrime mthode que vous pouvez rednir. Si doInBack
ground() appelle la mthode publishProgress() de la tche, lobjet ou les objets
passs cette mthode seront transmis onProgressUpdate(), mais dans le thread de
linterface. onProgressUpdate() peut donc alerter lutilisateur que la tche en arrire-
plan a progress en mettant jour une ProgressBar ou en continuant une animation,
par exemple. Cette mthode reoit un tableau variable dlments du second type
mentionn plus haut, contenant les donnes publies par doInBackground() via publish
Progress().
Exemple de tche
Comme on la indiqu, limplmentation dune classe AsyncTask nest pas aussi simple
que celle dune classe Runnable. Cependant, une fois passe la difcult de la gnricit
et des paramtres variables, ce nest pas si compliqu que cela.
Le projet Threads/Asyncer implmente une ListActivity qui utilise une AsyncTask :
package com.commonsware.android.async;
import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import java.util.ArrayList;
public class AsyncDemo extends ListActivity {
private static String[] items={"lorem", "ipsum", "dolor",
"sit", "amet", "consectetuer",
"adipiscing", "elit", "morbi",
"vel", "ligula", "vitae",
"arcu", "aliquet", "mollis",
"etiam", "vel", "erat",
"placerat", "ante",
"porttitor", "sodales",
"pellentesque", "augue",
"purus"};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);


setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
new ArrayList()));

new AddStringTask().execute();
}

class AddStringTask extends AsyncTask<Void, String, Void> {
@Override
protected Void doInBackground(Void... inutilise) {
for (String item : items) {
publishProgress(item);
SystemClock.sleep(200);
}

return(null);
}

@Override
protected void onProgressUpdate(String... item) {
((ArrayAdapter)getListAdapter()).add(item[0]);
}

@Override
protected void onPostExecute(Void inutilise) {
Toast
.makeText(AsyncDemo.this, "Fini !", Toast.LENGTH_SHORT)
.show();
}
}
}
Il sagit dune autre variante de la liste de mots lorem ipsum qui a dj t souvent utilise
dans ce livre. Cette fois-ci, au lieu de simplement fournir la liste un ArrayAdapter, on
simule la cration de ces mots dans un thread en arrire-plan laide de la classe
AddStringTask, notre implmentation dAsyncTask.
Les sections qui suivent passent en revue les diffrentes parties de ce code.
Dclaration dAddStringTask
class AddStringTask extends AsyncTask<Void, String, Void> {
On utilise ici la gnricit pour congurer les types de donnes spciques dont nous
aurons besoin dans AddStringTask :
Nous navons besoin daucune information de conguration ; par consquent, le
premier type est Void.
Nous voulons passer onProgressUpdate() chaque chane "produite" par notre tche
en arrire-plan, an de pouvoir lajouter la liste. Le second type est donc String.
Nous ne renvoyons pas de rsultat proprement parler (hormis les mises jour) ; par
consquent, le troisime type est Void.


La mthode doInBackground()
@Override
protected Void doInBackground(Void... inutilise) {
for (String item : items) {
publishProgress(item);
SystemClock.sleep(200);
}
return(null);
}
La mthode doInBackground() tant appele dans un thread en arrire-plan, elle peut
durer aussi longtemps quon le souhaite. Dans une vraie application, nous pourrions, par
exemple, parcourir une liste dURL et toutes les tlcharger. Ici, nous parcourons notre
liste statique de mots latins en appelant publishProgress() pour chacun deux, puis nous
nous mettons en sommeil pendant le cinquime de seconde pour simuler un traitement.
Comme nous avons choisi de nutiliser aucune information de conguration, nous navons
normalement pas besoin de paramtre pour doInBackground(). Cependant, le contrat
dimplmentation dAsyncTask prcise quil faut prendre un tableau variable dlments
du premier type gnrique : cest la raison pour laquelle le paramtre de cette mthode est
Void... inutilise.
En outre, ayant choisi de ne rien renvoyer alors que le contrat stipule que nous devons
renvoyer un objet du troisime type gnrique, le rsultat de cette mthode est de type
Void et nous renvoyons null.
La mthode onProgressUpdate()
@Override
protected void onProgressUpdate(String... item) {
((ArrayAdapter)getListAdapter()).add(item[0]);
}
La mthode onProgressUpdate() est appele dans le thread de linterface et nous voulons
quelle signale lutilisateur que lon est en train de charger les chanes. Ici, nous ajoutons
simplement la chane lArrayAdapter, an quelle soit ajoute la n de la liste.
Cette mthode attend un tableau variable dlments du deuxime type gnrique
(String... ici). Comme nous ne lui passons quune seule chane par appel publish
Progress(), on ne doit examiner que le premier lment du tableau item.
La mthode onPostExecute()
@Override
protected void onPostExecute(Void inutilise) {
Toast


.makeText(AsyncDemo.this, "Fini !", Toast.LENGTH_SHORT)
.show();
}
La mthode onPostExecute() est appele dans le thread de linterface et nous voulons
quelle signale que lopration qui sexcutait en arrire-plan sest termine. Dans une
vraie application, cela pourrait consister supprimer une ProgressBar ou stopper
une animation, par exemple. Ici, nous nous contentons de "lever un toast".
Son paramtre est Void inutilise pour respecter le contrat dimplmentation.
Lactivit
new AddStringTask().execute();
Pour utiliser AddStringTask, nous crons simplement une instance de cette classe et
appelons sa mthode execute(). Ceci a pour effet de lancer la chane des vnements qui
font raliser le travail par le thread en arrire-plan.
Si AddStringTask avait demand des paramtres de conguration, nous naurions pas
utilis Void comme premier type gnrique et le constructeur aurait attendu zro ou plusieurs
paramtres de ce type. Ces valeurs auraient ensuite t passes doInBackground().
Le rsultat
Comme le montre la Figure 15.2, cette activit afche la liste qui se remplit "en temps
rel" pendant quelques secondes, puis afche un toast pour indiquer que le traitement est
termin.
Figure 15.2
Lapplication AsyncDemo,
au milieu du chargement
de la liste des mots.


viter les piges
Les threads en arrire-plan ne sont pas de mignons bbs bien sages, mme en passant par
le systme des Handler dAndroid. Non seulement ils ajoutent de la complexit, mais ils
ont un cot rel en termes de mmoire, de CPU et de batterie.
Cest pour cette raison que vous devez tenir compte dun grand nombre de scnarios lorsque
vous les utilisez. Parmi eux :
Les utilisateurs peuvent interagir avec linterface graphique de votre activit pendant
que le thread en arrire-plan sessoufe. Si son travail est altr ou modi par une
action de lutilisateur, vous devez lindiquer au thread. Les nombreuses classes du
paquetage java.util.concurrent vous aideront dans cette tche.
Lactivit peut avoir t supprime alors que le travail en arrire-plan se poursuit.
Aprs avoir lanc lactivit, lutilisateur peut avoir reu un appel tlphonique, suivi
dun SMS ; il a ventuellement ensuite effectu une recherche dans la liste de ses
contacts. Toutes ces oprations peuvent sufre supprimer votre activit de la
mmoire. Le prochain chapitre prsentera les diffrents vnements par lesquels passe
une activit ; vous devez grer les bons et vous assurer de mettre correctement n au
thread en arrire-plan lorsque vous en avez loccasion.
Un utilisateur risque dtre assez mcontent si vous consommez beaucoup de
temps CPU et dautonomie de la batterie sans rien lui donner en retour. Tactiquement,
ceci signie que vous avez tout intrt utiliser une ProgressBar ou tout autre moyen
de faire savoir lutilisateur quil se passe vraiment quelque chose. Stratgiquement,
cela implique que vous devez tre efcace les threads en arrire-plan ne peuvent pas
servir dexcuse un code inutile et lent.
Une erreur peut se produire au cours de lexcution du traitement en arrire-plan
pendant que vous rcuprez des informations partir dInternet, le terminal peut
perdre sa connectivit, par exemple. La meilleure solution consiste alors prvenir
lutilisateur du problme via une Notification, puis mettre n au thread en arrire-
plan.


16
Gestion des vnements
du cycle de vie
dune activit
Bien que cela puisse ressembler un disque ray, noubliez pas que les terminaux Android
sont, avant tout, des tlphones. Pour cette raison, certaines activits sont plus importantes
que dautres pour un utilisateur, pouvoir recevoir un appel est srement plus important
que faire un sudoku. En outre, un tlphone possde gnralement moins de mmoire
quun ordinateur de bureau ou quun ordinateur portable.
Par consquent, votre activit risque de se faire tuer parce que dautres activits ont lieu et
que le systme a besoin de la mmoire quelle occupe. Il faut considrer tout cela comme
lquivalent Android du "cercle de la vie" votre activit meurt an que dautres puissent
vivre, etc. Vous ne pouvez donc pas supposer que votre application sexcutera jusqu son
terme, que ce soit de votre point de vue ou de celui de lutilisateur.
Cest lun des exemples peut-tre le plus important de limpact du cycle de vie dune
activit sur le droulement de vos propres applications. Dans ce chapitre, nous prsente-
rons les diffrents tats qui composent le cycle de vie dune activit et la faon de les grer
correctement.


Lactivit de Schroedinger
En gnral, une activit se trouve toujours dans lun des quatre tats suivants :
Active. Lactivit a t lance par lutilisateur, elle sexcute au premier plan. Cest
cet tat que lon pense quand on voque le fonctionnement dune activit.
En pause. Lactivit a t lance par lutilisateur, elle sexcute et elle est visible, mais
une notication ou un autre vnement occupe une partie de lcran. Pendant ce temps,
lutilisateur voit lactivit mais peut ne pas tre capable dinteragir avec elle.
Lorsquun appel tlphonique est reu, lutilisateur a lopportunit de prendre cet
appel ou de lignorer, par exemple.
Stoppe. Lactivit a t lance par lutilisateur, elle sexcute mais est cache par
dautres activits qui ont t lances ou vers lesquelles le systme a bascul. Votre
application ne pourra rien prsenter dintressant lutilisateur directement : elle ne
peut passer que par une Notification.
Morte. Lactivit na jamais t lance (le tlphone vient dtre rinitialis, par exemple)
ou elle a t tue, probablement cause dun manque de mmoire.
Vie et mort dune activit
Android fera appel votre activit en considrant les transitions entre les quatre tats que
nous venons de prsenter. Certaines transitions peuvent provoquer plusieurs appels votre
activit, via les mthodes prsentes dans cette section ; parfois, Android tuera votre appli-
cation sans lappeler. Tout ceci est assez ou et sujet modications : cest la raison pour
laquelle vous devez consulter attentivement la documentation ofcielle dAndroid en plus
de cette section pour dcider des vnements qui mritent attention et de ceux que vous
pouvez ignorer.
Notez que vous devez appeler les versions de la superclasse lorsque vous implmentez les
mthodes dcrites ici ; sinon Android peut lever une exception.
onCreate() et onDestroy()
Tous les exemples que nous avons vus jusqu maintenant ont implment onCreate()
dans leurs sous-classes dActivity.
Cette mthode sera appele dans les trois cas suivants :
Lorsque lactivit est lance pour la premire fois (aprs le redmarrage du systme,
par exemple), onCreate() est appele avec le paramtre null.
Si lactivit sest excute, puis quelle a t tue, onCreate() sera appele avec, pour
paramtre, le Bundle obtenu par onSaveInstanceState() (voir plus loin).


Si lactivit sest excute et que vous layez congure pour quelle utilise des ressources
diffrentes en fonction des tats du terminal (mode portrait ou mode paysage, par
exemple), elle sera recre et onCreate() sera donc appele.
Cest dans cette mthode que vous congurez linterface utilisateur et tout ce qui ne doit
tre fait quune seule fois, quelle que soit lutilisation de lactivit.
lautre extrmit du cycle de vie, onDestroy() peut tre appele lorsque lactivit prend
n, soit parce quelle a appel finish() (qui "nit" lactivit), soit parce quAndroid a
besoin de mmoire et la ferme prmaturment. onDestroy() peut ne pas tre appele si
ce besoin de mmoire est urgent (la rception dun appel tlphonique, par exemple) et
que lactivit se terminera quoi quil en soit. Par consquent, onDestroy() est essen-
tiellement destine librer les ressources obtenues dans onCreate().
onStart(), onRestart() et onStop()
Une activit peut tre place au premier plan, soit parce quelle vient dtre lance, soit
parce quelle y a t mise aprs avoir t cache (par une autre activit ou la rception dun
appel, par exemple).
Dans ces deux cas, cest la mthode onStart() qui est appele. onRestart() nest invoque
que lorsque lactivit a t stoppe et redmarre.
Inversement, onStop() est appele lorsque lactivit va tre stoppe.
onPause() et onResume()
La mthode onResume() est appele immdiatement avant que lactivit ne passe au
premier plan, soit parce quelle vient dtre lance, soit parce quelle est repartie aprs
avoir t stoppe, soit aprs la fermeture dune bote de dialogue (ouverte par la rception
dun appel, par exemple). Cest donc un bon endroit pour reconstruire linterface en fonc-
tion de ce qui sest pass depuis que lutilisateur la vue pour la dernire fois. Si votre acti-
vit interroge un service pour savoir sil y a de nouvelles informations (de nouvelles
entres dans un ux RSS, par exemple), onResume() est le bon moyen de rafrachir la vue
courante et, si ncessaire, de lancer un thread en arrire-plan (via un Handler, par exemple)
pour modier cette vue.
Inversement, tout ce qui dtourne lutilisateur de votre activit essentiellement lactiva-
tion dune autre activit provoquera lappel donPause(). Vous pouvez proter de cette
mthode pour annuler tout ce que vous aviez fait dans onResume() : arrter les threads en
arrire-plan, librer les ressources en accs exclusif que vous auriez pu prendre (lappareil
photo, par exemple), etc.
Lorsque onPause() a t appele, Android se rserve le droit de tuer tout moment le
processus de lactivit. Par consquent, vous ne devriez pas supposer que vous pourrez
recevoir dautre vnement de la part de celle-ci.


Ltat de grce
Pour lessentiel, les mthodes que nous venons de mentionner interviennent au niveau
gnral de lapplication (onCreate() relie les dernires parties de linterface, onPause()
ferme les threads en arrire-plan, etc.).
Cependant, Android a pour but de fournir une apparence de simplicit de fonctionnement.
Autrement dit, bien que les activits puissent aller et venir en fonction des besoins
mmoire, les utilisateurs ne devraient pas savoir ce quil se trame. Un utilisateur qui utili-
sait la calculette devrait donc retrouver le ou les nombres sur lesquels il travaillait lorsquil
la rutilise aprs une absence sauf sil avait lui-mme ferm la calculette.
Pour que tout cela fonctionne, les activits doivent donc pouvoir sauvegarder, rapidement
et efcacement, ltat de linstance de lapplication quelles excutent. En outre, comme
elles peuvent tre tues tout moment, les activits peuvent devoir sauvegarder cet tat
plus frquemment quon ne pourrait le supposer. Rciproquement, une activit qui red-
marre doit rcuprer son tat antrieur an dapparatre dans la situation o elle se trouvait
prcdemment.
La sauvegarde de ltat dune instance est gre par la mthode onSaveInstanceState(),
qui fournit un objet Bundle dans lequel lactivit peut placer les donnes quelle souhaite
(le nombre afch par la calculette, par exemple). Limplmentation de cette mthode doit
tre rapide nessayez pas den faire trop : placez simplement les donnes dans le Bundle
et quittez la mthode.
Vous pouvez rcuprer ltat de linstance dans les mthodes onCreate() et onRestore
InstanceState() : cest vous qui dcidez du moment dappliquer cet tat votre activit
lune ou lautre de ces mthodes de rappel convient.


Partie III
Stockage de donnes,
services rseaux et API
CHAPITRE 17. Utilisation des prfrences
CHAPITRE 18. Accs aux chiers
CHAPITRE 19. Utilisation des ressources
CHAPITRE 20. Accs et gestion des bases de donnes locales
CHAPITRE 21. Tirer le meilleur parti des bibliothques Java
CHAPITRE 22. Communiquer via Internet


17
Utilisation des prfrences
Android offre plusieurs moyens de stocker les donnes dont a besoin une activit. Le plus
simple consiste utiliser le systme des prfrences.
Les activits et les applications peuvent sauvegarder leurs prfrences sous la forme de
paires cls/valeurs qui persisteront entre deux appels. Comme leur nom lindique, le but
principal de ces prfrences est de permettre la mmorisation dune conguration choisie
par lutilisateur le dernier ux consult par le lecteur RSS, lordre de tri par dfaut des
listes, etc. Vous pouvez, bien sr, les stocker comme vous le souhaitez du moment que les
cls sont des chanes (String) et les valeurs, des types primitifs (boolean, String, etc.).
Les prfrences peuvent tre propres une activit ou partages par toutes les activits
dune application. Elles peuvent mme tre partages par les applications, bien que cela ne
soit pas encore possible avec les versions actuelles dAndroid.
Obtenir ce que vous voulez
Pour accder aux prfrences, vous pouvez :
appeler la mthode getPreferences() partir de votre activit pour accder ses
prfrences spciques ;
appeler la mthode getSharedPreferences() partir de votre activit (ou dun autre
Context de lapplication) pour accder aux prfrences de lapplication ;


appeler la mthode getDefaultSharedPreferences() dun objet Preferences
Manager pour accder aux prfrences partages qui fonctionnent de concert avec le
systme des prfrences globales dAndroid.
Les deux premiers appels prennent un mode de scurit en paramtre pour le moment,
utilisez la valeur 0. getSharedPreferences() attend galement le nom dun ensemble de
prfrences : en ralit, getPreferences() appelle getSharedPreferences() en lui passant
le nom de classe de votre activit en paramtre. getDefaultSharedPreferences() prend
en paramtre le Context des prfrences (cest--dire votre Activity).
Toutes ces mthodes renvoient une instance de SharedPreferences qui fournit un ensem-
ble de mthodes daccs aux prfrences par leurs noms. Ces mthodes renvoient un rsul-
tat du type adquat (getBoolean(), par exemple, renvoie une prfrence boolenne).
Elles attendent galement une valeur par dfaut, qui sera renvoye sil nexiste pas de
prfrences ayant la cl indique.
Dnir vos prfrences
partir dun objet SharedPreferences, vous pouvez appeler edit() pour obtenir un
"diteur" pour les prfrences. Cet objet dispose dun ensemble de mthodes modicatrices
limage des mthodes daccs de lobjet parent SharedPreferences. Il fournit galement
les mthodes suivantes :
remove() pour supprimer une prfrence par son nom ;
clear() pour supprimer toutes les prfrences ;
commit() pour valider les modications effectues via lditeur.
La dernire est importante : si vous modiez les prfrences avec lditeur et que vous nappe-
liez pas commit(), les modications disparatront lorsque lditeur sera hors de porte.
Inversement, comme les prfrences acceptent des modications en direct, si lune des
parties de votre application (une activit, par exemple) modie des prfrences partages,
les autres parties (tel un service) auront immdiatement accs la nouvelle valeur.
Un mot sur le framework
partir de sa version 0.9, Android dispose dun framework de gestion des prfrences qui
ne change rien ce que nous venons de dcrire. En fait, il existe surtout pour prsenter un
ensemble cohrent de prfrences aux utilisateurs, an que les applications naient pas
rinventer la roue.
Llment central de ce framework est encore une structure de donnes XML. Vous
pouvez dcrire les prfrences de votre application dans un chier XML stock dans le
rpertoire res/xml/ du projet. partir de ce chier, Android peut prsenter une interface
graphique pour manipuler ces prfrences, qui seront ensuite stockes dans lobjet Shared
Preferences obtenu par getDefaultSharedPreferences().


Voici, par exemple, le contenu dun chier XML des prfrences, extrait du projet Prefs/
Simple :
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="@string/checkbox"
android:title="Preference case a cocher"
android:summary="Cochez ou decochez" />
<RingtonePreference
android:key="@string/ringtone"
android:title="Preference sonnerie"
android:showDefault="true"
android:showSilent="true"
android:summary="Choisissez une sonnerie" />
</PreferenceScreen>
La racine de ce document XML est un lment PreferenceScreen (nous expliquerons
plus loin pourquoi il sappelle ainsi). Comme le montre ce chier, PreferenceScreen
peut contenir des dnitions de prfrences des sous-classes de Preference, comme
CheckBoxPreference ou RingtonePreference. Comme lon pourrait sy attendre, elles
permettent, respectivement, de cocher une case et de choisir une sonnerie. Dans le cas de
RingtonePreference, vous pouvez autoriser les utilisateurs choisir la sonnerie par
dfaut du systme ou "silence".
Laisser les utilisateurs choisir
Lorsque vous avez mis en place le chier XML des prfrences, vous pouvez utiliser une
activit "quasi intgre" pour permettre aux utilisateurs de faire leur choix. Cette activit
est "quasi intgre" car il suft den crer une sous-classe, de la faire pointer vers ce
chier et de la lier au reste de votre application.
Voici, par exemple, lactivit EditPreferences du projet Prefs/Simple :
package com.commonsware.android.prefs;
import android.app.Activity;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class EditPreferences extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

addPreferencesFromResource(R.xml.preferences);
}
}


Comme vous pouvez le constater, il ny a pas grand-chose lire car il suft dappeler
addPreferencesFromResource() en lui indiquant la ressource XML contenant les prf-
rences.
Vous devrez galement ajouter cette activit votre chier AndroidManifest.xml :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.prefs">
<application android:label="@string/app_name">
<activity
android:name=".SimplePrefsDemo"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".EditPreferences"
android:label="@string/app_name">
</activity>
</application>
</manifest>
Voici le code de SimplePrefsDemo, qui permet dappeler cette activit partir dun menu
doptions :
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, EDIT_ID, Menu.NONE, "Modifier Prefs")
.setIcon(R.drawable.misc)
.setAlphabeticShortcut(m);
menu.add(Menu.NONE, CLOSE_ID, Menu.NONE, "Fermeture")
.setIcon(R.drawable.eject)
.setAlphabeticShortcut(f);
return(super.onCreateOptionsMenu(menu));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case EDIT_ID:
startActivity(new Intent(this, EditPreferences.class));
return(true);
case CLOSE_ID:
finish();
return(true);
}
return(super.onOptionsItemSelected(item));
}


La Figure 17.1 montre linterface de conguration des prfrences de cette application.
La case cocher peut tre coche ou dcoche directement. Pour modier la sonnerie, il
suft de cliquer sur son entre dans les prfrences pour voir apparatre une bote de dialogue
comme celle de la Figure 17.2.
Vous remarquerez quil nexiste pas de bouton "Save" ou "Commit" : les modications
sont sauvegardes ds quelles sont faites.
Figure 17.1
Linterface des prf-
rences de Simple
PrefsDemo.
Figure 17.2
Choix dune sonnerie
partir des prfrences.


Outre ce menu, lapplication SimplePrefsDemo afche galement les prfrences courantes
via un TableLayout :
<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TableRow>
<TextView
android:text="Case a cocher:"
android:paddingRight="5px"
/>
<TextView android:id="@+id/checkbox"
/>
</TableRow>
<TableRow>
<TextView
android:text="Sonnerie:"
android:paddingRight="5px"
/>
<TextView android:id="@+id/ringtone"
/>
</TableRow>
</TableLayout>
Les champs de cette table se trouvent dans la mthode onCreate() :
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
checkbox=(TextView)findViewById(R.id.checkbox);
ringtone=(TextView)findViewById(R.id.ringtone);
}
Ils sont mis jour chaque appel donResume() :
@Override
public void onResume() {
super.onResume();
SharedPreferences prefs=PreferenceManager
.getDefaultSharedPreferences(this);
checkbox.setText(new Boolean(prefs
.getBoolean("checkbox", false))
.toString());
ringtone.setText(prefs.getString("ringtone", "<unset>"));
}
Ceci signie que les champs seront modis louverture de lactivit et aprs la n de
lactivit des prfrences (via le bouton "back", par exemple).


La Figure 17.3 montre le contenu de cette table aprs le choix de lutilisateur.
Ajouter un peu de structure
Si vous devez proposer un grand nombre de prfrences congurer, les mettre toutes
dans une seule longue liste risque dtre peu pratique. Le framework dAndroid permet
donc de structurer un ensemble de prfrences en catgories ou en crans.
Les catgories sont cres laide dlments PreferenceCategory dans le chier XML des
prfrences et permettent de regrouper des prfrences apparentes. Au lieu quelles soient
toutes des lles de la racine PreferenceScreen, vous pouvez placer vos prfrences dans
les catgories appropries en plaant des lments PreferenceCategory sous la racine.
Visuellement, ces groupes seront spars par une barre contenant le titre de la catgorie.
Si vous avez un trs grand nombre de prfrences trop pour quil soit envisageable que
lutilisateur les fasse dler , vous pouvez galement les placer dans des "crans"
distincts laide de llment PreferenceScreen (le mme que llment racine).
Tout ls de PreferenceScreen est plac dans son propre cran. Si vous imbriquez des
PreferenceScreen, lcran pre afchera lcran ls sous la forme dune entre : en la
touchant, vous ferez apparatre le contenu de lcran ls correspondant.
Le chier XML des prfrences du projet Prefs/Structured contient la fois des
lments PreferenceCategory et des lments PreferenceScreen imbriqus :
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Preferences simples">
Figure 17.3
Liste des prfrences de
SimplePrefsDemo.


<CheckBoxPreference
android:key="@string/checkbox"
android:title="Preference case a cocher"
android:summary="Cochez ou decochez"
/>
<RingtonePreference
android:key="@string/ringtone"
android:title="Preference sonnerie"
android:showDefault="true"
android:showSilent="true"
android:summary="Choisissez une sonnerie"
/>
</PreferenceCategory>
<PreferenceCategory android:title="Ecrans">
<PreferenceScreen
android:key="detail"
android:title="Ecran"
android:summary="Preferences supplementaires dans une autre page">
<CheckBoxPreference
android:key="@string/checkbox2"
android:title="Une autre case"
android:summary="On ou Off. Peu importe."
/>
</PreferenceScreen>
</PreferenceCategory>
</PreferenceScreen>
Utilis avec notre implmentation de PreferenceActivity, ce chier XML produit une
liste dlments comme ceux de la Figure 17.4, regroups en catgories.
Figure 17.4
Linterface des prf-
rences de Structured
PrefsDemo, montrant les
catgories et un marqueur
dcran.


En touchant lentre du sous-cran, celui-ci safche et montre les prfrences quil
contient (voir Figure 17.5).
Botes de dialogue
Toutes les prfrences ne sont pas, bien sr, que des cases cocher et des sonneries.
Pour les autres, comme les champs de saisie et les listes, Android utilise des botes de
dialogue. Les utilisateurs nentrent plus directement leurs prfrences dans lactivit inter-
face des prfrences mais touchent une prfrence, remplissent une valeur et cliquent sur
OK pour valider leur modication.
Comme le montre ce chier XML, extrait du projet Prefs/Dialogs, les champs et les
listes nont pas une structure bien diffrente de celle des autres types :
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Simple Preferences">
<CheckBoxPreference
android:key="@string/checkbox"
android:title="Preference case a cocher"
android:summary="Cochez ou decochez"
/>
<RingtonePreference
android:key="@string/ringtone"
android:title="Preference sonnerie"
android:showDefault="true"
android:showSilent="true"
Figure 17.5
Le sous-cran de prf-
rences de Structured
PrefsDemo.


android:summary="Choisissez une sonnerie"
/>
</PreferenceCategory>
<PreferenceCategory android:title="Ecrans">
<PreferenceScreen
android:key="detail"
android:title="Ecran"
android:summary="Preferences supplementaires dans une autre page">
<CheckBoxPreference
android:key="@string/checkbox2"
android:title="Autre case a cocher"
android:summary="On ou Off. Peu importe."
/>
</PreferenceScreen>
</PreferenceCategory>
<PreferenceCategory android:title="Preferences simples">
<EditTextPreference
android:key="@string/text"
android:title="Dialogue de saisie dun texte"
android:summary="Cliquez pour ouvrir un champ de saisie"
android:dialogTitle="Entrez un texte interessant"
/>
<ListPreference
android:key="@string/list"
android:title="Dialogue de choix"
android:summary="Cliquez pour ouvrir une liste de choix"
android:entries="@array/villes"
android:entryValues="@array/codes_aeroports"
android:dialogTitle="Choisissez une ville" />
</PreferenceCategory>
</PreferenceScreen>
Pour le champ de texte (EditTextPreference), outre le titre et le rsum de la prfrence
elle-mme, nous donnons galement un titre la bote de dialogue.
Pour la liste (ListPreference), on fournit la fois un titre au dialogue et deux ressources
de type tableau de chanes : lun pour les noms afchs, lautre pour les valeurs correspon-
dantes qui doivent tre dans le mme ordre lindice du nom afch dtermine la valeur
stocke dans lobjet SharedPreferences. Voici par exemple les tableaux utiliss pour
cette liste :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="villes">
<item>Philadelphia</item>
<item>Pittsburgh</item>
<item>Allentown/Bethlehem</item>
<item>Erie</item>
<item>Reading</item>


<item>Scranton</item>
<item>Lancaster</item>
<item>Altoona</item>
<item>Harrisburg</item>
</string-array>
<string-array name="codes_aeroports">
<item>PHL</item>
<item>PIT</item>
<item>ABE</item>
<item>ERI</item>
<item>RDG</item>
<item>AVP</item>
<item>LNS</item>
<item>AOO</item>
<item>MDT</item>
</string-array>
</resources>
Comme le montre la Figure 17.6, les prfrences comprennent dsormais une catgorie
supplmentaire contenant deux nouvelles entres.
Toucher lentre "Dialogue de saisie dun texte" provoque lafchage dun... dialogue de
saisie dun texte ici, il est prrempli avec la valeur courante de la prfrence (voir
Figure 17.7).
Toucher lentre "Dialogue de choix" afche... une liste de choix sous forme de bote de
dialogue prsentant les noms des villes (voir Figure 17.8).
Figure 17.6
Lcran des prfrences
de DialogsDemo.


Figure 17.7
Modication dune prf-
rence avec un champ de
saisie.
Figure 17.8
Modication dune prf-
rence avec une liste.


18
Accs aux chiers
Bien quAndroid dispose de moyens de stockage structurs via les prfrences et les bases
de donnes, un simple chier suft parfois. Android offre donc deux modles daccs aux
chiers : lun pour les chiers fournis dans le paquetage de lapplication, un autre pour
ceux qui sont crs sur le terminal par lapplication.
Allons-y !
Supposons que vous vouliez fournir des donnes statiques avec une application : une liste
de mots pour un correcteur orthographique, par exemple. Le moyen le plus simple dy
parvenir consiste placer ces donnes dans un chier situ dans le rpertoire res/raw:
elles seront alors intgres au chier APK de lapplication comme une ressource brute au
cours du processus dempaquetage.
Pour accder ce chier, vous avez besoin dun objet Resources que vous pouvez obtenir
partir de lactivit en appelant getResources(). Cet objet fournit la mthode open
RawResource() pour rcuprer un InputStream sur le chier spci par son identiant
(un entier). Cela fonctionne exactement comme laccs aux widgets avec find
ViewById() : si vous placez un chier mots.xml dans res/raw, son identiant dans le
code Java sera R.raw.mots.


Comme vous ne pouvez obtenir quun InputStream, vous navez aucun moyen de modi-
er ce chier : cette approche nest donc vraiment utile que pour lire des donnes stati-
ques. En outre, comme elles ne changeront pas jusqu ce que lutilisateur installe une
nouvelle version de votre paquetage, ces donnes doivent tre valides pour une certaine
priode ou vous devrez fournir un moyen de les mettre jour. Le moyen le plus simple de
rgler ce problme consiste utiliser ces donnes pour construire une autre forme de stoc-
kage qui, elle, sera modiable (une base de donnes, par exemple), mais cela impose
davoir deux copies des donnes. Une autre solution consiste les conserver telles quelles
et placer les modications dans un autre chier ou dans une base de donnes, puis les
fusionner lorsque vous avez besoin dune vue complte de ces informations. Si votre appli-
cation fournit une liste dURL, par exemple, vous pourriez grer un deuxime chier pour
stocker les URL ajoutes par lutilisateur ou pour rfrencer celles quil a supprimes.
Le projet Files/Static reprend lexemple ListViewDemo du Chapitre 8 en utilisant cette
fois-ci un chier XML la place dun tableau dni directement dans le programme.
Le chier de description XML est identique dans les deux cas :
<?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" >
<TextView
android:id="@+id/selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="false"
/>
</LinearLayout>
On a galement besoin dun autre chier XML contenant les mots de la liste :
<words>
<word value="lorem" />
<word value="ipsum" />
<word value="dolor" />
<word value="sit" />
<word value="amet" />
<word value="consectetuer" />
<word value="adipiscing" />
<word value="elit" />
<word value="morbi" />
<word value="vel" />


<word value="ligula" />
<word value="vitae" />
<word value="arcu" />
<word value="aliquet" />
<word value="mollis" />
<word value="etiam" />
<word value="vel" />
<word value="erat" />
<word value="placerat" />
<word value="ante" />
<word value="porttitor" />
<word value="sodales" />
<word value="pellentesque" />
<word value="augue" />
<word value="purus" />
</words>
Bien que cette structure XML ne soit pas exactement un modle de concision, elle sufra
pour notre dmonstration.
Le code Java doit maintenant lire ce chier, en extraire les mots et les placer quelque part
pour que lon puisse remplir la liste :
public class StaticFileDemo extends ListActivity {
TextView selection;
ArrayList<String> items=new ArrayList<String>();

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
selection=(TextView)findViewById(R.id.selection);

try {
InputStream in=getResources().openRawResource(R.raw.mots);
DocumentBuilder builder=DocumentBuilderFactory
.newInstance()
.newDocumentBuilder();
Document doc=builder.parse(in, null);
NodeList words=doc.getElementsByTagName("word");

for (int i=0;i<words.getLength();i++) {
items.add(((Element)words.item(i)).getAttribute("value"));
}

in.close();
}


catch (Throwable t) {
Toast
.makeText(this, "Exception : " + t.toString(), 2000)
.show();
}

setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
items));
}

public void onListItemClick(ListView parent, View v, int position,
long id) {
selection.setText(items.get(position).toString());
}
}
Les diffrences entre cet exemple et celui du Chapitre 8 se situent essentiellement dans le
corps de la mthode onCreate(). Ici, on obtient un InputStream pour le chier XML en
appelant getResources().openRawResource(R.raw.mots), puis on se sert des fonc-
tionnalits danalyse XML prdnies pour transformer le chier en document DOM, en
extraire les lments word et placer les valeurs de leurs attributs value dans un objet
ArrayList qui sera utilis par lArrayAdapter.
Comme le montre la Figure 18.1, le rsultat de lactivit est identique celui lexemple du
Chapitre 8 car la liste de mots est la mme.
Figure 18.1
Lapplication
StaticFileDemo.


Comme nous le verrons au chapitre suivant, il existe videmment des moyens encore plus
simples dutiliser les chiers XML contenus dans le paquetage dune application : utiliser
une ressource XML, par exemple. Cependant, bien que cet exemple utilise XML, le chier
aurait pu simplement contenir un mot par ligne ou utiliser un format non reconnu nativement
par le systme de ressources dAndroid.
Lire et crire
Lire et crire ses propres chiers de donnes spciques est quasiment identique ce que
lon pourrait faire avec une application Java traditionnelle. La solution consiste utiliser
les mthodes openFileInput() et openFileOutput() sur lactivit ou tout autre
Context an dobtenir, respectivement, un InputStream et un OutputStream. partir de
l, le processus nest pas beaucoup diffrent de celui des E/S Java classiques :
On enveloppe ces ux selon les besoins, par exemple avec un InputStreamReader ou
un OutputStreamWriter si lon souhaite effectuer des E/S en mode texte.
On lit ou on crit les donnes.
On libre le ux avec close() lorsquon a termin.
Deux applications qui essaient de lire en mme temps un chier notes.txt via open
FileInput() accderont chacune leur propre dition du chier. Si vous voulez quun
mme chier soit accessible partir de plusieurs endroits, vous devrez srement crer un
fournisseur de contenu, comme on lexplique au Chapitre 28. Notez galement quopen
FileInput() et openFileOutput() ne prennent pas en paramtre des chemins daccs
(chemin/vers/fichier.txt, par exemple), mais uniquement des noms de chiers.
Le code suivant, extrait du projet Files/ReadWrite, montre la disposition de lditeur de
texte le plus simple du monde :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<Button android:id="@+id/close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fermer" />
<EditText
android:id="@+id/editor"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:singleLine="false"
/>
</LinearLayout>


Les deux seuls lments de linterface sont un gros widget ddition de texte et un bouton
"Fermer" plac en dessous. Le code Java est peine plus compliqu :
package com.commonsware.android.files;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
public class ReadWriteFileDemo extends Activity {
private final static String NOTES="notes.txt";
private EditText editor;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
editor=(EditText)findViewById(R.id.editor);
Button btn=(Button)findViewById(R.id.close);

btn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
finish();
}
});
}

public void onResume() {
super.onResume();

try {
InputStream in=openFileInput(NOTES);

if (in!=null) {
InputStreamReader tmp=new InputStreamReader(in);
BufferedReader reader=new BufferedReader(tmp);
String str;
StringBuffer buf=new StringBuffer();

while ((str = reader.readLine())!= null) {
buf.append(str + "\n");
}



in.close();
editor.setText(buf.toString());
}
}
catch (java.io.FileNotFoundException e) {
// Ok, nous ne lavons probablement pas encore cr
}
catch (Throwable t) {
Toast
.makeText(this, "Exception : "+ t.toString(), 2000)
.show();
}
}

public void onPause() {
super.onPause();

try {
OutputStreamWriter out=
new OutputStreamWriter(openFileOutput(NOTES, 0));

out.write(editor.getText().toString());
out.close();
}
catch (Throwable t) {
Toast
.makeText(this, "Exception : "+ t.toString(), 2000)
.show();
}
}
}
Nous commenons par lier le bouton la fermeture de notre activit, en invoquant
finish() sur lactivit partir de setOnClickListener() lorsque nous cliquons sur le
bouton.
Puis nous nous servons donResume() pour prendre le contrle lorsque notre diteur
devient actif (aprs son lancement) ou le redevient (aprs avoir t g). Nous lisons le
chier notes.txt laide dopenFileInput() et nous y plaons son contenu dans
lditeur de texte. Si le chier na pas t trouv, nous supposons que cest parce que cest
la premire fois que lactivit sexcute (ou que le chier a t supprim par dautres
moyens) et nous nous contentons de laisser lditeur vide.
Enn, nous nous servons donPause() pour prendre le contrle lorsque notre activit a t
cache par une autre ou quelle a t ferme (par notre bouton "Fermer", par exemple).
Nous ouvrons alors le chier notes.txt laide dopenFileOutput() et nous y plaons
le contenu de lditeur de texte.


Le rsultat est un bloc-notes persistant : tout ce qui est tap le restera jusqu sa suppres-
sion ; le texte survivra la fermeture de lactivit, lextinction du tlphone et aux autres
situations similaires.


19
Utilisation des ressources
Les ressources sont des informations statiques, stockes en dehors du code Java. Dans les
exemples de ce livre, vous avez dj souvent rencontr un type de ressource les chiers
de description (layouts) , mais il en existe de nombreux autres dont vos applications
peuvent tirer prot : les images et les chanes de caractres, par exemple.
Les diffrents types de ressources
Les ressources dun projet Android sont stockes dans des chiers situs sous le rpertoire
res/ de larborescence. lexception des ressources brutes (res/raw/), tous les types de
ressources sont analyss automatiquement, soit par le systme de paquetages dAndroid,
soit par le systme du terminal ou de lmulateur. Si vous dcrivez, par exemple, linter-
face utilisateur dune activit via une ressource de type layout (dans res/layout), vous
navez pas besoin danalyser vous-mme le contenu du chier XML Android sen
chargera pour vous.
Outre les layouts (que nous avons rencontrs pour la premire fois au Chapitre 5) et les
ressources brutes (introduites au Chapitre 18), il existe plusieurs autres types de ressources :
Les animations (res/anim/) sont destines aux animations courtes qui font partie de
linterface utilisateur : la simulation dune page qui se tourne quand on clique sur un
bouton, par exemple.


Les images (res/drawable) permettent de placer des icnes statiques ou dautres
images dans une interface utilisateur.
Les chanes, les couleurs, les tableaux et les dimensions (res/values/) permettent
dassocier des noms symboliques ces types de constantes et de les sparer du reste du
code (pour linternationalisation et la localisation, notamment).
Les chiers XML statiques (res/xml/) permettent de stocker vos propres donnes et
structures.
Thorie des chanes
Placer les labels et les autres textes lextrieur du code source de lapplication est, gn-
ralement, une trs bonne ide. Ceci facilite, notamment, linternationalisation (I18N) et la
localisation (L10N), qui sont prsentes un peu plus loin dans ce chapitre. Mme si vous
ne comptez pas traduire vos textes dans dautres langues, cette sparation facilite les
corrections car toutes les chanes sont au mme endroit au lieu dtre dissmines dans le
code source.
Android permet dutiliser des chanes externes classiques, mais galement des "formats de
chanes", contenant des emplacements qui seront remplacs par des informations au cours
de lexcution. En outre, le formatage de texte simple, appel "texte styl", est galement
disponible, ce qui permet de mlanger des mots en gras ou en italique avec du texte
normal.
Chanes normales
En rgle gnrale, vous navez besoin pour les chanes normales que dun chier XML
situ dans le rpertoire res/values (le plus souvent res/values/strings.xml). La
racine de ce document est llment ressources, qui a autant de ls string quil y a de
chanes encoder comme ressource. Llment string a un attribut name, contenant le
nom unique de la chane. Le contenu de cet lment est le texte de la chane :
<resources>
<string name="whisky">Portez ce vieux whisky...</string>
<string name="zephir">Le vif zphir jubile...</string>
</resources>
Le seul point pineux concerne la prsence de guillemets (") ou dapostrophes () dans le
texte de la chane car vous devrez alors les protger en les prxant dun antislash (il
fait beau aujourd\hui, par exemple). Dans le cas de lapostrophe, vous pouvez galement
placer tout le texte entre guillemets ("il fait beau aujourdhui").
Vous pouvez ensuite faire rfrence cette chane depuis un chier de description (sous la
forme @string/whisky, ou @string/zephir, par exemple) ou y accder depuis votre


code Java laide de getString(), en lui passant lidentiant de ressource de la chane,
cest--dire son nom unique prx par R.string (comme getString(R.string.whisky)).
Formats de chanes
Comme les autres implmentations du langage Java, la machine virtuelle Dalvik
dAndroid reconnat les formats de chanes. Ces formats sont des chanes contenant des
marqueurs demplacements et seront remplacs lors de lexcution par des donnes varia-
bles (Mon nom est %1$s, par exemple). Les chanes normales stockes sous forme de
ressources peuvent tre utilises comme des formats de chanes :
String strFormat=getString(R.string.mon_nom);
String strResult=String.format(strFormat, "Tim");
((TextView)findViewById(R.id.un_label))
.setText(strResult);
Texte styl
Pour enrichir du texte, vous pourriez utiliser des ressources brutes contenant du HTML,
puis les placer dans un widget WebKit. Cependant, pour un formatage lger utilisant <b>,
<i> et <u>, une ressource chane fera laffaire :
<resources>
<string name="b">Ce texte est en <b>gras</b>.</string>
<string name="i">Alors que celui-ci est en <i>italiques</i> !</string>
</resources>
Vous pouvez ensuite y accder comme nimporte quelle autre chane normale, sauf que
le rsultat de lappel getString() sera ici un objet implmentant linterface
android.text.Spanned :
((TextView)findViewById(R.id.autre_label))
.setText(getString(R.string.i));
Formats styls
Les styles deviennent compliqus grer lorsquil sagit de les utiliser avec les formats de
chanes. En effet, String.format() sapplique des objets String, pas des objets
Spanned disposant dinstructions de formatage. Si vous avez vraiment besoin de formats
de chanes styls, vous pouvez suivre ces tapes :
1. Dans la ressource chane, remplacez les chevrons par des entits HTML (Je suis
&lt;b&gt;%1$s&lt;/b&gt;, par exemple).
2. Rcuprez la ressource comme dhabitude, bien quelle ne soit pas encore style (avec
getString(R.string.format style)).


3. Produisez le rsultat du formatage, en vous assurant de protger les valeurs de chanes
que vous substituez, au cas o elles contiendraient des chevrons ou des esperluettes :
String.format(getString(R.string.format_style),
TextUtils.htmlEncode(nom));
4. Convertissez le HTML encod en objet Spanned grce Html.fromHtml().
uneTextView.setText(Html.fromHtml(resultatDeStringFormat));
Pour voir tout ceci en action, examinons le chier de description du projet Resources/
Strings :
<?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"
>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<Button android:id="@+id/format"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_name"
/>
<EditText android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
<TextView android:id="@+id/result"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Comme vous pouvez le constater, linterface utilisateur nest compose que dun bouton,
dun champ et dun label. Le but est que lutilisateur entre son nom dans le champ puis clique
sur le bouton pour que le label soit remplac par un message format contenant ce nom.
Llment Button de ce chier faisant rfrence une ressource chane (@string/
btn name), nous avons besoin dun chier de ressource chane (res/values/strings.xml) :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">StringsDemo</string>
<string name="btn_name">Nom :</string>
<string name="funky_format">Je suis &lt;b&gt;%1$s&lt;/b&gt;</string>
</resources>


La ressource app name est automatiquement cre par le script activityCreator. La
chane btn name est le titre du bouton, tandis que le format de chane styl se trouve dans
funky_format.
Enn, nous avons besoin dun peu de Java pour relier tous les morceaux :
package com.commonsware.android.resources;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.Html;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class StringsDemo extends Activity {
EditText name;
TextView result;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

name=(EditText)findViewById(R.id.name);
result=(TextView)findViewById(R.id.result);

Button btn=(Button)findViewById(R.id.format);

btn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
applyFormat();
}
});
}

private void applyFormat() {
String format=getString(R.string.funky_format);
String simpleResult=String.format(format,
TextUtils.htmlEncode(name.getText().toString()));
result.setText(Html.fromHtml(simpleResult));
}
}
La manipulation de la ressource chane a lieu dans applyFormat(), qui est appele
lorsque lon clique sur le bouton. On rcupre dabord notre format par un appel
getString() chose que nous aurions pu faire dans onCreate() pour plus defcacit.
Puis nous nous en servons pour formater la valeur du champ, ce qui nous renvoie une
String puisque la ressource chane est encode en HTML. Vous remarquerez que nous


utilisons TextUtils.htmlEncode() pour traduire en entits les caractres spciaux du
nom qui a t saisi, au cas o un utilisateur dciderait dentrer une esperluette, par exem-
ple. Enn, on convertit ce texte HTML en objet texte styl laide de Html.fromHtml() et
nous modions notre label.
La Figure 19.1 montre que le label de lactivit est vide lors de son lancement.
La Figure 19.2 montre ce que lon obtient aprs avoir saisi un nom et cliqu sur le bouton.
Figure 19.1
Lapplication Strings
Demo aprs son lancement.
Figure 19.2
La mme application,
aprs avoir entr un nom
clbre.


Vous voulez gagner une image ?
Android reconnat les images aux formats PNG, JPEG et GIF, bien que GIF soit ofcielle-
ment dconseill ; il est gnralement prfrable dutiliser PNG. Les images peuvent tre
utilises partout o lon attend un objet Drawable, comme pour limage et le fond dune
ImageView.
Lutilisation des images consiste simplement placer les chiers images dans res/drawa
ble/ puis y faire rfrence comme des ressources. Dans les chiers de description XML,
vous pouvez les dsigner par @drawable/nomfic, o nomfic est le nom de base du chier
(le nom de la ressource correspondant au chier res/drawable/truc.png est donc
@drawable/truc). Dans le code Java, il suft de prxer le nom de base du chier par
R.drawable lorsque vous avez besoin de lidentiant de ressource (R.drawable.truc,
par exemple).
Si vous avez besoin dune URI vers une ressource image, vous pouvez utiliser deux formats
diffrents pour indiquer son chemin :
android.resource://com.example.app/id, o com.example.app est le nom du
paquetage Java utilis par lapplication dans AndroidManifest.xml et id est lidenti-
ant de ressource numrique de la ressource (R.drawable.truc, par exemple).
android.resource://com.example.app/raw/nom, o com.example.app est le nom
du paquetage Java utilis par lapplication dans AndroidManifest.xml et nom est le
nom de la ressource brute (tel truc pour res/drawable/truc.png).
Android est fourni avec quelques ressources images prdnies qui sont accessibles en
Java via un prxe android.R.drawable an de les distinguer des ressources spciques
aux applications (android.R.drawable.picture frame, par exemple).
Modions lexemple prcdent pour quil utilise une icne la place de la ressource
chane du bouton. Ce projet, Resources/Image, ncessite dabord de modier lgrement
le chier de description an dutiliser un ImageButton et de faire rfrence un Drawable
nomm @drawable/icon :
<?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"
>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<ImageButton android:id="@+id/format"
android:layout_width="wrap_content"


android:layout_height="wrap_content"
android:src="@drawable/icon"
/>
<EditText android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
<TextView android:id="@+id/result"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Puis nous devons placer un chier image dans res/drawable avec icon comme nom de
base. Ici, nous utiliserons un chier PNG de 32 _ 32 pixels, issu de lensemble dicnes
Nuvola
1
. Enn, nous modions le code Java pour remplacer le Button par un Image
Button :
package com.commonsware.android.resources;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.Html;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.EditText;
import android.widget.TextView;
public class ImagesDemo extends Activity {
EditText name;
TextView result;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

name=(EditText)findViewById(R.id.name);
result=(TextView)findViewById(R.id.result);

ImageButton btn=(ImageButton)findViewById(R.id.format);

1. http://en.wikipedia.org/wiki/Nuvola.


btn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
applyFormat();
}
});
}

private void applyFormat() {
String format=getString(R.string.funky_format);
String simpleResult=String.format(format,
TextUtils.htmlEncode(name.getText().toString()));
result.setText(Html.fromHtml(simpleResult));
}
}
Notre bouton contient dsormais licne dsire (voir Figure 19.3).
Les ressources XML
Au Chapitre 18, nous avons vu que nous pouvions intgrer au paquetage de lapplication
des chiers XML sous forme de ressources brutes : il fallait ensuite y accder depuis le
code Java pour les analyser et les utiliser. Il existe un autre moyen dempaqueter des
documents XML statiques avec une application : les ressources XML.
Il suft de placer un chier XML dans res/xml/ pour pouvoir y accder en appelant la
mthode getXml() sur un objet Resources et en lui passant en paramtre un identiant
Figure 19.3
Lapplication
ImagesDemo.


prx par R.xml, suivi du nom de base du chier XML. Si une activit est fournie avec
un chier words.xml, par exemple, vous pouvez appeler getResour
ces().getXml(R.xml.words) an dobtenir une instance de la classe XmlPullParser,
qui se trouve dans lespace de noms org.xmlpull.v1. lheure o ce livre est crit, cette
classe nest pas documente, mais vous trouverez la documentation de cette bibliothque
sur son site web
1
.
Un analyseur pull XML est pilot par les vnements : il suft dappeler sa mthode
next() pour obtenir lvnement suivant, qui peut tre START TAG, END TAG,
END DOCUMENT, etc. Sur un vnement START TAG, vous pouvez accder au nom du
marqueur et ses attributs ; un unique vnement TEXT reprsente la concatnation de tous
les nuds qui sont les ls directs de cet lment. En itrant, en testant et en invoquant du
code lment par lment, vous nissez par analyser tout le chier.
Rcrivons le projet Files/Static pour quil utilise une ressource XML. Ce nouveau
projet Resources/XML exige que vous placiez le chier words.xml dans res/xml et
non plus dans res/raw. Le layout restant identique, il suft de modier le code
source :
package com.commonsware.android.resources;
import android.app.Activity;
import android.os.Bundle;
import android.app.ListActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.InputStream;
import java.util.ArrayList;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
public class XMLResourceDemo extends ListActivity {
TextView selection;
ArrayList<String> items=new ArrayList<String>();

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
selection=(TextView)findViewById(R.id.selection);
try {
1. http://www.xmlpull.org/v1/doc/api/org/xmlpull/v1/package-summary.html.


XmlPullParser xpp=getResources().getXml(R.xml.words);

while (xpp.getEventType()!=XmlPullParser.END_DOCUMENT) {
if (xpp.getEventType()==XmlPullParser.START_TAG) {
if (xpp.getName().equals("word")) {
items.add(xpp.getAttributeValue(0));
}
}

xpp.next();
}
}
catch (Throwable t) {
Toast
.makeText(this, "Echec de la requete : "+ t.toString(), 4000)
.show();
}

setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
items));
}

public void onListItemClick(ListView parent, View v, int position,
long id) {
selection.setText(items.get(position).toString());
}
}
Dsormais, dans notre bloc try...catch, nous obtenons un XmlPullParser et nous
bouclons jusqu la n du document. Si lvnement courant est START TAG et que le nom
de llment soit word (xpp.getName(). equals("word")), nous rcuprons le seul et
unique attribut et nous lajoutons la liste de mots de notre widget. Comme nous avons un
contrle total sur le chier XML (puisque cest nous qui lavons cr), nous pouvons
supposer quil ny a exactement quun attribut si nous nen tions pas srs, nous pour-
rions compter le nombre dattributs avec getAttributeCount() et obtenir leur nom avec
getAttributeName() au lieu de supposer aveuglment que lattribut dindice 0 est celui
auquel on pense.
Comme le montre la Figure 19.4, mis part le nom dans la barre de titre, le rsultat est
identique lexemple du Chapitre 18.


Valeurs diverses
Dans le rpertoire res/values/, vous pouvez placer un ou plusieurs chiers XML dcri-
vant des ressources simples : des dimensions, des couleurs et des tableaux. Dans les exem-
ples prcdents, nous avons dj vu des utilisations des dimensions et des couleurs, qui
taient passes sous forme de chanes simples aux appels de mthodes ("10px", par exem-
ple). Vous pouvez, bien sr, congurer ces valeurs comme des objets Java constants et
utiliser leurs noms symboliques, mais cela ne peut fonctionner que dans le code source
Java, pas dans les chiers de description XML. En les plaant dans des chiers de
ressources XML, vous pouvez rfrencer ces valeurs la fois dans du code Java et dans
les chiers de description ; en outre, elles sont ainsi centralises au mme endroit, ce qui
facilite leur maintenance.
Les chiers ressources XML ont pour racine llment resources ; tous les autres
lments sont des ls de cette racine.
Dimensions
Android utilise les dimensions en de nombreux endroits pour dcrire des distances, comme
la valeur de remplissage dun widget. Bien que nous utilisions souvent le pixel comme unit
de mesure (10px pour 10 pixels, par exemple), vous pouvez en choisir dautres :
in et mm indiquent, respectivement, des pouces et des millimtres, daprs la taille
physique de lcran.
pt reprsente des points, cest--dire 1/72
e
de pouce en termes typographiques, l aussi
daprs la taille physique de lcran.
Figure 19.4
Lapplication
XMLResourceDemo.


dp (device-independant pixel) et sp (scale-independant pixel) indiquent des pixels
indpendants du terminal un pixel est gal un dp pour un cran de 160 dpi, le
facteur dchelle reposant sur la densit de lcran (les pixels indpendants de lchelle
tiennent galement compte de la taille de la police choisie par lutilisateur).
Pour encoder une dimension comme une ressource, ajoutez un lment dimen ayant un
attribut name qui nomme de faon unique cette ressource. Le contenu de cet lment est
un texte reprsentant la valeur de la dimension :
<resources>
<dimen name="fin">10px</dimen>
<dimen name="epais">1in</dimen>
</resources>
Dans un chier de description, les dimensions peuvent tre rfrences par @dimen/nom,
o nom est le nom unique de la ressource (fin ou epais, dans lexemple prcdent). Dans
le code Java, il suft dutiliser le nom unique prx par R.dimen (Resources.getDimen
(R.dimen.fin), par exemple).
Couleurs
Les couleurs Android sexpriment en valeurs RGB hexadcimales et peuvent prciser un
canal alpha. Vous avez le choix entre des valeurs hexadcimales dun seul caractre ou de
deux caractres, ce qui donne donc quatre formats possibles :
#RGB ;
#ARGB ;
#RRGGBB ;
#AARRGGBB.
Ces valeurs sont les mmes que dans les feuilles de style CSS.
Vous pouvez, bien sr, les placer comme des chanes de caractres dans le code Java ou les
chiers de description. Cependant, il suft dajouter des lments color au chier de
ressource an de les grer comme des ressources. Ces lments doivent possder un attribut
name pour donner un nom unique la couleur et contenir la valeur RGB :
<resources>
<color name="jaune_orange">#FFD555</color>
<color name="vert_foret">#005500</color>
<color name="ambre_fonce">#8A3324</color>
</resources>
Dans un chier de description, ces couleurs peuvent tre dsignes par @color/nom, o
nom est le nom unique de la couleur (ambre fonce, par exemple). En Java, prxez ce
nom unique par R.color (Resources.getColor(R.color.vert foret), par exemple).


Tableaux
Les ressources tableaux sont conues pour contenir des listes de chanes simples, comme
une liste de civilits (M., Mme, Mlle, Dr, etc.).
Dans le chier ressource, vous avez besoin dun lment string array ayant un attribut
name pour donner un nom unique au tableau. Cet lment doit avoir autant de ls item
quil y a dlments dans ce tableau. Le contenu de chacun de ces ls est la valeur de
lentre correspondante :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="villes">
<item>Philadelphia</item>
<item>Pittsburgh</item>
<item>Allentown/Bethlehem</item>
<item>Erie</item>
<item>Reading</item>
<item>Scranton</item>
<item>Lancaster</item>
<item>Altoona</item>
<item>Harrisburg</item>
</string-array>
<string-array name="codes_aeroports">
<item>PHL</item>
<item>PIT</item>
<item>ABE</item>
<item>ERI</item>
<item>RDG</item>
<item>AVP</item>
<item>LNS</item>
<item>AOO</item>
<item>MDT</item>
</string-array>
</resources>
Dans le code Java, vous pouvez ensuite utiliser Resources.getStringArray(R.array.nom)
pour obtenir un String[] contenant tous les lments du tableau nom (Resources.get
StringArray(R.array.codes aeroports), par exemple).
Grer la diffrence
Un mme ensemble de ressources peut ne pas convenir toutes les situations dans lesquel-
les votre application est utilise. Un exemple vident est celui des ressources chanes et la
gestion de linternationalisation (I18N) et de la localisation (L10N). Nutiliser que des
chanes dune mme langue fonctionne parfaitement au moins pour le dveloppeur
mais cela produit une application qui rduit le nombre de ses utilisateurs potentiels.


Les ressources peuvent galement diffrer sur dautres points :
Lorientation de lcran. Lcran est-il en mode portrait (vertical) ou en mode
paysage (horizontal) ? Il peut galement tre carr, ce qui signie quil na donc pas
dorientation particulire.
La taille de lcran. Combien a-t-il de pixels ? Vous devez en tenir compte pour accor-
der en consquence la taille de vos ressources (petites ou grandes icnes, par exemple).
cran tactile. Si votre cran est tactile, est-il manipulable avec un stylet ou avec le
doigt ?
Clavier. De quel clavier dispose lutilisateur (alphanumrique, numrique, les deux) ?
Est-il toujours disponible ou est-ce une option ?
Autres dispositifs de saisie. Le terminal a-t-il dautres moyens de saisie, comme un
pad directionnel ou une molette ?
Pour grer toutes ces diffrences, Android utilise plusieurs rpertoires de ressources dont
les noms contiennent le critre concern.
Supposons, par exemple, que vous vouliez fournir des textes anglais et franais. Pour une
application non traduite, vous placeriez normalement ces chanes dans le chier res/
values/strings.xml. Pour pouvoir reconnatre la fois langlais et le franais, vous
devrez crer deux rpertoires, res/values en et res/values fr, o les deux lettres qui
suivent le tiret reprsentent la langue souhaite selon le codage ISO-639-1
1
. Les chanes
anglaises seraient donc places dans le chier res/values en/strings.xml et les fran-
aises, dans res/values fr/strings.xml. Android choisira alors le bon chier en fonction
de la conguration du terminal de lutilisateur.
Cela semble simple, nest-ce pas ?
Les choses se compliquent lorsque vous avez besoin de plusieurs critres distincts pour
vos ressources. Supposons, par exemple, que vous vouliez dvelopper une application la
fois pour le G1 de T-Mobile et deux autres terminaux ctifs. Lun deux (le Fictif Un)
dispose dun cran VGA gnralement en mode paysage (640 _ 480), dun clavier alpha-
numrique toujours ouvert, dun pad directionnel, mais pas dcran tactile. Lautre (le
Fictif Deux) a le mme cran que le G1 (320 _ 480), un clavier numrique mais pas alpha-
btique, un pad directionnel mais pas dcran tactile. Pour tirer parti de ces diffrences
dcrans et doptions de saisie, vous pourriez crer des chiers de description diffrents :
pour chaque combinaison de rsolution et dorientation ;
pour les terminaux qui ont un cran tactile et ceux qui nen ont pas ;
pour les terminaux qui ont des claviers alphanumriques et ceux qui nen ont pas.
Dans ces situations, toutes sortes de rgles entrent en jeu :
1. http://fr.wikipedia.org/wiki/Liste_des_codes_ISO_639-1.


Les options de conguration ( en, par exemple) ont une certaine priorit et doivent
apparatre dans cet ordre dans le nom du rpertoire. La documentation dAndroid
1
dcrit lordre prcis dans lequel ces options peuvent apparatre. Pour les besoins de
notre exemple, lorientation de lcran doit prcder le type de lcran (tactile ou non),
qui doit lui-mme prcder sa taille.
Il ne peut exister quune seule valeur par rpertoire pour chaque catgorie doption de
conguration.
Les options sont sensibles la casse.
Pour notre scnario, nous aurions donc besoin, en thorie, des rpertoires suivants :
res/layout port notouch qwerty 640x480 ;
res/layout port notouch qwerty 480x320 ;
res/layout port notouch 12key 640x480 ;
res/layout port notouch 12key 480x320 ;
res/layout port notouch nokeys 640x480 ;
res/layout port notouch nokeys 480x320 ;
res/layout port stylus qwerty 640x480 ;
res/layout port stylus qwerty 480x320 ;
res/layout port stylus 12key 640x480 ;
res/layout port stylus 12key 480x320 ;
res/layout port stylus nokeys 640x480 ;
res/layout port stylus nokeys 480x320 ;
res/layout port finger qwerty 640x480 ;
res/layout port finger qwerty 480x320 ;
res/layout port finger 12key 640x480 ;
res/layout port finger 12key 480x320 ;
res/layout port finger nokeys 640x480 ;
res/layout port finger nokeys 480x320 ;
res/layout land notouch qwerty 640x480 ;
res/layout land notouch qwerty 480x320 ;
res/layout land notouch 12key 640x480 ;
res/layout land notouch 12key 480x320 ;
res/layout land notouch nokeys 640x480 ;
res/layout land notouch nokeys 480x320 ;
1. http://code.google.com/android/devel/resources-i18n.html#AlternateResources.


res/layout land stylus qwerty 640x480 ;
res/layout land stylus qwerty 480x320 ;
res/layout land stylus 12key 640x480 ;
res/layout land stylus 12key 480x320 ;
res/layout land stylus nokeys 640x480 ;
res/layout land stylus nokeys 480x320 ;
res/layout land finger qwerty 640x480 ;
res/layout land finger qwerty 480x320 ;
res/layout land finger 12key 640x480 ;
res/layout land finger 12key 480x320 ;
res/layout land finger nokeys 640x480 ;
res/layout land finger nokeys 480x320.
Pas de panique ! Nous allons abrger cette liste dans un petit moment !
En ralit, beaucoup de ces chiers de description seront identiques. Par exemple, nous
voulons simplement que les descriptions des crans tactiles soient diffrentes de celles des
crans non tactiles. Cependant, comme nous ne pouvons pas combiner les deux types
dcrans, nous devrons thoriquement avoir des rpertoires distincts avec des contenus
identiques pour les crans manipulables avec le doigt et ceux manipulables avec un stylet.
Notez galement que rien nempche davoir un rpertoire avec un nom de base simple
(res/layout). En ralit, cest mme srement prfrable au cas o une nouvelle version
dAndroid introduirait dautres options de conguration que vous navez pas prises en
compte disposer dune description par dfaut permettra alors votre application de fonc-
tionner sur ce nouveau terminal.
Nous pouvons maintenant "tricher" un peu en dcodant les rgles quutilise Android pour
dterminer le "bon" rpertoire des ressources parmi lensemble des candidats :
1. Premirement, Android limine les candidats invalides dans le contexte. Si la taille de
lcran du terminal est de 320 240, par exemple, les rpertoires 640x480 seront
limins des candidats possibles car ils font spciquement appel une autre taille.
2. Deuximement, Android compte le nombre de correspondances pour chaque rper-
toire et ne conserve que les rpertoires qui en ont le plus.
3. Enn, Android suit lordre de priorit des options en dautres termes, il parcourt le
nom du rpertoire de gauche droite.
Nous pouvons donc nous ramener aux congurations suivantes :
res/layout port notouch qwerty 640x480 ;
res/layout port notouch qwerty ;
res/layout port notouch 640x480 ;


res/layout port notouch ;
res/layout port qwerty 640x480 ;
res/layout port qwerty ;
res/layout port 640x480 ;
res/layout port ;
res/layout land notouch qwerty 640x480 ;
res/layout land notouch qwerty ;
res/layout land notouch 640x480 ;
res/layout land notouch ;
res/layout land qwerty 640x480 ;
res/layout land qwerty ;
res/layout land 640x480 ;
res/layout land.
Ici, nous tirons parti du fait que les correspondances spciques ont priorit sur les valeurs
"non spcies". Ainsi, un terminal disposant dun clavier alphanumrique choisira une
ressource ayant qwerty dans son nom de rpertoire plutt quune ressource qui ne prcise
pas son type de clavier. Si lon combine cet tat de fait avec la rgle "le plus grand nombre
de correspondances lemporte", nous voyons que res/layout port ne correspondra
quaux terminaux dots dcrans de 480 320 pixels, sans clavier alphanumrique et avec
un cran tactile orient en mode portrait.
Nous pourrions prciser tout cela encore un peu plus, pour ne couvrir que les terminaux
que nous visons (le G1 de HTC, Fictif Un et Fictif Deux) et en gardant res/layout
comme description par dfaut :
res/layout port notouch 640x480 ;
res/layout port notouch ;
res/layout land notouch 640x480 ;
res/layout land notouch ;
res/layout land ;
res/layout.
Ici, 640x480 permet de diffrencier Fictif Un des deux autres, tandis que notouch distingue
Fictif Deux du G1 de HTC.


20
Accs et gestion des bases
de donnes locales
SQLite
1
est une base de donnes trs apprcie car elle fournit une interface SQL tout en
offrant une empreinte mmoire trs rduite et une rapidit de traitement satisfaisante. En
outre, elle appartient au domaine public et tout le monde peut donc lutiliser. De nombreuses
socits (Adobe, Apple, Google, Sun, Symbian) et plusieurs projets open-source (Mozilla,
PHP, Python) fournissent dsormais des produits intgrant SQLite.
SQLite tant intgr au moteur dexcution dAndroid, toute application peut crer des
bases de donnes SQLite. Ce SGBD disposant dune interface SQL, son utilisation est
assez vidente pour quiconque a une exprience avec dautres SGBDR. Cependant, son
API native nest pas JDBC, qui, dailleurs, serait trop lourd pour les terminaux limits en
mmoire comme les tlphones. Par consquent, les programmeurs Android doivent
apprendre une nouvelle API mais, comme nous allons le voir, ce nest pas trs difcile.
Ce chapitre prsente les bases de lutilisation de SQLite dans le contexte du dveloppe-
ment Android. Il ne prtend absolument pas tre une prsentation exhaustive de ce
SGBDR : pour plus de renseignements et pour savoir comment lutiliser dans dautres
1. http://www.sqlite.org.


environnements quAndroid, nous vous conseillons louvrage de Mike Owens, The De-
nitive Guide to SQLite
1
(Apress, 2006).
Les activits accdant gnralement une base de donnes via un fournisseur de contenu
(content provider) ou un service, ce chapitre ne contient pas dexemple complet : vous
trouverez un exemple de fournisseur de contenu faisant appel une base de donnes au
Chapitre 28.
Prsentation rapide de SQLite
SQLite, comme son nom lindique, utilise un dialecte de SQL pour effectuer des requtes
(SELECT), des manipulations de donnes (INSERT, etc.) et des dnitions de donnes
(CREATE TABLE, etc.). certains moments, il scarte du standard SQL-92, comme la
plupart des autres SGBDR, dailleurs. La bonne nouvelle est que SQLite est si efcace en
terme de mmoire que le moteur dexcution dAndroid peut linclure dans son intgra-
lit : vous ntes donc pas oblig de vous contenter dun sous-ensemble de ses fonctionna-
lits pour gagner de la place.
La plus grosse diffrence avec les autres SGBDR concerne principalement le typage des
donnes. Tant que vous pouvez prciser les types des colonnes dans une instruction
CREATE TABLE et tant que SQLite les utilise comme indication, tout va pour le mieux.
Vous pouvez mettre les donnes que vous voulez dans les colonnes que vous souhaitez. Vous
voulez placer une chane dans une colonne INTEGER ? Pas de problme ! Et vice versa ?
Cela marche aussi ! Cest ce que SQLite appelle "typage manifeste" ; il est dcrit de la
faon suivante dans sa documentation
2
:
Avec le typage manifeste, le type dune donne est une proprit de la valeur elle-mme,
pas de la colonne dans laquelle la valeur est stocke. SQLite permet donc de stocker une
valeur de nimporte quel type dans nimporte quelle colonne, quel que soit le type dclar
de cette colonne.
Certaines fonctionnalits standard de SQL ne sont pas reconnues par SQLite, notamment
les contraintes FOREIGN KEY, les transactions imbriques, RIGHT OUTER JOIN,
FULL OUTER JOIN et certaines variantes de ALTER TABLE.
Ces remarques mises part, vous disposez dun SGBDR complet, avec des triggers, des
transactions, etc. Les instructions SQL de base, comme SELECT, fonctionnent exactement
comme vous tes en droit de lattendre. Si vous tes habitu travailler avec un gros SGBDR
comme Oracle, vous pourriez considrer que SQLite est un "jouet", mais noubliez pas
que ces deux systmes ont t conus pour rsoudre des problmes diffrents et que vous
ntes pas prs de voir une installation complte dOracle sur un tlphone.
1. http://www.amazon.com/Denitive-Guide-SQLite/dp/1590596730.
2. http://www.sqlite.org/different.html.


Commencer par le dbut
Android ne fournit aucune base de donnes de son propre chef. Si vous voulez utiliser
SQLite, vous devez crer votre propre base, puis la remplir avec vos tables, vos index et
vos donnes.
Pour crer et ouvrir une base de donnes, la meilleure solution consiste crer une sous-
classe de SQLiteOpenHelper. Cette classe enveloppe tout ce qui est ncessaire la cra-
tion et la mise jour dune base, selon vos spcications et les besoins de votre application.
Cette sous-classe aura besoin de trois mthodes :
Un constructeur qui appelle celui de sa classe parente et qui prend en paramtre le
Context (une Activity), le nom de la base de donnes, une ventuelle fabrique de
curseur (le plus souvent, ce paramtre vaudra null) et un entier reprsentant la version
du schma de la base.
onCreate(), laquelle vous passerez lobjet SQLiteDatabase que vous devrez
remplir avec les tables et les donnes initiales que vous souhaitez.
onUpgrade(), laquelle vous passerez un objet SQLiteDatabase ainsi que lancien et
le nouveau numro de version. Pour convertir une base dun ancien schma un
nouveau, lapproche la plus simple consiste supprimer les anciennes tables et en
crer de nouvelles. Le Chapitre 28 donnera tous les dtails ncessaires.
Le reste de ce chapitre est consacr la cration et la suppression des tables, linsertion
des donnes, etc. Il prsentera galement un exemple de sous-classe de SQLiteOpenHelper.
Pour utiliser votre sous-classe, crez une instance et demandez-lui dappeler getReadable
Database() ou getWriteableDatabase() selon que vous vouliez ou non modier son
contenu :
db=(new DatabaseHelper(getContext())).getWritableDatabase();
return (db == null)? false: true;
Cet appel renverra une instance de SQLiteDatabase qui vous servira ensuite interroger
ou modier la base de donnes.
Lorsque vous avez ni de travailler sur cette base (lorsque lactivit est ferme, par exem-
ple), il suft dappeler la mthode close() de cette instance pour librer votre connexion.
Mettre la table
Pour crer des tables et des index, vous devez appeler la mthode execSQL() de lobjet
SQLiteDatabase en lui passant linstruction du LDD (langage de dnition des donnes)
que vous voulez excuter. En cas derreur, cette mthode renvoie null.


Vous pouvez, par exemple, utiliser le code suivant :
db.execSQL("CREATE TABLE
constantes (_id INTEGER PRIMARY KEY AUTOINCREMENT,
titre TEXT, valeur REAL);");
Cet appel cre une table constantes avec une colonne de cl primaire id qui est un
entier incrment automatiquement (SQLite lui affectera une valeur pour vous lorsque
vous insrerez les lignes). Cette table contient galement deux colonnes de donnes :
titre (un texte) et valeur (un nombre rel). SQLite crera automatiquement un index sur
la colonne de cl primaire si vous le souhaitez, vous pouvez en ajouter dautres laide
dinstructions CREATE INDEX.
Le plus souvent, vous crerez les tables et les index ds la cration de la base de donnes
ou, ventuellement, lorsquelle devra tre mise jour suite une nouvelle version de votre
application. Si les schmas des tables ne changent pas, les tables et les index nont pas
besoin dtre supprims mais, si vous devez le faire, il suft dutiliser execSQL() an
dexcuter les instructions DROP INDEX et DROP TABLE.
Ajouter des donnes
Lorsque lon cre une base de donnes et une ou plusieurs tables, cest gnralement pour
y placer des donnes. Pour ce faire, il existe principalement deux approches.
Vous pouvez encore utiliser execSQL(), comme vous lavez fait pour crer les tables.
Cette mthode permet en effet dexcuter nimporte quelle instruction SQL qui ne renvoie
pas de rsultat, ce qui est le cas dINSERT, UPDATE, DELETE, etc. Vous pourriez donc
utiliser ce code :
db.execSQL("INSERT INTO widgets (name, inventory)"+
"VALUES (Sprocket, 5)");
Une autre solution consiste utiliser insert(), update() et delete() sur lobjet SQLite
Database. Ces mthodes utilisent des objets ContentValues qui implmentent une inter-
face ressemblant Map mais avec des mthodes supplmentaires pour prendre en compte
les types de SQLite : outre get(), qui permet de rcuprer une valeur par sa cl, vous
disposez galement de getAsInteger(), getAsString(), etc.
La mthode insert() prend en paramtre le nom de la table, celui dune colonne pour
lastuce de la colonne nulle et un objet ContentValues contenant les valeurs que vous
voulez placer dans cette ligne. Lastuce de la colonne nulle est utilise dans le cas o
linstance de ContentValues est vide la colonne indique pour cette astuce recevra alors
explicitement la valeur NULL dans linstruction INSERT produite par insert().
ContentValues cv=new ContentValues();
cv.put(Constantes.TITRE, "Gravity, Death Star I");
cv.put(Constantes.VALEUR, SensorManager.GRAVITY_DEATH_STAR_I);
db.insert("constantes", getNullColumnHack(), cv);


La mthode update() prend en paramtre le nom de la table, un objet ContentValues
contenant les colonnes et leurs nouvelles valeurs et, ventuellement, une clause WHERE
et une liste de paramtres qui remplaceront les marqueurs prsents dans celle-ci. update()
nautorisant que des valeurs xes pour mettre jour les colonnes, vous devrez utiliser
execSQL() si vous souhaitez affecter des rsultats calculs.
La clause WHERE et la liste de paramtres fonctionnent comme les paramtres positionnels
qui existent galement dans dautres API de SQL :
// remplacements est une instance de ContentValues
String[] params=new String[] {"snicklefritz"};
db.update("widgets", remplacements, "name=?", params);
La mthode delete() fonctionne comme update() et prend en paramtre le nom de la
table et, ventuellement, une clause WHERE et une liste des paramtres positionnels pour
cette clause.
Le retour de vos requtes
Comme pour INSERT, UPDATE et DELETE, vous pouvez utiliser plusieurs approches
pour rcuprer les donnes dune base SQLite avec SELECT :
rawQuery() permet dexcuter directement une instruction SELECT.
query() permet de construire une requte partir de ses diffrentes composantes.
Un sujet de confusion classique est la classe SQLiteQueryBuilder et le problme des
curseurs et de leurs fabriques.
Requtes brutes
La solution la plus simple, au moins du point de vue de lAPI, consiste utiliser
rawQuery() en lui passant simplement la requte SELECT. Cette dernire peut contenir
des paramtres positionnels qui seront remplacs par les lments du tableau pass en
second paramtre. Voici un exemple :
Cursor c=db.rawQuery("SELECT name FROM sqlite_master
WHERE type=table
AND name=constantes", null);
Ici, nous interrogeons une table systme de SQLite (sqlite master) pour savoir si la
table constantes existe dj. La valeur renvoye est un Cursor qui dispose de mthodes
permettant de parcourir le rsultat (voir la section "Utilisation des curseurs").
Si vos requtes sont bien intgres votre application, cest une approche trs simple. En
revanche, elle se complique lorsquune requte comprend des parties dynamiques que les
paramtres positionnels ne peuvent plus grer. Si lensemble de colonnes que vous voulez


rcuprer nest pas connu au moment de la compilation, par exemple, concatner les noms
des colonnes pour former une liste dlimite par des virgules peut tre ennuyeux cest l
que query() entre en jeu.
Requtes normales
La mthode query() prend en paramtre les parties dune instruction SELECT an de
construire la requte. Ces diffrentes composantes apparaissent dans lordre suivant dans
la liste des paramtres :
1. Le nom de la table interroge.
2. La liste des colonnes rcuprer.
3. La clause WHERE, qui peut contenir des paramtres positionnels.
4. La liste des valeurs substituer ces paramtres positionnels.
5. Une ventuelle clause GROUP BY.
6. Une ventuelle clause ORDER BY.
7. Une ventuelle clause HAVING.
part le nom de la table, ces paramtres peuvent valoir null lorsquils ne sont pas
ncessaires :
String[] colonnes={"ID", "inventory"};
String[] params={"snicklefritz"};
Cursor result=db.query("widgets", colonnes, "name=?",
params, null, null, null);
Utilisation des "builders"
Une autre possibilit consiste utiliser SQLiteQueryBuilder, qui offre bien plus de possi-
bilits pour construire les requtes complexes, notamment celles qui impliquent dunir les
rsultats de plusieurs sous-requtes, par exemple. En outre, linterface SQLiteQueryBuilder
saccorde parfaitement avec linterface ContentProvider pour excuter les requtes.
Un patron de conception classique pour limplmentation de la mthode query() de votre
fournisseur de contenu consiste donc crer un objet SQLiteQueryBuilder, lui fournir
certaines valeurs par dfaut, puis lui faire construire (et, ventuellement, excuter) la
requte complte en combinant ces valeurs par dfaut avec celles qui ont t passes au
fournisseur de contenu lors de la demande de requte.
Voici, par exemple, un extrait de code dun fournisseur de contenu utilisant SQLiteQuery
Builder :
@Override
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sort) {


SQLiteQueryBuilder qb=new SQLiteQueryBuilder();
qb.setTables(getTableName());
if (isCollectionUri(url)) {
qb.setProjectionMap(getDefaultProjection());
}
else {
qb.appendWhere(getIdColumnName()+"="+url.getPathSegments().get(1));
}
String orderBy;
if (TextUtils.isEmpty(sort)) {
orderBy=getDefaultSortOrder();
} else {
orderBy=sort;
}
Cursor c=qb.query(db, projection, selection, selectionArgs,
null, null, orderBy);
c.setNotificationUri(getContext().getContentResolver(), url);
return c;
}
Les fournisseurs de contenu (content provider) seront expliqus en dtail dans la
cinquime partie de ce livre. Ici, nous pouvons nous contenter de remarquer que :
1. Nous construisons un objet SQLiteQueryBuilder.
2. Nous lui indiquons la table concerne par la requte avec setTables(getTableName()).
3. Soit nous lui indiquons lensemble de colonnes renvoyer par dfaut (avec setPro
jectionMap()), soit nous lui donnons une partie de clause WHERE an didentier
une ligne prcise de la table partir dun identiant extrait de lURI fournie lappel
de query() (avec appendWhere()).
4. Enn, nous lui demandons dexcuter la requte en mlangeant les valeurs de dpart
avec celles fournies query() (qb.query(db, projection, selection, selection
Args, null, null, orderBy)).
Au lieu de faire excuter directement la requte par lobjet SQLiteQueryBuilder, nous
aurions pu appeler buildQuery() pour la produire et renvoyer linstruction SELECT dont
nous avions besoin ; nous aurions alors pu lexcuter nous-mmes.
Utilisation des curseurs
Quelle que soit la faon dont vous excutez la requte, vous obtiendrez un Cursor en retour.
Il sagit de la version Android/SQLite des curseurs de bases de donnes, un concept utilis par
de nombreux SGBDR. Avec ce curseur, vous pouvez effectuer les oprations suivantes :
connatre le nombre de lignes du rsultat grce getCount() ;
parcourir les lignes du rsultat avec moveToFirst(), moveToNext() et isAfterLast() ;


connatre les noms des colonnes avec getColumnNames(), les convertir en numros de
colonnes grce getColumnIndex() et obtenir la valeur dune colonne donne de la
ligne courante via des mthodes comme getString(), getInt(), etc. ;
excuter nouveau la requte qui a cr le curseur, avec requery() ;
librer les ressources occupes par le curseur avec close().
Voici, par exemple, comment parcourir les entres de la table widgets rencontre dans les
extraits prcdents :
Cursor result=
db.rawQuery("SELECT ID, name, inventory FROM widgets");
result.moveToFirst();
while (!result.isAfterLast()) {
int id=result.getInt(0);
String name=result.getString(1);
int inventory=result.getInt(2);
// Faire quelque chose de ces valeurs...
result.moveToNext();
}
result.close();
Crer ses propres curseurs
Dans certains cas, vous pouvez vouloir utiliser votre propre sous-classe de Cursor plutt
que limplmentation de base fournie par Android. Dans ces situations, vous pouvez vous
servir des mthodes queryWithFactory() et rawQueryWithFactory(), qui prennent
toutes les deux en paramtre une instance de SQLiteDatabase.CursorFactory. Cette
fabrique, comme lon pourrait sy attendre, est responsable de la cration de nouveaux
curseurs via son implmentation de newCursor().
Limplmentation et lutilisation de cette approche sont laisses en exercice au lecteur. En
fait, vous ne devriez pas avoir besoin de crer vos propres classes de curseur au cours du
dveloppement dune application Android classique.
Des donnes, des donnes, encore des donnes
Si vous avez lhabitude de dvelopper avec dautres SGBDR, vous avez srement aussi utilis
des outils permettant dinspecter et de manipuler le contenu de la base de donnes et qui vont
au-del de lAPI. Avec lmulateur dAndroid, vous avez galement deux possibilits.
Premirement, lmulateur est cens fournir le programme sqlite3, accessible via la
commande adb shell. Lorsque vous avez lanc cette commande, tapez simplement sqlite3
suivi du chemin vers le chier de votre base de donnes, qui est gnralement de la forme :
/data/data/votre.paquetage.app/databases/nom_base


Ici, votre.paquetage.app est le paquetage Java de lapplication (com.commons
ware.android, par exemple) et nom base est le nom de la base de donnes, tel quil est
fourni createDatabase().
Le programme sqlite3 fonctionne bien et, si vous avez lhabitude de manipuler vos
tables partir de la console, il vous sera trs utile. Si vous prfrez disposer dune inter-
face un peu plus conviviale, vous pouvez copier la base de donnes SQLite du terminal sur
votre machine de dveloppement, puis utiliser un client graphique pour SQLite. Cepen-
dant, noubliez pas que vous travaillez alors sur une copie de la base : si vous voulez rper-
cuter les modications sur le terminal, vous devrez retransfrer cette base sur celui-ci.
Pour rcuprer la base du terminal, utilisez la commande adb pull (ou son quivalent
dans votre environnement de dveloppement) en lui fournissant le chemin de la base sur le
terminal et celui de la destination sur votre machine. Pour stocker une base de donnes
modie sur le terminal, utilisez la commande adb push en lui indiquant le chemin de
cette base sur votre machine et le chemin de destination sur le terminal.
Lextension SQLite Manager
1
pour Firefox est lun des clients SQLite les plus accessibles
(voir Figure 20.1), car elle est disponible sur toutes les plates-formes.
Vous trouverez galement dautres clients
2
sur le site web de SQLite
3
.
1. https://addons.mozilla.org/en-US/refox/addon/5817.
Figure 20.1
Lextension SQLite Manager de Firefox.
2. http://www.sqlite.org/cvstrac/wiki?p=SqliteTools.
3. http://www.sqlite.org.


21
Tirer le meilleur parti
des bibliothques Java
Java a autant de bibliothques tierces que les autres langages de programmation modernes,
si ce nest plus. Quand nous parlons de "bibliothques tierces", nous faisons rfrence ici
aux innombrables JAR que vous pouvez inclure dans une application Java, quelle quelle
soit : cela concerne tout ce que le SDK Java ne fournit pas lui-mme.
Dans le cas dAndroid, le cur de la machine virtuelle Dalvik nest pas exactement Java,
et ce que fournit son SDK nest pas la mme chose quun SDK Java traditionnel. Ceci
tant dit, de nombreuses bibliothques Java fournissent les fonctionnalits dont ne dispose
pas Android et peuvent donc vous tre utiles.
Ce chapitre explique ce quil faut faire pour tirer parti de ces bibliothques et dcrit les
limites de lintgration du code tiers une application Android.


Limites extrieures
Tout le code Java existant ne fonctionne videmment pas avec Android. Un certain nombre
de facteurs doivent tre pris en compte :
API pour la plate-forme. Est-ce que le code suppose que vous utilisez une JVM plus
rcente que celle sur laquelle repose Android ou suppose-t-il lexistence dune API
Java fournie avec J2SE mais qui nexiste pas dans Android, comme Swing ?
Taille. Le code Java conu pour tre utilis sur les machines de bureau ou les serveurs
ne se soucie pas beaucoup de lespace disque ni de la taille mmoire. Android, videm-
ment, manque des deux. Lutilisation de code tiers, notamment lorsquil est empaquet
sous forme de JAR, peut faire goner la taille de votre application..
Performances. Est-ce que le code Java suppose un CPU beaucoup plus puissant que
ceux que vous pouvez trouver sur la plupart des terminaux Android ? Ce nest pas
parce quun ordinateur de bureau peut lexcuter sans problme quun tlphone
mobile moyen pourra faire de mme.
Interface. Est-ce que le code Java suppose une interface en mode console, ou sagit-il
dune API que vous pouvez envelopper dans votre propre interface ?
Une astuce pour rgler quelques-uns de ces problmes consiste utiliser du code Java
open-source et modier ce code pour ladapter Android. Si, par exemple, vous nutili-
sez que 10 % dune bibliothque tierce, il est peut-tre plus intressant de recompiler ce
sous-ensemble ou, au moins, dter les classes inutilises du JAR. La premire approche
est plus sre dans la mesure o le compilateur vous garantit que vous ne supprimerez pas
une partie essentielle du code, mais elle peut tre assez dlicate.
Ant et JAR
Vous avez deux possibilits pour intgrer du code tiers dans votre projet : utiliser du code
source ou des JAR dj compils.
Si vous choisissez la premire mthode, il suft de copier le code source dans larbores-
cence de votre projet (sous le rpertoire src/) an quil soit plac ct de votre propre
code, puis de laisser le compilateur faire son travail.
Si vous choisissez dutiliser un JAR dont vous ne possdez peut-tre pas les sources, vous
devrez expliquer votre chane de dveloppement comment lutiliser. Avec un IDE, il
suft de lui donner la rfrence du JAR. Si, en revanche, vous utilisez le script Ant
build.xml, vous devez placer le chier JAR dans le rpertoire libs/ cr par activity
Creator, o le processus de construction dAnt ira le chercher.
Dans une dition prcdente de ce livre, par exemple, nous prsentions un projet MailBuzz
qui, comme son nom lindique, traitait du courrier lectronique. Ce projet utilisait les API


JavaMail et avait besoin de deux JAR JavaMail : mail 1.4.jar et activation 1.1.jar.
Avec ces deux chiers dans le rpertoire libs/, le classpath demandait javac deffec-
tuer une dition de liens avec ces JAR an que toutes les rfrences JavaMail dans le
code de MailBuzz puissent tre correctement rsolues. Puis le contenu de ces JAR
tait numr avec les classes compiles de MailBuzz lors de la conversion en instruc-
tions Dalvik laide de loutil dex. Sans cette tape, le code se serait peut-tre
compil, mais il naurait pas trouv les classes JavaMail lexcution, ce qui aurait provoqu
une exception.
Cependant, la machine virtuelle Dalvik et le compilateur fournis avec Android 0.9 et les
SDK plus rcents ne supportent plus certaines fonctionnalits du langage Java utilises par
JavaMail et, bien que le code source de JavaMail soit disponible, sa licence open-source
(Common Development and Distribution licence CDDL) pose certains problmes.
Suivre le script
la diffrence des autres systmes pour terminaux mobiles, Android nimpose aucune
restriction sur ce qui peut sexcuter tant que cest du Java qui utilise la machine virtuelle
Dalvik. Vous pouvez donc incorporer votre propre langage de script dans votre application, ce
qui est expressment interdit sur dautres terminaux.
BeanShell
1
est lun de ces langages de script Java. Il offre une syntaxe compatible Java,
avec un typage implicite, et ne ncessite pas de compilation.
Pour ajouter BeanShell, vous devez placer le chier JAR de linterprteur dans votre rper-
toire libs/. Malheureusement, le JAR 2.0b4 disponible au tlchargement sur le site de
BeanShell ne fonctionne pas tel quel avec Android 0.9 et les SDK plus rcents, probable-
ment cause du compilateur utilis pour le compiler. Il est donc prfrable de rcuprer
son code source laide de Subversion
2
, dexcuter ant jarcore pour le compiler et de
copier le JAR ainsi obtenu (dans le rpertoire dist/ de BeanShell) dans le rpertoire
libs/ de votre projet. Vous pouvez galement utiliser le JAR BeanShell accompagnant les
codes sources de ce livre (il se trouve dans le projet Java/AndShell). Ensuite, lutilisation
de BeanShell avec Android est identique son utilisation dans un autre environnement
Java :
1. On cre une instance de la classe Interpreter de BeanShell.
2. On congure les variables globales pour le script laide dInterpreter#set().
3. On appelle Interpreter#eval() pour lancer le script et, ventuellement, obtenir le
rsultat de la dernire instruction.
1. http://beanshell.org.
2. http://beanshell.org/developer.html.


Voici par exemple le chier de description XML du plus petit IDE BeanShell du monde :
<?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"
>
<Button
android:id="@+id/eval"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Go!"
/>
<EditText
android:id="@+id/script"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:singleLine="false"
android:gravity="top"
/>
</LinearLayout>
Voici limplmentation de lactivit :
package com.commonsware.android.andshell;
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import bsh.Interpreter;
public class MainActivity extends Activity {
private Interpreter i=new Interpreter();

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
Button btn=(Button)findViewById(R.id.eval);
final EditText script=(EditText)findViewById(R.id.script);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
String src=script.getText().toString();

try {
i.set("context", MainActivity.this);


i.eval(src);
}
catch (bsh.EvalError e) {
AlertDialog.Builder builder=
new AlertDialog.Builder(MainActivity.this);

builder
.setTitle("Exception!")
.setMessage(e.toString())
.setPositiveButton("OK", null)
.show();
}
}
});
}
}
Compilez ce projet (en incorporant le JAR de BeanShell comme on la mentionn plus
haut), puis installez-le sur lmulateur. Lorsque vous le lancerez, vous obtiendrez un IDE
trs simple, avec une grande zone de texte vous permettant de saisir votre script et un gros
bouton Go! pour lexcuter (voir Figure 21.1).
import android.widget.Toast;
Toast.makeText(context, "Hello, world!", 5000).show();
Notez lutilisation de context pour dsigner lactivit lors de la cration du toast.
Cette variable a t congure globalement par lactivit pour se dsigner elle-mme.
Figure 21.1
LIDE AndShell.


Vous pourriez lappeler autrement : ce qui importe est que lappel set() et le code du
script utilisent le mme nom.
Lorsque vous cliquez sur le bouton Go!, vous obtenez le rsultat de la Figure 21.2.
Ceci tant dit, il y a quelques prcautions prendre.
Premirement, tous les langages de script ne fonctionneront pas. Ceux qui implmentent
leur propre forme de compilation JIT (just-in-time) en produisant le pseudo-code Java
la vole , notamment, devront srement tre modis pour produire du pseudo-code
Dalvik la place. Les langages plus simples, qui interprtent seulement les chiers de
script en appelant les API dintrospection de Java pour se ramener des appels de classes
compiles, fonctionneront probablement mieux. Mme en ce cas, certaines fonctionnalits
du langage peuvent ne pas tre disponibles si elles reposent sur une caractristique de lAPI
Java traditionnelle qui nexiste pas avec Dalvik ce qui peut tre le cas du BeanShell ou
de certains JAR tiers avec les versions actuelles dAndroid.
Deuximement, les langages de script sans JIT seront forcment plus lents que des appli-
cations Dalvik compiles ; cette lenteur peut dplaire aux utilisateurs et impliquer plus de
consommation de batterie pour le mme travail. Construire une application Android en
BeanShell uniquement parce que vous trouvez quelle est plus facile crire peut donc
rendre vos utilisateurs assez mcontents.
Troisimement, les langages de script qui exposent toute lAPI Java, comme BeanShell,
peuvent raliser tout ce quautorise le modle de scurit sous-jacent dAndroid. Si votre
application dispose de la permission READ_CONTACTS, par exemple, tous les scripts
BeanShell quelle excutera lauront galement.
Figure 21.2
LIDE AndShell excutant
un script BeanShell.


Enn, mais ce nest pas le moins important, les JAR des interprteurs ont tendance tre...
gros. Celui du BeanShell utilis ici fait 200 Ko, par exemple. Ce nest pas ridicule si lon
considre ce quil est capable de faire, mais cela implique que les applications qui utilisent
BeanShell seront bien plus longues tlcharger, quelles prendront plus de place sur le
terminal, etc.
Tout fonctionne... enn, presque
Tous les codes Java ne fonctionneront pas avec Android et Dalvik. Vous devez plus prci-
sment tenir compte des paramtres suivants :
Si le code Java suppose quil sexcute avec Java SE, Java ME ou Java EE, il ne trou-
vera peut-tre pas certaines API quil a lhabitude de trouver sur ces plates-formes
mais qui ne sont pas disponibles avec Android. Certaines bibliothques de trac de
diagrammes supposent, par exemple, la prsence de primitives de trac Swing ou AWT
(Abstract Window Toolkit), qui sont gnralement absentes dAndroid.
Le code Java peut dpendre dun autre code Java qui, son tour, peut avoir des probl-
mes pour sexcuter sur Android. Vous pourriez vouloir utiliser un JAR qui repose, par
exemple, sur une version de la classe HTTPComponents dApache plus ancienne (ou
plus rcente) que celle fournie avec Android.
Le code Java peut utiliser des fonctionnalits du langage que le moteur Dalvik ne
reconnat pas.
Dans toutes ces situations, vous pouvez ne rencontrer aucun problme lors de la compila-
tion de votre application avec un JAR compil ; ces problmes surviendront plutt lors de
lexcution. Cest pour cette raison quil est prfrable dutiliser du code open-source avec
Android chaque fois que cela est possible : vous pourrez ainsi construire vous-mme le
code tiers en mme temps que le vtre et dtecter plus tt les difcults rsoudre.
Relecture des scripts
Ce chapitre tant consacr lcriture des scripts avec Android, vous apprcierez srement
de savoir quil existe dautres possibilits que lintgration directe de Beanshell dans votre
projet.
Certains essais ont t raliss avec dautres langages reposant sur la JVM, notamment
JRuby et Jython. Pour le moment, leur support dAndroid est incomplet, mais cette int-
gration progresse.
En outre, ASE (Android Scripting Environment), tlchargeable partir dAndroid
Market, permet dcrire des scripts en Python et Lua, et de les faire excuter par
BeanShell. Ces scripts ne sont pas des applications part entire et, lheure o ce livre
est crit, elles ne sont pas vraiment redistribuables. De plus, ASE na pas t rellement


conu pour tendre dautres applications, mme sil peut tre utilis de cette faon. Cepen-
dant, si vous voulez programmer directement sur le terminal, il est srement la meilleure
solution actuelle.


22
Communiquer via Internet
On sattend gnralement ce que la plupart des terminaux Android, si ce nest tous, int-
grent un accs Internet. Cet accs peut passer par le Wi, les services de donnes cellu-
laires (EDGE, 3G, etc.) ou, ventuellement, un mcanisme totalement diffrent. Quoi quil
en soit, la plupart des gens en tout cas, ceux qui ont un accs donnes ou Wi peuvent
accder Internet partir de leur tlphone Android.
Il nest donc pas tonnant quAndroid offre aux dveloppeurs un large ventail de moyens
leur permettant dexploiter cet accs. Ce dernier peut tre de haut niveau, comme le navi-
gateur WebKit intgr que nous avons tudi au Chapitre 13. Mais il peut galement inter-
venir au niveau le plus bas et utiliser des sockets brutes. Entre ces deux extrmits, il
existe des API disponibles sur le terminal ou via des JAR tiers donnant accs des
protocoles spciques comme HTTP, XMPP, SMTP, etc.
Ce livre sintresse plutt aux accs de haut niveau comme le composant WebKit et les
API Internet car, dans la mesure du possible, les dveloppeurs devraient sefforcer de
rutiliser des composants existants au lieu de crer leurs propres protocoles.


REST et relaxation
Bien quAndroid ne dispose pas dAPI cliente pour SOAP ou XML-RPC, il intgre la
bibliothque HttpComponents dApache. Vous pouvez donc soit ajouter une couche
SOAP/XML-RPC au-dessus de cette bibliothque, soit lutiliser directement pour accder aux
services web de type REST. Dans ce livre, nous considrerons les "services web REST"
comme de simples requtes HTTP classiques avec des rponses aux formats XML, JSON,
etc.
Vous trouverez des didacticiels plus complets, des FAQ et des HOWTO sur le site web de
HttpComponents
1
: nous ne prsenterons ici que les bases en montrant comment consulter
les informations mtorologiques.
Oprations HTTP via HttpComponents
Le composant HttpClient de HttpComponents gre pour vous toutes les requtes HTTP.
La premire tape pour lutiliser consiste videmment crer un objet. HttpClient tant
une interface, vous devrez donc instancier une implmentation de celle-ci, comme
DefaultHttpClient.
Ces requtes sont enveloppes dans des instances de HttpRequest, chaque commande
HTTP tant gre par une implmentation diffrente de cette interface (HttpGet pour les
requtes GET, par exemple). On cre donc une instance dune implmentation de HttpRe
quest, on construit lURL rcuprer ainsi que les autres donnes de conguration (les
valeurs des formulaires si lon effectue une commande POST via HttpPost, par exemple) puis
lon passe la mthode au client pour quil effectue la requte HTTP en appelant execute().
Ce qui se passe ensuite peut tre trs simple ou trs compliqu. On peut obtenir un objet
HttpResponse enveloppant un code de rponse (200 pour OK, par exemple), des en-ttes
HTTP, etc. Mais on peut galement utiliser une variante dexecute() qui prend en para-
mtre un objet ResponseHandler<String> : cet appel renverra simplement une reprsen-
tation String de la rponse. En pratique, cette approche est dconseille car il est
prfrable de vrier les codes de rponses HTTP pour dtecter les erreurs. Cependant,
pour les applications triviales comme les exemples de ce livre, la technique Response
Handler<String> convient parfaitement.
Le projet Internet/Weather, par exemple, implmente une activit qui rcupre les
donnes mtorologiques de votre emplacement actuel partir du site Google Weather.
Ces donnes sont converties en HTML puis passes un widget WebKit qui se charge de
les afcher. Nous laissons en exercice au lecteur la rcriture de ce programme pour quil
utilise un ListView. En outre, cet exemple tant relativement long, nous ne prsenterons
1. http://hc.apache.org/.


ici que les extraits de code en rapport avec ce chapitre ; les sources complets sont disponibles
dans les exemples fournis avec ce livre
1
.
Pour rendre tout ceci un peu plus intressant, nous utilisons les services de localisation
dAndroid pour dterminer notre emplacement actuel. Les dtails de fonctionnement de ce
service sont dcrits au Chapitre 33.
Lorsquun emplacement a t trouv soit au lancement, soit parce que nous avons boug
, nous rcuprons les donnes de Google Weather via la mthode updateForecast() :
private void updateForecast(Location loc) {
String url = String.format(format, ""
+ (int) (loc.getLatitude() * 1000000), ""
+ (int) (loc.getLongitude() * 1000000));
HttpGet getMethod = new HttpGet(url);
try {
ResponseHandler<String> responseHandler =
new BasicResponseHandler();
String responseBody = client.execute(getMethod,
responseHandler);
buildForecasts(responseBody);
String page = generatePage();
browser.loadDataWithBaseURL(null, page, "text/html",
"UTF-8", null);
} catch (Throwable t) {
Toast.makeText(this, "La requete a echouee: " +
t.toString(), 4000).show();
}
}
}
La mthode updateForecast() prend un objet Location en paramtre, obtenu via le
processus de mise jour de la localisation. Pour linstant, il suft de savoir que Location
dispose des mthodes getLatitude() et getLongitude(), qui renvoient, respectivement,
la latitude et la longitude.
LURL Google Weather est stocke dans une ressource chane laquelle nous ajoutons en
cours dexcution la latitude et la longitude. Nous construisons un objet HttpGet avec
cette URL (lobjet HttpClient a t cr dans onCreate()) puis nous excutons cette
mthode. partir de la rponse XML, nous construisons la page HTML des prvisions que
nous transmettons au widget WebKit. Si lobjet HttpClient choue avec une exception,
nous indiquons lerreur laide dun toast.
1. Reportez-vous la page ddie cet ouvrage sur le site www.pearson.fr.


Traitement des rponses
La rponse que lon obtient est dans un certain format HTML, XML, JSON, etc. et
cest nous, bien sr, de choisir linformation qui nous intresse pour en tirer quelque
chose dutile. Dans le cas de WeatherDemo, nous voulons extraire lheure de la prvision,
la temprature et licne (qui reprsente les conditions mtorologiques) an de nous en
servir pour produire une page HTML.
Android fournit :
trois analyseurs XML, lanalyseur DOM classique du W3C (org.w3c.dom), un analyseur
SAX (org.xml.sax) et lanalyseur pull prsent au Chapitre 19 ;
un analyseur JSON (org.json).
Lorsque cela est possible, vous pouvez bien sr utiliser du code Java tiers pour prendre en
charge dautres formats un analyseur RSS/Atom, par exemple. Lutilisation du code tiers
a t dcrite au Chapitre 21.
Pour WeatherDemo, nous utilisons lanalyseur DOM du W3C dans notre mthode build
Forecasts() :
void buildForecasts(String raw) throws Exception {
DocumentBuilder builder =
DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc =
builder.parse(new InputSource(new StringReader(raw)));
NodeList forecastList =
doc.getElementsByTagName("forecast_conditions");
for (int i = 0; i < forecastList.getLength(); i++) {
Element currentFore = (Element) forecastList.item(i);
// Retrouvons le jour de la semaine
String day =currentFore.getElementsByTagName("day_of_week")
.item(0).getAttributes()
.item(0).getNodeValue();
String lowTemp = currentFore.getElementsByTagName("low")
.item(0).getAttributes()
.item(0).getNodeValue();
String highTemp = currentFore.getElementsByTagName("high")
.item(0).getAttributes()
.item(0).getNodeValue();
String icon = currentFore.getElementsByTagName("icon")
.item(0).getAttributes()
.item(0).getNodeValue();
Forecast f = new Forecast();
f.setDay(day);
f.setLowTemp(lowTemp);
f.setHighTemp(highTemp);
f.setIcon(icon);
forecasts.add(f);
}
}


Le code HTML est lu comme un InputStream et est fourni lanalyseur DOM. Puis on
recherche les lments forecast conditions et lon remplit un ensemble de modles Fore
cast en incluant la date, la temprature et lURL de licne qui sera afche en fonction du
temps.
La mthode generatePage() produit son tour un tableau HTML rudimentaire contenant
les prvisions :
String generatePage() {
StringBuffer bufResult = new StringBuffer("<html><body><table>");
bufResult.append("<tr><th width=\"50%\">Jour</th>"
+ "<th>Basse</th><th>Haute</th><th>Tendance</th></tr>");
for (Forecast forecast : forecasts) {
bufResult.append("<tr><td align=\"center\">");
bufResult.append(forecast.getDay());
bufResult.append("</td><td align=\"center\">");
bufResult.append(forecast.getLowTemp());
bufResult.append("</td>");
bufResult.append("</td><td align=\"center\">");
bufResult.append(forecast.getHighTemp());
bufResult.append("</td><td><img src=\"");
bufResult.append(forecast.getIcon());
bufResult.append("\"></td></tr>");
}
bufResult.append("</table></body></html>");
return (bufResult.toString());
}
La Figure 22.1 montre le rsultat obtenu.
Figure 22.1
Lapplication
WeatherDemo.


Autres points importants
Si vous devez utiliser SSL, noubliez pas que la conguration de HttpClient ninclut pas
SSL par dfaut car cest vous de dcider comment grer la prsentation des certicats SSL
les acceptez-vous tous aveuglment, mme ceux qui sont autosigns ou qui ont expir ?
Prfrez-vous demander conrmation lutilisateur avant daccepter les certicats un peu
tranges ?
De mme, HttpClient est conu par dfaut pour tre utilis dans une application
monothread, bien quil puisse aisment tre congur pour travailler dans un contexte
multithread.
Pour tous ces types de problmes, la meilleure solution consiste consulter la documentation
et le support disponibles sur le site web de HttpComponents.


Partie IV
Intentions (Intents)
CHAPITRE 23. Cration de ltres dintentions
CHAPITRE 24. Lancement dactivits et de sous-activits
CHAPITRE 25. Trouver les actions possibles grce lintrospection
CHAPITRE 26. Gestion de la rotation


23
Cration de ltres
dintentions
Pour linstant, nous ne nous sommes intresss quaux activits ouvertes directement par
lutilisateur partir du lanceur du terminal, ce qui est, videmment, le moyen le plus
vident de lancer une activit et de la rendre disponible lutilisateur. Dans la plupart des
cas, cest de cette faon que lutilisateur commencera utiliser votre application.
Dans de nombreuses situations, le systme Android repose sur un grand nombre de
composants troitement lis. Ce que vous pouvez obtenir dans une interface graphique via
des botes de dialogue, des fentres lles, etc. est gnralement trait par des activits
indpendantes. Bien que lune delles puisse tre "spcique" puisquelle apparat dans le
lanceur, les autres doivent toutes tre accessibles... dune faon ou dune autre.
Elles le sont grce aux intentions.
Une intention est essentiellement un message que lon passe Android pour lui dire "je
veux que tu fasses... quelque chose". Ce "quelque chose" dpend de la situation parfois,
on sait parfaitement de quoi il sagit (ouvrir lune de nos autres activits, par exemple)
mais, dautres fois, on ne le sait pas.


Dans labsolu, Android ne se consacre quaux intentions et leurs rcepteurs. Mainte-
nant que nous avons vu comment crer des activits, plongeons-nous dans les intentions
an de pouvoir crer des applications plus complexes tout en tant de "bons citoyens
dAndroid".
Quelle est votre intention ?
Lorsque sir Tim Berners-Lee a conu le protocole de transfert hypertexte, HTTP, il a dni un
ensemble de verbes et dadresses sous la forme dURL. Une adresse dsigne une ressource :
une page web, une image ou un programme qui sexcute sur un serveur, par exemple. Un
verbe prcise laction qui doit sappliquer cette adresse : GET pour la rcuprer, POST
pour lui envoyer des donnes de formulaire an quelle les traite, etc.
Les intentions sont similaires car elles reprsentent une action et un contexte. Bien
quelles permettent de dnir plus dactions et de composants de contexte quil ny a de
verbes et de ressources HTTP, le concept est le mme.
Tout comme un navigateur web sait comment traiter une paire verbe + URL, Android sait
comment trouver les activits ou les autres applications qui sauront grer une intention
donne.
Composantes des intentions
Les deux parties les plus importantes dune intention sont laction et ce quAndroid
appelle les "donnes". Elles sont quasiment analogues aux verbes et aux URL de HTTP
laction est le verbe et les "donnes" sont une Uri comme content://contacts/people/1,
reprsentant un contact dans la base de donnes des contacts. Les actions sont des constan-
tes, comme ACTION VIEW (pour afcher la ressource), ACTION EDIT (pour lditer) ou
ACTION PICK (pour choisir un lment disponible dans une Uri reprsentant une collection,
comme content://contacts/people).
Si vous crez une intention combinant ACTION VIEW avec lUri content://contacts/
people/1 et que vous la passiez Android, ce dernier saura comment trouver et ouvrir une
activit capable dafcher cette ressource.
Outre laction et lUri des "donnes", vous pouvez placer dautres critres dans une intention
(qui est reprsente par un objet Intent) :
Une catgorie. Votre activit "principale" appartient la catgorie LAUNCHER, pour
indiquer quelle apparat dans le menu du lanceur. Les autres activits appartiendront
probablement aux catgories DEFAULT ou ALTERNATIVE.
Un type MIME indiquant le type de ressource sur laquelle vous voulez travailler si
vous ne connaissez pas une Uri collection.


Un composant, cest--dire la classe de lactivit suppose recevoir cette intention. Cette
utilisation des composants vite davoir besoin des autres proprits de lintention,
mais elle rend cette dernire plus fragile car elle suppose des implmentations spciques.
Des "Extras", cest--dire un Bundle dautres informations que vous voulez passer au
rcepteur en mme temps que lintention et dont ce dernier pourra tirer parti. Les infor-
mations utilisables par un rcepteur donn dpendent du rcepteur et sont (heureusement)
bien documentes.
La documentation dAndroid consacre la classe Intent contient les listes des actions et
des catgories standard.
Routage des intentions
Comme on la mentionn prcdemment, si le composant cible a t prcis dans linten-
tion, Android naura aucun doute sur sa destination il lancera lactivit en question. Ce
mcanisme peut convenir si lintention cible se trouve dans votre application, mais nest
vraiment pas recommand pour envoyer des intentions dautres applications car les noms
des composants sont globalement considrs comme privs lapplication et peuvent
donc tre modis. Il est prfrable dutiliser les modles dUri et les types MIME pour
identier les services auxquels vous souhaitez accder.
Si vous ne prcisez pas de composant cible, Android devra trouver les activits (ou les
autres rcepteurs dintentions) ligibles pour cette intention. Vous aurez remarqu que
nous avons mis "activits" au pluriel car une activit peut trs bien se rsoudre en plusieurs
activits. Cette approche du routage est prfrable au routage implicite.
Essentiellement, trois conditions doivent tre vries pour quune activit soit ligible
pour une intention donne :
1. Lactivit doit supporter laction indique.
2. Lactivit doit supporter le type MIME indiqu (sil a t fourni).
3. Lactivit doit supporter toutes les catgories nommes dans lintention.
La conclusion est que vous avez intrt ce que vos intentions soient sufsamment spci-
ques pour trouver le ou les bons rcepteurs, mais pas plus.
Tout ceci deviendra plus clair mesure que nous tudierons quelques exemples.
Dclarer vos intentions
Tous les composants Android qui souhaitent tre prvenus par des intentions doivent
dclarer des ltres dintention an quAndroid sache quelles intentions devraient aller vers
quel composant. Pour ce faire, vous devez ajouter des lments intent filter au chier
AndroidManifest.xml.


Le script de cration des applications Android (activityCreator ou son quivalent IDE)
fournit des ltres dintention tous les projets. Ces dclarations sont de la forme :
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.skeleton">
<application>
<activity android:name=".Now" android:label="Now">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Notez la prsence de llment intent filter sous llment activity. Il annonceles
choses suivantes :
Cette activit est lactivit principale de cette application.
Elle appartient la catgorie LAUNCHER, ce qui signie quelle aura une icne dans le
menu principal dAndroid.
Cette activit tant lactivit principale de lapplication, Android sait quelle est le
composant quil doit lancer lorsquun utilisateur choisit cette application partir du
menu principal.
Vous pouvez indiquer plusieurs actions ou catgories dans vos ltres dintention an de
prciser que le composant associ (lactivit) gre plusieurs sortes dintentions diffrentes.
Il est fort probable que vous voudrez galement que vos activits secondaires (non MAIN)
prcisent le type MIME des donnes quelles manipulent. Ainsi, si une intention est desti-
ne ce type MIME directement ou indirectement via une Uri rfrenant une ressource
de ce type , Android saura que le composant sait grer ces donnes.
Vous pourriez, par exemple, dclarer une activit de la faon suivante :
<activity android:name=".TourViewActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.commonsware.tour" />
</intent-filter>
</activity>
Celle-ci sera alors lance par une intention demandant lafchage dune Uri reprsentant
un contenu vnd.android.cursor.item/vnd.commonsware.tour. Cette intention pour-
rait provenir dune autre activit de la mme application (lactivit principale, par exemple)
ou dune autre application qui connat une Uri que cette activit peut grer.


Rcepteurs dintention
Dans les exemples que nous venons de voir, les ltres dintention taient congurs sur
des activits. Cependant, lier les intentions des activits nest parfois pas exactement ce
dont on a besoin :
Certains vnements systme peuvent nous obliger dclencher une opration dans un
service plutt quune activit.
Certains vnements peuvent devoir lancer des activits diffrentes en fonction des
circonstances, o le critre repose non pas uniquement sur lintention elle-mme, mais
sur un autre tat (si lon obtient lintention X et que la base de donnes contienne Y, on
lance lactivit M ; si la base ne contient pas Y, on lance lactivit N, par exemple).
Dans ces situations, Android offre un rcepteur dintention dni comme une classe qui
implmente linterface BroadcastReceiver. Les rcepteurs dintention sont des objets
conus pour recevoir des intentions notamment celles qui sont diffuses et pour effec-
tuer une action impliquant gnralement le lancement dautres intentions pour dclencher
une opration dans une activit, un service ou un autre composant.
Linterface BroadcastReceiver ne possde quune seule mthode onReceive(), que les
rcepteurs dintention doivent donc implmenter pour y effectuer les traitements quils
souhaitent en cas de rception dune intention. Pour dclarer un rcepteur dintention, il
suft dajouter un lment receiver au chier AndroidManifest.xml :
<receiver android:name=".MaClasseReceptriceDIntention/>
Un rcepteur dintention ne vit que le temps de traiter onReceive() lorsque cette
mthode se termine, linstance est susceptible dtre supprime par le ramasse-miettes et
ne sera pas rutilise. Ceci signie donc que les fonctionnalits de ces rcepteurs sont un
peu limites, essentiellement pour viter lappel de fonctions de rappel. Ils ne peuvent
notamment pas tre lis un service ni ouvrir une bote de dialogue.
La seule exception est lorsque le BroadcastReceiver est implment sur un composant
qui a une dure de vie assez longue, comme une activit ou un service : dans ce cas, le
rcepteur vivra aussi longtemps que son "hte" (jusqu ce que lactivit soit stoppe, par
exemple). Cependant, dans cette situation, vous ne pouvez pas dclarer le rcepteur dans
AndroidManifest.xml : il faut appeler registerReceiver() dans la mthode onResume()
de lactivit pour annoncer son intrt pour une intention, puis appeler unregister
Receiver() dans sa mthode onPause() lorsque vous navez plus besoin de ces intentions.
Attention la pause
Il y a un petit problme lorsque lon utilise des objets Intent pour transmettre des messa-
ges : ceci ne fonctionne que lorsque le rcepteur est actif. Voici ce que prcise la documen-
tation de BroadcastReceiver ce sujet :


Si vous enregistrez un rcepteur dans votre implmentation dActivity.onResume(), il
faut le dsinscrire dans Activity.onPause() (vous ne recevrez pas dintention pendant
la pause et cela vite une surcharge inutile du systme). Neffectuez pas cette dsinscription
dans Activity.onSaveInstanceState(), car cette mthode nest pas appele lorsque
lutilisateur revient dans son historique.
Vous pouvez donc utiliser les intentions pour transmettre des messages aux condition
suivantes :
Votre rcepteur ne se soucie pas de manquer des messages lorsquil est inactif.
Vous fournissez un moyen pour que le rcepteur rcupre les messages quil a manqus
pendant quil tait inactif.
Aux Chapitres 30 et 31, nous verrons un exemple de la premire condition, o le rcepteur
(le client du service) utilise des messages reposant sur des intentions lorsquelles sont
disponibles, mais pas quand le client nest pas actif.


24
Lancement dactivits
et de sous-activits
La thorie sous-jacente de larchitecture de linterface utilisateur dAndroid est que les
dveloppeurs devraient dcomposer leurs applications en activits distinctes, chacune
tant implmente par une Activity accessible via des intentions, avec une activit
"principale" lance partir du menu dAndroid. Une application de calendrier, par exem-
ple, pourrait avoir des activits permettant de consulter le calendrier, de visualiser un
simple vnement, den modier un (et den ajouter un), etc.
Ceci implique, bien sr, que lune de vos activits ait un moyen den lancer une autre. Si,
par exemple, lutilisateur clique sur un vnement partir de lactivit qui afche tout le
calendrier, vous voudrez montrer lactivit permettant dafcher cet vnement. Ceci
signie que vous devez pouvoir lancer cette activit en lui faisant afcher un vnement
spcique (celui sur lequel lutilisateur a cliqu).


Cette approche peut utiliser deux scnarios :
Vous connaissez lactivit lancer, probablement parce quelle fait partie de votre
application.
Vous disposez dune Uri vers quelque chose et vous voulez que vos utilisateurs
puissent en faire quelque chose, bien que vous ne sachiez pas encore comment.
Ce chapitre prsente le premier scnario ; le suivant dtaillera le second.
Activits paires et sous-activits
Lorsque vous dcidez de lancer une activit, une question essentielle laquelle vous devez
rpondre est : "Est-ce que mon activit a besoin de savoir quand se termine lactivit
quelle a lance ?"
Supposons par exemple que vous vouliez crer une activit pour collecter des informations
dauthentication pour un service web auquel vous vous connectez vous devrez peut-
tre vous authentier avec OpenID
1
pour utiliser un service OAuth
2
. En ce cas, votre acti-
vit principale devra savoir quand se termine lauthentication pour pouvoir commencer
utiliser le service web.
Imaginons maintenant une application de courrier lectronique Android. Lorsque lutilisa-
teur dcide de visualiser un chier attach, ni vous ni lutilisateur ne sattend ce que
lactivit principale sache quand cette visualisation se terminera.
Dans le premier scnario, lactivit lance est clairement subordonne lactivit qui la
lance. La premire sera donc srement lance comme une sous-activit, ce qui signie
que la seconde sera prvenue de la n de son activit lle.
Dans le second scnario, lactivit lance est plutt un "pair" de lactivit qui la lance. Elle
sera donc plutt lance comme une activit classique. Votre activit ne sera pas informe de la
n de sa "lle" mais, encore une fois, elle na pas vraiment besoin de le savoir.
Dmarrage
Pour dmarrer une activit, il faut une intention et choisir comment la lancer.
Cration dune intention
Comme on la expliqu au Chapitre 1, les intentions encapsulent une requte pour une
activit ou une demande adresse un autre rcepteur dintention, an quil ralise une
certaine tche.
1. http://openid.net/.
2. http://oauth.net/.


Si lactivit que vous comptez lancer vous appartient, il peut tre plus simple de crer une
intention explicite, nommant le composant lancer. partir de votre activit, vous pourriez
par exemple crer une intention de la faon suivante :
new Intent(this, HelpActivity.class);
Cette instruction indique que vous voulez lancer HelpActivity.
Vous pourriez galement crer une intention pour une Uri donne, demandant une action
particulire :
Uri uri=Uri.parse("geo:" + lat.toString() + "," + lon.toString());
Intent i=new Intent(Intent.ACTION_VIEW, uri);
Ici, partir de la latitude et de la longitude dune position (respectivement lat et lon),
nous construisons une Uri de schma geo et nous crons une intention demandant de
lafcher (ACTION VIEW).
Faire appel
Lorsque lon dispose de lintention, il faut la passer Android et rcuprer lactivit lle
lancer. Quatre choix sont alors possibles :
Le plus simple consiste appeler startActivity() en lui passant lintention
Android recherchera lactivit qui correspond le mieux et lui passera lintention pour
quelle la traite. Votre activit ne sera pas prvenue de la n de lactivit lle.
Vous pouvez appeler startActivityForResult() en lui passant lintention et un
identiant (unique pour lactivit appelante). Android recherchera lactivit qui corres-
pond le mieux et lui passera lintention. Votre activit sera prvenue par la mthode de
rappel onActivityResult() de la n de lactivit lle (voir plus loin).
Vous pouvez appeler sendBroadcast(). Dans ce cas, Android passera lintention
tous les BroadcastReceiver enregistrs qui pourraient vouloir cette intention, pas
uniquement celui qui correspond le mieux.
Vous pouvez appeler sendOrderedBroadcast(). Android passera alors lintention
tous les BroadcastReceiver candidats, chacun leur tour si lun deux "consomme"
lintention, les autres candidats ne sont pas prvenus.
La plupart du temps, vous utiliserez startActivity() ou startActivityForResult()
les intentions diffuses sont plutt lances par le systme Android lui-mme.
Comme on la indiqu, vous pouvez implmenter la mthode de rappel onActivity
Result() lorsque vous utilisez startActivityForResult(), an dtre prvenu de la n
de lactivit lle. Cette mthode reoit lidentiant unique fourni startActivityFor
Result() pour que vous puissiez savoir quelle est lactivit qui sest termine. Vous rcu-
prez galement :


Le code rsultat de lactivit lle qui a appel setResult(). Gnralement, ce code
vaut RESULT OK ou RESULT CANCELLED, bien que vous puissiez crer vos propres
codes (choisissez un entier partir de la valeur RESULT FIRST USER).
Un objet String optionnel contenant des donnes du rsultat, comme une URL vers
une ressource interne ou externe une intention ACTION PICK se sert gnralement de
cette chane pour renvoyer le contenu slectionn.
Un objet Bundle optionnel contenant des informations supplmentaires autres que le
code rsultat et la chane de donnes.
Pour mieux comprendre le lancement dune activit paire, examinons le projet Activi
ties/Launch. Le chier de description XML est assez simple puisquil contient deux
champs pour la latitude et la longitude, ainsi quun bouton :
<?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"
>
<TableLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="1,2"
>
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="2dip"
android:paddingRight="4dip"
android:text="Situation :"
/>
<EditText android:id="@+id/lat"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:cursorVisible="true"
android:editable="true"
android:singleLine="true"
android:layout_weight="1"
/>
<EditText android:id="@+id/lon"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:cursorVisible="true"
android:editable="true"
android:singleLine="true"
android:layout_weight="1"
/>


</TableRow>
</TableLayout>
<Button android:id="@+id/map"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Montre moi !"
/>
</LinearLayout>
LOnClickListener du bouton prend la latitude et la longitude pour les intgrer dans une
Uri de schma geo, puis lance lactivit.
package com.commonsware.android.activities;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class LaunchDemo extends Activity {
private EditText lat;
private EditText lon;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

Button btn=(Button)findViewById(R.id.map);
lat=(EditText)findViewById(R.id.lat);
lon=(EditText)findViewById(R.id.lon);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
String _lat=lat.getText().toString();
String _lon=lon.getText().toString();
Uri uri=Uri.parse("geo:" + _lat + "," +_lon);

startActivity(new Intent(Intent.ACTION_VIEW, uri));
}
});
}
}
Comme on le voit la Figure 24.1, cette activit ne montre pas grand-chose lorsquelle est
lance.
Lafchage devient plus intressant si lon entre un emplacement (38.8891 de latitude et
77.0492 de longitude, par exemple) et que lon clique sur le bouton (voir Figure 24.2).


Notez quil sagit de lactivit de cartographie intgre Android : nous navons pas cr
dactivit pour afcher cette carte.
Au Chapitre 34, nous verrons comment crer des cartes dans nos activits, au cas o lon
aurait besoin de plus de contrle sur leur afchage.
Figure 24.1
Lapplication
LaunchDemo, dans
laquelle on a saisi
un emplacement.
Figure 24.2
La carte lance
par LaunchDemo,
montrant lemplacement
de la Tour Eiffel Paris.


Navigation avec onglets
La navigation par onglet est lune des principales fonctionnalits des navigateurs web
actuels : grce elle, une mme fentre peut afcher plusieurs pages rparties dans une
srie donglets. Sur un terminal mobile, cela a moins dintrt car on gaspillerait la
prcieuse surface de lcran pour afcher les onglets eux-mmes. Toutefois, pour les
besoins de la dmonstration, nous montrerons comment crer ce genre de navigateur, en
utilisant TabActivity et les intentions.
Au Chapitre 10, nous avons vu quun onglet pouvait contenir une vue ou une activit.
Dans ce dernier cas, vous devez fournir une intention qui lancera lactivit souhaite ; le
framework de gestion des onglets placera alors linterface utilisateur de cette activit dans
longlet.
Votre premier instinct pourrait tre dutiliser une Uri http: comme nous lavions fait avec
une Uri geo: dans lexemple prcdent :
Intent i=new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("http://commonsware.com"));
Vous pourriez ainsi utiliser le navigateur intgr et disposer de toutes ses fonctionnalits.
Malheureusement, cela ne marche pas car, pour des raisons de scurit, vous ne pouvez
pas hberger les activits dautres applications dans vos onglets uniquement vos propres
activits.
Nous allons donc dpoussirer nos dmonstrations de WebView du Chapitre 13 pour crer
le projet Activities/IntentTab.
Voici le code source de lactivit principale, celle qui hberge le TabView :
public class IntentTabDemo extends TabActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TabHost host=getTabHost();

host.addTab(host.newTabSpec("un")
.setIndicator("CW")
.setContent(new Intent(this, CWBrowser.class)));
host.addTab(host.newTabSpec("deux")
.setIndicator("Android")
.setContent(new Intent(this, AndroidBrowser.class)));
}
}
Comme vous pouvez le constater, notre classe hrite de TabActivity : nous navons donc
pas besoin de crer un chier de description XML TabActivity sen occupe pour nous.


Nous nous contentons daccder au TabHost et de lui ajouter deux onglets, chacun prci-
sant une intention qui fait directement rfrence une autre classe. Ici, nos deux onglets
hbergeront respectivement un CWBrowser et un AndroidBrowser.
Ces activits sont de simples variantes de nos prcdents exemples de navigateurs :
public class CWBrowser extends Activity {
WebView browser;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);

browser=new WebView(this);
setContentView(browser);
browser.loadUrl("http://commonsware.com");
}
}
public class AndroidBrowser extends Activity {
WebView browser;

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);

browser=new WebView(this);
setContentView(browser);
browser.loadUrl("http://code.google.com/android");
}
}
Tous les deux chargent simplement une URL diffrente dans le navigateur : la page
daccueil de CommonsWare dans lun (voir Figure 24.3), celle dAndroid dans lautre
(voir Figure 24.4). Le rsultat montre laspect quaurait un navigateur onglets avec
Android.
Lutilisation de sous-classes diffrentes pour chaque page cible est plutt onreuse. la
place, nous aurions pu empaqueter lURL pour quelle souvre comme un "extra" dans une
intention et utiliser cette intention pour crer une activit BrowserTab gnraliste qui
extrairait lURL de cet "extra" an de lutiliser. Cette approche est laisse en exercice au
lecteur.


Figure 24.3
Lapplication IntentTab
Demo montrant le premier
onglet.
Figure 24.4
Lapplication IntentTab
Demo montrant le second
onglet.


25
Trouver les actions
possibles grce
lintrospection
Parfois, on sait exactement ce que lon veut faire afcher lune de nos autres activits,
par exemple ou lon en a une assez bonne ide comme voir le contenu de la ressource
dsigne par une Uri ou demander lutilisateur de choisir un contenu dun certain type
MIME. Mais, dautres fois, on ne sait rien... on dispose simplement dune Uri dont on ne
sait vraiment que faire.
Supposons que nous dveloppions un sous-systme de marquage pour Android, an que
les utilisateurs puissent marquer des contenus contacts, URL, emplacements gogra-
phiques, etc. Ce sous-systme se ramne lUri du contenu concern et aux marqueurs
associs, an que dautres sous-systmes puissent, par exemple, demander tous les contenus
utilisant un marqueur particulier.
Nous devons galement prvoir une activit de lecture permettant aux utilisateurs de
consulter tous leurs marqueurs et les contenus marqus. Le problme est quils satten-
dront pouvoir manipuler les contenus trouvs par ce sous-systme appeler un contact
ou afcher une carte correspondant un emplacement, par exemple.


Pourtant, on na absolument aucune ide de ce quil est possible de faire avec toutes
les Uri. On peut srement afcher tous les contenus, mais peut-on les modier ? Peut-on les
appeler au tlphone ? En outre, lutilisateur pouvant ajouter des applications avec des
nouveaux types de contenus tout moment, on ne peut pas supposer connatre toutes
les combinaisons possibles en consultant simplement les applications de base fournies
avec tous les terminaux Android.
Heureusement, les concepteurs dAndroid ont pens ce problme et ont mis notre
disposition plusieurs moyens de prsenter nos utilisateurs un ensemble dactivits
lancer pour une Uri donne mme si lon na aucune ide de ce que reprsente vraiment
cette Uri. Ce chapitre explore quelques-uns de ces outils dintrospection.
Faites votre choix
Parfois, on sait quune Uri reprsente une collection dun certain type : on sait, par exem-
ple, que content://contacts/people reprsente la liste des contacts dans le rpertoire
initial des contacts. Dans ce cas, on laisse lutilisateur choisir un contact que notre activit
pourra ensuite utiliser (pour le marquer ou lappeler, par exemple).
Pour ce faire, on doit crer une intention ACTION PICK sur lUri concerne, puis lancer
une sous-activit (par un appel startActivityForResult()) an que lutilisateur
puisse choisir un contenu du type indiqu. Si notre mthode de rappel onActivityRe
sult() pour cette requte reoit le code rsultat RESULT OK, la chane de donnes peut
tre analyse an de produire une Uri reprsentant le contenu choisi.
titre dexemple, examinons le projet Introspection/Pick : cette activit prsente
lutilisateur un champ pouvant contenir une Uri dsignant une collection (prremplie ici
avec content://contacts/people), plus un trs gros bouton :
<?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"
>
<EditText android:id="@+id/type"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:cursorVisible="true"
android:editable="true"
android:singleLine="true"
android:text="content://contacts/people"
/>
<Button
android:id="@+id/pick"
android:layout_width="fill_parent"


android:layout_height="fill_parent"
android:text="Dismoi tout!"
android:layout_weight="1"
/>
</LinearLayout>
Lorsquon clique dessus, le bouton cre une intention ACTION PICK pour lUri collection
qui a t saisie par lutilisateur ; puis la sous-activit est lance. Si cette dernire se
termine par RESULT OK, une intention ACTION VIEW est invoque pour lUri rsultante.
public class PickDemo extends Activity {
static final int PICK_REQUEST=1337;
private EditText type;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
type=(EditText)findViewById(R.id.type);

Button btn=(Button)findViewById(R.id.pick);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i=new Intent(Intent.ACTION_PICK,
Uri.parse(type.getText().toString()));
startActivityForResult(i, PICK_REQUEST);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode==PICK_REQUEST) {
if (resultCode==RESULT_OK) {
startActivity(new Intent(Intent.ACTION_VIEW,
data.getData()));
}
}
}
}
Lutilisateur peut donc choisir une collection (voir Figure 25.1), slectionner un contenu
(voir Figure 25.2) et lafcher (voir Figure 25.3).


Figure 25.1
Lapplication PickDemo
lors de son dmarrage.
Figure 25.2
La mme application,
aprs avoir cliqu sur le
bouton : la liste des
contacts safche.
Figure 25.3
Afchage dun contact
lanc par PickDemo aprs
que lutilisateur a choisi
une personne de la liste.


Prfrez-vous le menu ?
Un autre moyen dautoriser lutilisateur effectuer des actions sur un contenu, sans savoir
lavance quelles sont les actions possibles, consiste injecter un ensemble de choix dans
le menu de lapplication, en appelant addIntentOptions(). Cette mthode prend en
paramtre une intention et remplit un ensemble de choix de linstance Menu sur laquelle
elle est appele, chacun de ces choix reprsentant une action possible. Slectionner lun de
ces choix lancera lactivit associe.
Dans lexemple prcdent, le contenu, dont nous ne savions rien, provenait dune autre
application Android. Ceci dit, nous pouvons aussi savoir parfaitement quel est ce contenu,
lorsque cest le ntre. Cependant, les applications Android tant tout fait capables
dajouter de nouvelles actions des types de contenus existants, les utilisateurs auront
peut-tre dautres possibilits que lon ne connat pas encore, mme si lon crit une appli-
cation en sattendant un certain traitement du contenu.
Revenons, par exemple, au sous-systme de marquage voqu au dbut de ce chapitre.
Il serait trs ennuyeux pour les utilisateurs de devoir faire appel un outil de marquage
spar pour choisir un marqueur, puis revenir au contenu sur lequel ils travaillaient an de
lui associer le marqueur choisi. Ils prfreraient srement disposer dune option dans le
menu "Home" de lactivit leur permettant dindiquer quils veulent effectuer un
marquage, ce qui les mnerait une activit de conguration du marqueur, qui saurait dj
le contenu qui doit tre marqu.
Pour ce faire, le sous-systme de marquage doit congurer un ltre dintention supportant
nimporte quel contenu avec sa propre action (ACTION TAG, par exemple) et avec la cat-
gorie CATEGORY ALTERNATIVE, ce qui est la convention lorsquune application ajoute des
actions au contenu dune autre.
Pour crire des activits qui seront prvenues des ajouts possibles, comme le marquage,
faites appel addIntentOptions() pour ajouter les actions de ces ajouts votre menu,
comme ici :
Intent intent = new Intent(null, monUri);
intent.addCategory(Intent.ALTERNATIVE_CATEGORY);
menu.addIntentOptions(Menu.ALTERNATIVE, 0,
new ComponentName(this, MonActivite.class),
null, intent, 0, null);
Ici, monUri est une Uri dcrivant le contenu qui sera afch par lutilisateur dans cette
activit. MonActivite est le nom de la classe de lactivit et menu, le menu modier.
Dans notre cas, lintention que lon utilise pour choisir les actions exige que les rcepteurs
dintention appropris reconnaissent la catgorie CATEGORY ALTERNATIVE. Puis nous


ajoutons les options au menu avec la mthode addIntentOptions(), laquelle nous
passons les paramtres suivants :
La position de tri pour cet ensemble de choix. Cette valeur est gnralement 0 (pour
que lensemble apparaisse dans lordre o il est ajout au menu) ou ALTERNATIVE
(pour quil apparaisse aprs les autres choix du menu).
Un nombre unique pour cet ensemble de choix ou 0 si lon na pas besoin de ce nombre.
Une instance de ComponentName reprsentant lactivit qui remplit son menu elle sert
ltrer les propres actions de lactivit, an quelle puisse les traiter comme elle le souhaite.
Un tableau dinstances dIntent, contenant les correspondances "spciques" toutes
les actions correspondant ces Intent safcheront dans le menu avant les autres
actions possibles.
Lintention pour laquelle vous voulez les actions disponibles.
Un ensemble dindicateurs. Le seul rellement pertinent est reprsent par
MATCH DEFAULT ONLY, qui indique que les actions qui correspondent doivent gale-
ment implmenter la catgorie DEFAULT CATEGORY. Si lon na pas besoin de cette
information, il suft dutiliser la valeur 0 pour ces indicateurs.
Un tableau de Menu.Items qui contiendra les lments de menu qui correspondent au
tableau des instances Intent spciques fourni en quatrime paramtre, ou null si
lon nutilise pas ces lments.
Demander lentourage
Les familles ActivityAdapter et addIntentOptions() utilisent toutes les deux la mthode
queryIntentActivityOptions() pour rechercher les actions possibles. queryIntent
ActivityOptions() est implmente dans PackageManager : pour obtenir une instance
de cette classe, servez-vous de la mthode getPackageManager().
La mthode queryIntentActivityOptions() prend certains des paramtres daddIn
tentOptions(), notamment le ComponentName de lappelant, le tableau des instances
dIntent "spciques", lIntent gnrale reprsentant les actions que vous recherchez et
lensemble des indicateurs. Elle renvoie une liste dinstances dIntent correspondant aux
critres indiqus, les Intent spciques en premier.
Pour offrir des actions alternatives aux utilisateurs par un autre moyen quaddIntent
Options(), vous pouvez appeler queryIntentActivityOptions(), obtenir les instances
dIntent et les utiliser pour remplir une autre interface utilisateur (une barre doutils, par
exemple).


26
Gestion de la rotation
Certains terminaux Android, comme le G1 de T-Mobile, disposent dun clavier " tiroir"
qui, lorsquil est sorti, provoque le passage de lcran du mode portrait au mode paysage.
Dautres, comme liPhone, utilisent des acclromtres pour dterminer lorientation de
lcran.
Android fournit plusieurs moyens de grer la rotation de lcran an que vos applications
puissent elles-mmes traiter correctement les deux orientations. Ces outils vous aident
simplement dtecter et grer le processus de rotation cest vous de vrier que vos
interfaces utilisateurs apparatront correctement dans les deux orientations.
Philosophie de la destruction
Par dfaut, lorsquune modication dans la conguration du tlphone risque daffecter la
slection des ressources, Android supprimera et recrera toutes les activits en cours
dexcution ou en pause la prochaine fois quelles seront afches. Bien que ce phno-
mne puisse avoir lieu pour un grand nombre de modications de conguration (change-
ment de la langue, par exemple), il interviendra surtout dans le cas des rotations, car un
pivotement de lcran force le chargement dun nouvel ensemble de ressources (les
chiers de description, notamment).


Il faut bien comprendre quil sagit du comportement par dfaut : il peut tre le plus adapt
lune ou lautre de vos activits, mais vous pouvez le contrler et adapter la rponse de
vos activits aux changements dorientation ou de conguration.
Tout est pareil, juste diffrent
Comme, par dfaut, Android dtruit et rcre votre activit lorsque lorientation change, il
suft dutiliser la mthode onSaveInstanceState(), qui est appele chaque fois que
lactivit est supprime quelle quen soit la raison (tel un manque de mmoire). Implmen-
tez cette mthode dans votre activit an de stocker dans le Bundle fourni sufsamment
dinformations pour pouvoir revenir ltat courant. Puis, dans onCreate() (ou onRes
tore InstanceState(), si vous prfrez), restaurez les donnes du Bundle et utilisez-les
pour remettre lactivit dans son tat antrieur.
Le projet Rotation/RotationOne, par exemple, utilise deux chiers layout main.xml
pour les modes portrait et paysage. Ces chiers se trouvent respectivement dans les rper-
toires res/layout/ et res/layout land/.
Voici la disposition du mode portrait :
<?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"
>
<Button android:id="@+id/pick"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:text="Choisir"
android:enabled="true"
/>
<Button android:id="@+id/view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:text="Voir"
android:enabled="false"
/>
</LinearLayout>
Voici celle du mode paysage :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"


android:layout_height="fill_parent"
>
<Button android:id="@+id/pick"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:text="Choisir"
android:enabled="true"
/>
<Button android:id="@+id/view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:text="Voir"
android:enabled="false"
/>
</LinearLayout>
Linterface utilisateur est essentiellement compose de deux boutons occupant, chacun, la
moiti de lcran. En mode portrait, les boutons sont placs lun au-dessus de lautre ; en
mode paysage, ils sont cte cte.
Lapplication semble fonctionner correctement une rotation (Ctrl+F12 dans lmulateur)
modie la disposition de linterface. Bien que les boutons naient pas dtat, vous consta-
teriez, si vous utilisiez dautres widgets (comme EditText), quAndroid prend automati-
quement en charge une partie de leur tat (le texte saisi dans lEditText, par exemple).
En revanche, Android ne peut pas vous aider pour tout ce qui se trouve lextrieur des
widgets.
Cette application drive de lexemple PickDemo du Chapitre 25 (voir Figures 25.1, 25.2 et
25.3), o cliquer sur un contact vous permettait de consulter sa che. Ici, on la divise en
deux boutons, "Voir" ntant actif que lorsquun contact a t slectionn.
Voyons comment tout ceci est gr par onSaveInstanceState() :
public class RotationOneDemo extends Activity {
static final int PICK_REQUEST=1337;
Button viewButton=null;
Uri contact=null;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Button btn=(Button)findViewById(R.id.pick);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i=new Intent(Intent.ACTION_PICK,


Uri.parse("content://contacts/people"));
startActivityForResult(i, PICK_REQUEST);
}
});

viewButton=(Button)findViewById(R.id.view);

viewButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
startActivity(new Intent(Intent.ACTION_VIEW, contact));
}
});

restoreMe(savedInstanceState);

viewButton.setEnabled(contact!=null);
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode==PICK_REQUEST) {
if (resultCode==RESULT_OK) {
contact=data.getData();
viewButton.setEnabled(true);
}
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

if (contact!=null) {
outState.putString("contact", contact.toString());
}
}

private void restoreMe(Bundle state) {
contact=null;

if (state!=null) {
String contactUri=state.getString("contact");

if (contactUri!=null) {
contact=Uri.parse(contactUri);
}
}
}
}
Dans lensemble, ceci ressemble une activit normale... parce que cen est une. Initiale-
ment, le "modle" un contact dsign par une Uri vaut null. Il est initialis la suite
du lancement de la sous-activit ACTION PICK. Sa reprsentation sous forme de chane est


sauvegarde dans onSaveInstanceState() et restaure dans restoreMe() (qui est appe-
le partir donCreate()). Si le contact nest pas null, le bouton "Voir" est activ et
permet de visualiser la che du contact slectionn.
Le rsultat est prsent aux Figures 26.1 et 26.2.
Lavantage de cette implmentation est quelle gre un certain nombre dvnements
systme en plus de la rotation la fermeture de lapplication cause dun manque de
mmoire, par exemple.
Pour le plaisir, mettez en commentaire lappel de restoreMe() dans onCreate() et
essayez de lancer lapplication : vous constaterez quelle "oublie" un contact slectionn
dans lune des orientations lorsque vous faites tourner lmulateur ou le terminal.
Figure 26.1
Lapplication
RotationOne
en mode portrait.
Figure 26.2
Lapplication
RotationOne
en mode paysage.


Il ny a pas de petites conomies !
Le problme avec onSaveInstanceState() est que lon est limit un Bundle car cette
mthode de rappel est galement appele lorsque tout le processus est arrt (comme
quand il ny a plus assez de mmoire) : les donnes sauvegarder doivent donc pouvoir
tre srialises et ne pas dpendre du processus en cours.
Pour certaines activits, cette restriction ne pose pas de problme mais, pour dautres, elle
peut tre assez ennuyeuse. Dans une application de chat, par exemple, vous devrez couper
la connexion au serveur et la rtablir car il ny a pas moyen de stocker une socket dans un
Bundle ; ceci ne pose pas seulement un problme de performances mais peut galement
affecter la discussion elle-mme : votre dconnexion et votre reconnexion apparatront
dans les journaux du chat.
Un moyen de contourner ce problme consiste utiliser onRetainNonConfiguration
Instance() au lieu donSaveInstanceState() pour les "petites" modications comme
les rotations. La mthode de rappel onRetainNonConfigurationInstance() de votre
activit peut en effet renvoyer un Object, que vous pourrez rcuprer plus tard avec
getLastNonConfigurationInstance(). Cet Object peut reprsenter peu prs tout ce
que vous voulez gnralement, ce sera un objet "contexte" contenant ltat de lactivit,
comme les threads en cours dexcution, les sockets ouvertes, etc. La mthode
onCreate() de votre activit peut appeler getLastNonConfigurationInstance() si
elle renvoie une valeur non null, vous disposez de vos sockets, de vos threads, etc. La
plus grande limitation est que vous ne pouvez pas mettre dans ce contexte sauvegard tout
ce qui pourrait faire rfrence une ressource qui sera supprime, comme un Drawable
charg partir dune ressource.
Le projet Rotation/RotationTwo utilise cette approche pour grer les rotations. Les
chiers de description, et donc lapparence visuelle, sont identiques ceux de Rotation/
RotationOne. Les diffrences apparaissent uniquement dans le code Java :
public class RotationTwoDemo extends Activity {
static final int PICK_REQUEST=1337;
Button viewButton=null;
Uri contact=null;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Button btn=(Button)findViewById(R.id.pick);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i=new Intent(Intent.ACTION_PICK,
Uri.parse("content://contacts/people"));


startActivityForResult(i, PICK_REQUEST);
}
});

viewButton=(Button)findViewById(R.id.view);

viewButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
startActivity(new Intent(Intent.ACTION_VIEW, contact));
}
});

restoreMe();

viewButton.setEnabled(contact!=null);
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode==PICK_REQUEST) {
if (resultCode==RESULT_OK) {
contact=data.getData();
viewButton.setEnabled(true);
}
}
}
@Override
public Object onRetainNonConfigurationInstance() {
return(contact);
}

private void restoreMe() {
contact=null;

if (getLastNonConfigurationInstance()!=null) {
contact=(Uri)getLastNonConfigurationInstance();
}
}
}
Ici, nous rednissons onRetainNonConfigurationInstance() pour quelle renvoie
lUri de notre contact au lieu de sa reprsentation textuelle. De son ct, restoreMe()
appelle getLastNonConfigurationInstance() : si cette mthode renvoie une valeur non
null, il sagit de notre contact et lon active le bouton "Voir".
Lavantage de cette approche est que lon transmet lUri au lieu dune reprsentation
textuelle. Ici, cela ne reprsente pas une grosse conomie, mais notre tat pourrait tre
bien plus compliqu et contenir des threads, des sockets ou dautres choses que nous ne
pourrions pas empaqueter dans un Bundle.


Rotation maison
Ceci dit, mme cette approche peut tre trop intrusive pour votre application. Supposons,
par exemple, que vous dveloppiez un jeu en temps rel, comme un FPS (First Person
Shooter). Les "ralentissements" (lags) que vos utilisateurs constateront lorsque lactivit
est supprime et recre peuvent leur sufre se faire tuer, ce quils napprcieront sre-
ment pas. Bien que ce soit moins un problme avec le G1 puisquune rotation implique
douvrir le clavier chose que lutilisateur ne fera pas au milieu dun jeu , dautres termi-
naux effectuent une rotation simplement en fonction des informations de leurs acclro-
mtres.
La troisime possibilit pour grer les rotations consiste donc indiquer Android que
vous vous en occuperez vous-mme et que vous ne voulez pas que le framework vous aide.
Pour ce faire :
1. Utilisez lattribut android:configChanges de llment activity dans votre chier
AndroidManifest.xml pour numrer les modications de conguration que vous
voulez grer vous-mme.
2. Dans votre classe Activity, implmentez la mthode onConfigurationChanged(),
qui sera appele lorsque lune des modications numres dans android:config
Changes aura lieu.
Vous pouvez dsormais outrepasser tout le processus de suppression dactivit pour
nimporte quelle modication de conguration et simplement laisser une mthode de
rappel vous prvenir de cette modication.
Le projet Rotation/RotationThree utilise cette technique. L encore, les chiers de
description sont les mmes que prcdemment et lapplication aura donc le mme aspect
que RotationOne et RotationTwo. Le code Java est en revanche assez diffrent car nous
nous proccupons non plus de sauvegarder ltat mais plutt de mettre jour linterface
utilisateur pour grer les changements dorientation.
Nous devons dabord ajouter une petite modication notre manifeste :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.rotation.three"
android:versionCode="1"
android:versionName="1.0.0">
<application android:label="@string/app_name">
<activity android:name=".RotationThreeDemo"
android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>


</activity>
</application>
</manifest>
Ici, nous indiquons que nous traiterons nous-mmes les modications de conguration
keyboardHidden et orientation, ce qui nous protge de toutes les modications de
"rotation" une ouverture dun clavier ou une rotation physique. Notez que cette congu-
ration sapplique une activit, pas lapplication si vous avez plusieurs activits, vous
devrez dcider pour chacune delles de la tactique employer.
Voici le code Java de ce projet :
public class RotationThreeDemo extends Activity {
static final int PICK_REQUEST=1337;
Button viewButton=null;
Uri contact=null;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setupViews();
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode==PICK_REQUEST) {
if (resultCode==RESULT_OK) {
contact=data.getData();
viewButton.setEnabled(true);
}
}
}
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);

setupViews();
}

private void setupViews() {
setContentView(R.layout.main);

Button btn=(Button)findViewById(R.id.pick);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent i=new Intent(Intent.ACTION_PICK,
Uri.parse("content://contacts/people"));
startActivityForResult(i, PICK_REQUEST);
}


});

viewButton=(Button)findViewById(R.id.view);

viewButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
startActivity(new Intent(Intent.ACTION_VIEW, contact));
}
});

viewButton.setEnabled(contact!=null);
}
}
Limplmentation donCreate() dlgue lessentiel de son traitement la mthode
setupViews(), qui charge le layout et congure les boutons. Cette partie a t place dans
sa propre mthode car elle est galement appele partir donConfigurationChanged().
Forcer le destin
Dans les trois sections prcdentes, nous avons vu comment traiter les vnements de rota-
tion. Il existe, bien sr, une alternative radicale : demander Android de ne jamais faire
pivoter votre activit car, si lactivit ne pivote pas, il nest plus ncessaire de se soucier
dcrire le code pour grer les rotations.
Pour empcher Android de faire pivoter votre activit, il suft dajouter android:scree
nOrientation = "portrait" (ou "landscape") au chier AndroidManifest.xml (voir
le projet Rotation/RotationFour) :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.rotation.four"
android:versionCode="1"
android:versionName="1.0.0">
<application android:label="@string/app_name">
<activity android:name=".RotationFourDemo"
android:screenOrientation="portrait"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Noubliez pas que, comme prcdemment, cette conguration est propre une activit.


Avec elle, lactivit est verrouille dans lorientation que vous avez prcise, quoi que lon
fasse ensuite. Les copies dcran des Figures 26.3 et 26.4 montrent la mme activit que
celle des trois sections prcdentes mais utilisent le manifeste ci-dessus, avec lmulateur
en mode portrait et en mode paysage. Vous remarquerez que linterface utilisateur na pas
vari et quelle reste en mode portrait dans les deux cas.
Figure 26.3
Lapplication Rotation
Four en mode portrait.
Figure 26.4
Lapplication Rotation
Four en mode paysage.


Tout comprendre
Tous ces scnarios supposent que vous faites pivoter lcran en ouvrant le clavier du termi-
nal (ou en utilisant la combinaison de touches Ctrl+F12 avec lmulateur), ce qui est la
norme pour les applications Android.
Cependant, nous navons pas encore prsent le scnario utilis par liPhone.
Vous avez sans doute dj vu une ou plusieurs publicits pour liPhone montrant comment
lcran change dorientation lorsque lon fait pivoter le tlphone. Par dfaut, le G1 ne se
comporte pas de cette faon sur ce terminal, lorientation de lcran ne dpend que de
louverture ou de la fermeture du clavier.
Cependant, il est trs facile de modier ce comportement : pour que lafchage pivote en
fonction de la position du tlphone, il suft dajouter android:screenOrientation =
"sensor" au chier AndroidManifest.xml (voir le projet Rotation/RotationFive) :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.rotation.five"
android:versionCode="1"
android:versionName="1.0.0">
<application android:label="@string/app_name">
<activity android:name=".RotationFiveDemo"
android:screenOrientation="sensor"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
La valeur "sensor" indique Android que vous souhaitez que ce soient les acclro-
mtres qui contrlent lorientation de lcran, an quil pivote en mme temps que le tl-
phone.
Au moins sur le G1, ceci semble ne fonctionner que lorsque lon passe de la position
portrait classique la position paysage en faisant pivoter le tlphone de 90 degrs dans
le sens inverse des aiguilles dune montre. Une rotation dans lautre sens ne modie pas
lcran.
Notez galement que cette conguration dsactive la rotation de lcran par louverture du
clavier. Dans une activit "normale", avec le terminal en position portrait, lcran pivotera
si lon ouvre le clavier ; avec une activit utilisant la conguration android:screen
Orientation = "sensor", lcran ne pivotera pas.


Partie V
Fournisseurs
de contenus et services
CHAPITRE 27. Utilisation dun fournisseur de contenu (content provider)
CHAPITRE 28. Construction dun fournisseur de contenu
CHAPITRE 29. Demander et exiger des permissions
CHAPITRE 30. Cration dun service
CHAPITRE 31. Appel dun service
CHAPITRE 32. Alerter les utilisateurs avec des notications


27
Utilisation dun fournisseur
de contenu
(content provider)
Avec Android, toute Uri de schma content:// reprsente une ressource servie par un
fournisseur de contenu. Les fournisseurs de contenu encapsulent les donnes en utilisant
des instances dUri comme descripteurs on ne sait jamais do viennent les donnes
reprsentes par lUri et lon na pas besoin de le savoir : la seule chose qui compte est
quelles soient disponibles lorsquon en a besoin. Ces donnes pourraient tre stockes
dans une base de donnes SQLite ou dans des chiers plats, voire rcupres partir dun
terminal ou stockes sur un serveur situ trs loin dici, sur Internet.
partir dune Uri, vous pouvez raliser les oprations CRUD de base (Create, Read,
Update, Delete) en utilisant un fournisseur de contenu. Les instances dUri peuvent repr-
senter des collections ou des lments individuels. Grce aux premires, vous pouvez
crer de nouveaux contenus via des oprations dinsertion. Avec les secondes, vous
pouvez lire les donnes quelles reprsentent, les modier ou les supprimer.


Android permet dutiliser des fournisseurs de contenu existants ou de crer les vtres. Ce
chapitre est consacr leur utilisation ; le Chapitre 28 expliquera comment mettre dispo-
sition vos propres donnes laide du framework des fournisseurs de contenu.
Composantes dune Uri
Le modle simpli de construction dune Uri est constitu du schma, de lespace de
noms des donnes et, ventuellement, de lidentiant de linstance. Ces diffrents compo-
sants sont spars par des barres de fraction, comme dans une URL. Le schma dune Uri
de contenu est toujours content://.
LUri content://constants/5 reprsente donc linstance constants didentiant 5.
La combinaison du schma et de lespace de noms est appele "Uri de base" dun four-
nisseur de contenu ou dun ensemble de donnes support par un fournisseur de
contenu. Dans lexemple prcdent, content://constants est lUri de base dun four-
nisseur de contenu qui sert des informations sur "constants" (en loccurrence, des constantes
physiques).
LUri de base peut tre plus complique. Celle des contacts est, par exemple, content://
contacts/people, car le fournisseur de contenu des contacts peut fournir dautres
donnes en utilisant dautres valeurs pour lUri de base.
LUri de base reprsente une collection dinstances. Combine avec un identiant
dinstance (5, par exemple), elle reprsente une instance unique.
La plupart des API dAndroid sattendent ce que les URI soient des objets Uri, bien quil
soit plus naturel de les considrer comme des chanes. La mthode statique Uri.parse()
permet de crer une instance dUri partir de sa reprsentation textuelle.
Obtention dun descripteur
Do viennent ces instances dUri ?
Le point de dpart le plus courant, lorsque lon connat le type de donnes avec lequel on
souhaite travailler, consiste obtenir lUri de base du fournisseur de contenu lui-mme.
CONTENT URI, par exemple, est lUri de base des contacts reprsents par des personnes
elle correspond content://contacts/people. Si vous avez simplement besoin de la
collection, cette Uri fonctionne telle quelle ; si vous avez besoin dune instance dont vous
connaissez lidentiant, vous pouvez utiliser la mthode addId() dUri pour la lui ajouter,
an dobtenir une Uri pour cette instance prcise.
Vous pourriez galement obtenir des instances dUri partir dautres sources vous
pouvez rcuprer des descripteurs dUri pour les contacts via des sous-activits rpondant
aux intentions ACTION PICK, par exemple. Dans ce cas, lUri est vraiment un descripteur


opaque... jusqu ce que vous dcidiez de la dchiffrer laide des diffrentes mthodes
daccs de la classe Uri.
Vous pouvez galement utiliser des objets String cods en dur ("content:// contacts/
people", par exemple) et les convertir en instances Uri grce Uri.parse(). Ceci dit, ce
nest pas une solution idale car les valeurs des Uri de base sont susceptibles dvoluer au
cours du temps.
Cration des requtes
partir dune Uri de base, vous pouvez excuter une requte pour obtenir des donnes du
fournisseur de contenu li cette Uri. Ce mcanisme ressemble beaucoup SQL : on
prcise les "colonnes" qui nous intressent, les contraintes permettant de dterminer les
lignes du rsultat, un ordre de tri, etc. La seule diffrence est que cette requte sadresse
un fournisseur de contenu, pas directement un SGBDR comme SQLite.
Le point nvralgique de ce traitement est la mthode managedQuery(), qui attend cinq
paramtres :
1. LUri de base du fournisseur de contenu auquel sadresse la requte ou lUri
dinstance de lobjet interrog.
2. Un tableau des proprits dinstances que vous voulez obtenir de ce fournisseur de
contenu.
3. Une contrainte, qui fonctionne comme la clause WHERE de SQL.
4. Un ensemble ventuel de paramtres lier la contrainte, par remplacement des ven-
tuels marqueurs demplacements quelle contient.
5. Une instruction de tri facultative, qui fonctionne comme la clause ORDER BY de
SQL.
Cette mthode renvoie un objet Cursor partir duquel vous pourrez ensuite rcuprer les
donnes produites par la requte.
Les "proprits" sont aux fournisseurs de contenu ce que sont les colonnes aux bases de
donnes. En dautres termes, chaque instance (ligne) renvoye par une requte est forme
dun ensemble de proprits (colonnes) reprsentant, chacune, un lment des donnes.
Tout ceci deviendra plus clair avec un exemple : voici lappel de la mthode managed
Query() de la classe ConstantsBrowser du projet ContentProvider/Constants :
constantsCursor=managedQuery(Provider.Constants.CONTENT_URI,
PROJECTION, null, null, null);
Les paramtres de cet appel sont :


lUri passe lactivit par lappelant (CONTENT URI), qui reprsente ici lensemble
des constantes physiques gres par le fournisseur de contenu ;
la liste des proprits rcuprer (voir le code ci-aprs) ;
trois valeurs null, indiquant que nous nutilisons pas de contrainte (car lUri repr-
sente linstance que nous voulons), ni de paramtres de contrainte, ni de tri (nous ne
devrions obtenir quune seule ligne).
private static final String[] PROJECTION = new String[] {
Provider.Constants._ID, Provider.Constants.TITLE,
Provider.Constants.VALUE};
Le plus gros "tour de magie", ici, est la liste des proprits. La liste des proprits pour un
fournisseur de contenu donn devrait tre prcise dans la documentation (ou le code
source) de celui-ci. Ici, nous utilisons des valeurs logiques de la classe Provider qui
reprsentent les diffrentes proprits qui nous intressent (lidentiant unique, le nom et
la valeur de la constante).
Sadapter aux circonstances
Lorsque managedQuery() nous a fourni un Cursor, nous avons accs au rsultat de la
requte et pouvons en faire ce que nous voulons. Nous pouvons, par exemple, extraire
manuellement les donnes du Cursor pour remplir des widgets ou dautres objets.
Cependant, si le but de la requte est dobtenir une liste dans laquelle lutilisateur pourra
choisir un lment, il est prfrable dutiliser la classe SimpleCursorAdapter, qui tablit
un pont entre un Cursor et un widget de slection comme une ListView ou un Spinner.
Pour ce faire, il suft de copier le Cursor dans un SimpleCursorAdapter que lon passe
ensuite au widget ce widget montrera alors les options disponibles.
La mthode onCreate() de ConstantsBrowser, par exemple, prsente lutilisateur la
liste des constantes physiques :
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
constantsCursor=managedQuery(Provider.Constants.CONTENT_URI,
PROJECTION, null, null, null);
ListAdapter adapter=new SimpleCursorAdapter(this,
R.layout.row, constantsCursor,
new String[] {Provider.Constants.TITLE,
Provider.Constants.VALUE},
new int[] {R.id.title, R.id.value});
setListAdapter(adapter);
registerForContextMenu(getListView());
}


Aprs lexcution de managedQuery() et lobtention du Cursor, ConstantsBrowser cre
un SimpleCursorAdapter avec les paramtres suivants :
Lactivit (ou un autre Context) qui cre ladaptateur. Ici, il sagit de ConstantsBrowser
elle-mme.
Lidentiant du layout utilis pour afcher les lments de la liste (R.layout.row).
Le curseur (constantsCursor).
Les proprits extraire du curseur et utiliser pour congurer les instances View des
lments de la liste (TITLE et VALUE).
Les identiants correspondants des widgets TextView qui recevront ces proprits
(R.id.title et R.id.value).
Puis on place ladaptateur dans la ListView et lon obtient le rsultat prsent la
Figure 27.1.
Pour disposer de plus de contrle sur les vues, vous pouvez crer une sous-classe de
SimpleCursorAdapter et rednir getView() an de crer vos propres widgets pour la
liste, comme on la expliqu au Chapitre 9.
Gestion manuelle des curseurs
Vous pouvez, bien sr, extraire les donnes du curseur " la main". Linterface Cursor
ressemble ce que proposent les API daccs aux bases de donnes lorsquelles fournis-
Figure 27.1
ConstantsBrowser,
afchage dune liste de
constantes physiques.


sent des curseurs sous forme dobjets bien que, comme toujours, la diffrence rside dans
les dtails.
Position
Les instances de Cursor intgrent la notion de position, qui est semblable linterface
Iterator de Java. Pour accder aux lignes dun curseur, vous disposez des mthodes
suivantes :
moveToFirst() vous place sur la premire ligne de lensemble rsultat, tandis que
moveToLast() vous place sur la dernire.
moveToNext() vous place sur la ligne suivante et teste sil reste une ligne traiter
(auquel cas elle renvoie true ; false sinon).
moveToPrevious() vous place sur la ligne prcdente : cest la mthode inverse de
moveToNext().
moveToPosition() vous place sur la ligne lindice indiqu ; move() vous place sur
une ligne relativement la ligne courante (selon un dplacement qui peut tre positif
ou ngatif).
getPosition() renvoie lindice de la position courante.
Vous disposez galement dun ensemble de prdicats, dont isFirst(), isLast(),
isBeforeFirst() et isAfterLast().
Proprits
Lorsque le Cursor est positionn sur la ligne voulue, plusieurs mthodes supportant les
diffrents types possibles vous permettent dobtenir les proprits de cette ligne
(getString(), getInt(), getFloat(), etc.). Chacune delles prend en paramtre lindice
de la proprit voulue (en partant de zro).
Pour savoir si une proprit donne possde une valeur, vous pouvez utiliser la mthode
isNull() pour savoir si cette proprit est null ou non.
Insertions et suppressions
Les fournisseurs de contenu seraient assez peu intressants si vous ne pouviez pas ajouter
ou supprimer des donnes et que vous deviez vous contenter de modier celles qui sy
trouvent.
Pour insrer des donnes dans un fournisseur de contenu, linterface ContentProvider
(que vous pouvez obtenir via un appel getContentProvider() dans votre activit) offre
deux possibilits :


La mthode insert() prend en paramtre une Uri de collection et une structure
ContentValues dcrivant lensemble de donnes placer dans la ligne.
La mthode bulkInsert() prend en paramtre une Uri de collection et un tableau de
structures ContentValues pour remplir plusieurs lignes la fois.
La mthode insert() renvoie une Uri que vous pourrez manipuler ensuite. La mthode
bulkInsert() renvoie le nombre de lignes cres ; vous devrez effectuer une requte pour
obtenir nouveau les donnes que vous venez dinsrer.
Voici, par exemple, un extrait de ConstantsBrowser qui insre une nouvelle constante au
fournisseur de contenu partir dun DialogWrapper fournissant le nom et la valeur de
cette constante :
private void processAdd(DialogWrapper wrapper) {
ContentValues values=new ContentValues(2);
values.put(Provider.Constants.TITLE, wrapper.getTitle());
values.put(Provider.Constants.VALUE, wrapper.getValue());
getContentResolver().insert(Provider.Constants.CONTENT_URI,
values);
constantsCursor.requery();
}
Comme nous avons dj un Cursor en suspens pour le contenu de ce fournisseur, nous
appelons requery() sur celui-ci pour mettre jour son contenu. Cet appel, son tour,
mettra jour le SimpleCursorAdapter qui enveloppe ventuellement le Cursor et ces
modications seront rpercutes sur les widgets de slection (ListView, par exemple) qui
utilisent cet adaptateur.
Pour supprimer une ou plusieurs lignes dun fournisseur de contenu, utilisez la mthode
delete() de ContentResolver. Elle fonctionne comme linstruction DELETE de SQL et
prend trois paramtres :
1. LUri reprsentant la collection (ou linstance) que vous voulez supprimer.
2. Une contrainte fonctionnant comme une clause WHERE, qui sert dterminer les
lignes qui doivent tre supprimes.
3. Un ventuel ensemble de paramtres qui remplaceront les marqueurs demplacements
apparaissant dans la contrainte.
Attention aux BLOB !
Les BLOB (Binary large objects) existent dans de nombreux SGBDR, dont SQLite. Le
modle Android, cependant, prfre grer ces volumes de donnes via leurs propres Uri.


Un fournisseur de contenu, par consquent, ne fournit pas daccs direct via un Cursor
aux donnes binaires comme les photos : la place, cest une proprit de ce fournisseur
qui vous donnera lUri de ce BLOB particulier. Pour lire et crire les donnes binaires,
utilisez les mthodes getInputStream() et getOutputStream() de votre fournisseur de
contenu.
Dans la mesure du possible, il vaut mieux minimiser les copies de donnes inutiles. Lutili-
sation principale dune photo dans Android, par exemple, consiste lafcher, ce que sait
parfaitement faire le widget ImageView si on lui fournit une Uri vers un chier JPEG. En
stockant la photo de sorte quelle possde sa propre Uri, vous navez pas besoin de copier
des donnes du fournisseur de contenu vers une zone temporaire juste pour pouvoir laf-
cher : il suft dutiliser lUri. On suppose, en fait, que peu dapplications Android feront
beaucoup plus que dposer des donnes binaires et utiliser des widgets ou des activits
prdnies pour afcher ces donnes.


28
Construction dun
fournisseur de contenu
La construction dun fournisseur de contenu est srement la partie la plus complique et la
plus ennuyeuse du dveloppement avec Android car un fournisseur de contenu a de
nombreuses exigences en termes dimplmentation de mthodes et dexposition
de donnes publiques. Malheureusement, tant que vous naurez pas utilis votre fournis-
seur, vous ne pourrez pas savoir si vous avez tout fait correctement (cest donc diffrent de
la construction dune activit, o le compilateur vous indique les erreurs que vous avez
commises).
Ceci tant dit, la cration dun fournisseur de contenu est dune importance capitale
lorsquune application souhaite mettre ses donnes disposition dautres applications. Si
elle ne les garde que pour elle-mme, vous pouvez viter la cration dun fournisseur de
contenu en vous contentant daccder directement aux donnes depuis vos activits. En
revanche, si vous souhaitez que vos donnes puissent tre utilises par dautres si, par
exemple, vous dveloppez un lecteur RSS et que vous vouliez autoriser les autres
programmes accder aux ux que vous avez tlchargs et mis en cache , vous aurez
besoin dun fournisseur de contenu.


Dabord, une petite dissection
Comme on la expliqu au chapitre prcdent, lUri est le pilier de laccs aux donnes
dun fournisseur de contenu : cest la seule information quil faut rellement connatre.
partir de lUri de base du fournisseur, vous pouvez excuter des requtes ou construire
une Uri vers une instance prcise dont vous connaissez lidentiant.
Cependant, pour construire un fournisseur de contenu, vous devez en savoir un peu plus
sur les dtails internes de lUri dun contenu. Celle-ci est compose de deux quatre
parties en fonction de la situation :
Elle comprend toujours un schma (content://), indiquant quil sagit dune Uri de
contenu, pas dune Uri vers une ressource web (http://), par exemple.
Elle a toujours une autorit, qui est la premire partie du chemin plac aprs le schma.
Lautorit est une chane unique identiant le fournisseur qui gre le contenu associ
cette Uri.
Elle contient ventuellement un chemin de types de donnes, form de la liste des
segments de chemins situs entre lautorit et lidentiant dinstance (sil y en a un).
Ce chemin peut tre vide si le fournisseur ne gre quun seul type de donnes. Il peut
tre form dun seul segment (truc) ou de plusieurs (truc/machin/chouette) pour
grer tous les scnarios daccs aux donnes requis par le fournisseur de contenu.
Elle peut contenir un identiant dinstance, qui est un entier identiant une information
particulire du contenu. Une Uri de contenu sans identiant dinstance dsigne
lensemble du contenu reprsent par lautorit (et, sil est fourni, par le chemin des
donnes).
Une Uri de contenu peut tre aussi simple que content://sekrits, qui dsigne lensem-
ble du contenu fourni par nimporte quel fournisseur li lautorit sekrits (Secrets
Provider, par exemple), ou aussi complique que content://sekrits/card/pin/17,
qui dsigne linformation (identie par 17) de type card/pin gre par le fournisseur de
contenu sekrits.
Puis un peu de saisie
Vous devez ensuite proposer des types MIME correspondant au contenu de votre fournisseur.
Android utilise la fois lUri de contenu et le type MIME pour identier le contenu sur le
terminal. Une Uri de collection ou, plus prcisment, la combinaison dune autorit et
dun chemin de type de donnes doit correspondre deux types MIME, lun pour la
collection, lautre pour une instance donne. Ces deux types correspondent aux motifs
dUri que nous avons vus dans la section prcdente, respectivement pour les Uri avec et
sans identiant. Comme on la vu aux Chapitres 24 et 25, on peut fournir un type MIME
une intention pour quelle se dirige vers lactivit adquate (lintention ACTION_PICK


sur un type MIME de collection pour appeler une activit de slection permettant de
choisir une instance dans cette collection, par exemple).
Le type MIME de la collection doit tre de la forme vnd.X.cursor.dir/Y, o X est le nom
de votre socit, organisation ou projet et o Y est un nom de type dlimit par des points.
Vous pourriez, par exemple, utiliser le type MIME vnd.tlagency.cursor.dir/
sekrits.card.pin pour votre collection de secrets.
Le type MIME de l'instance doit tre de la forme vnd.X.cursor.item/Y o X et Y sont
gnralement les mmes valeurs que celles utilises pour le type MIME de la collection
(bien que ce ne soit pas obligatoire).
tape n 1 : crer une classe Provider
Un fournisseur de contenu est une classe Java, tout comme une activit et une intention. La
principale tape de la cration dun fournisseur consiste donc produire sa classe Java, qui
doit hriter de ContentProvider. Cette sous-classe doit implmenter six mthodes qui,
ensemble, assurent le service quun fournisseur de contenu est cens offrir aux activits
qui veulent crer, lire, modier ou supprimer du contenu.
onCreate()
Comme pour une activit, le point dentre principal dun fournisseur de contenu est sa
mthode onCreate(), o lon ralise toutes les oprations dinitialisation que lon
souhaite. Cest l, notamment, que lon initialise le stockage des donnes. Si, par exemple,
on compte stocker les donnes dans tel ou tel rpertoire dune carte SD et utiliser un
chier XML comme "table des matires", cest dans onCreate() que lon vriera que ce
rpertoire et ce chier existent et, dans le cas contraire, quon les crera pour que le reste
du fournisseur de contenu sache quils sont disponibles.
De mme, si le fournisseur de contenu utilise SQLite pour son stockage, cest dans
onCreate() que lon testera si les tables existent en interrogeant la table sqlite master.
Voici, par exemple, la mthode onCreate() de la classe Provider du projet Content
Provider/Constants :
@Override
public boolean onCreate() {
db=(new DatabaseHelper(getContext())).getWritableDatabase();
return (db == null) ? false : true;
}
Toute la "magie" de ce code se trouve dans lobjet priv DatabaseHelper, qui a t dcrit
au Chapitre 20.


query()
Comme lon pourrait sy attendre, la mthode query() contient le code grce auquel le
fournisseur de contenu obtient des dtails sur une requte quune activit veut raliser.
Cest vous de dcider deffectuer ou non cette requte.
La mthode query() attend les paramtres suivants :
Une Uri reprsentant la collection ou linstance demande.
Un String[] contenant la liste des proprits renvoyer.
Un String reprsentant lquivalent dune clause WHERE de SQL et qui contraint le
rsultat de la requte.
Un String[] contenant les valeurs qui remplaceront les ventuels marqueurs dempla-
cements dans le paramtre prcdent.
Un String quivalant une clause ORDER BY de SQL.
Cest vous qui tes responsable de linterprtation de ces paramtres et vous devez
renvoyer un Cursor qui pourra ensuite tre parcouru pour accder aux donnes.
Comme vous pouvez vous en douter, ces paramtres sont fortement orients vers lutilisa-
tion de SQLite comme moyen de stockage. Vous pouvez en ignorer certains (la clause
WHERE, par exemple) mais vous devez alors lindiquer pour que les activits ne vous
interrogent que par une Uri dinstance et nutilisent pas les paramtres que vous ne grez
pas. Pour les fournisseurs qui utilisent SQLite, toutefois, limplmentation de la mthode
query() devrait tre triviale : il suft dutiliser un SQLiteQueryBuilder pour convertir
les diffrents paramtres en une seule instruction SQL, puis dappeler la mthode query()
de cet objet pour invoquer la requte et obtenir le Cursor qui sera ensuite renvoy par
votre propre mthode query().
Voici, par exemple, limplmentation de la mthode query() de Provider :
@Override
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sort) {
SQLiteQueryBuilder qb=new SQLiteQueryBuilder();
qb.setTables(getTableName());
if (isCollectionUri(url)) {
qb.setProjectionMap(getDefaultProjection());
}
else {
qb.appendWhere(getIdColumnName() + "=" +
url.getPathSegments().get(1));
}
String orderBy;
if (TextUtils.isEmpty(sort)) {
orderBy=getDefaultSortOrder();
} else {


orderBy=sort;
}
Cursor c=qb.query(db, projection, selection, selectionArgs,
null, null, orderBy);
c.setNotificationUri(getContext().getContentResolver(), url);
return c;
}
Nous crons un SQLiteQueryBuilder et nous insrons les dtails de la requte dans cet
objet. Vous remarquerez que la requte pourrait utiliser soit une Uri de collection, soit une
Uri dinstance dans ce dernier cas, nous devons ajouter lidentiant dinstance la
requte. Puis nous utilisons la mthode query() de lobjet builder pour obtenir un Cursor
correspondant au rsultat.
insert()
Votre mthode insert() recevra une Uri reprsentant la collection et une structure
ContentValues contenant les donnes initiales de la nouvelle instance. Cest vous de la
crer, dy placer les donnes fournies et de renvoyer une Uri vers cette nouvelle instance.
Une fois encore, cette implmentation sera triviale pour un fournisseur de contenu qui
utilise SQLite : il suft de vrier que toutes les donnes requises ont t fournies par
lactivit, de fusionner les valeurs par dfaut avec les donnes fournies et dappeler la
mthode insert() de la base de donnes pour crer linstance.
Voici, par exemple, limplmentation de la mthode insert() de Provider :
@Override
public Uri insert(Uri url, ContentValues initialValues) {
long rowID;
ContentValues values;
if (initialValues!=null) {
values=new ContentValues(initialValues);
} else {
values=new ContentValues();
}
if (!isCollectionUri(url)) {
throw new IllegalArgumentException("URL inconnue" + url);
}
for (String colName : getRequiredColumns()) {
if (values.containsKey(colName) == false) {
throw new IllegalArgumentException("Colonne manquante : " +
colName);
}
}
populateDefaultValues(values);
rowID=db.insert(getTableName(), getNullColumnHack(), values);
if (rowID > 0) {
Uri uri=ContentUris.withAppendedId(getContentUri(), rowID);


getContext().getContentResolver().notifyChange(uri, null);
return uri;
}
throw new SQLException("Echec de linsertion dans " + url);
}
La technique est la mme que prcdemment : pour raliser linsertion, on utilise les parti-
cularits du fournisseur, plus les donnes insrer. Les points suivants mritent dtre
nots :
Une insertion ne peut se faire que dans une Uri de collection, cest la raison pour
laquelle on teste le paramtre avec isCollectionUri().
Le fournisseur sachant quelles sont les colonnes requises (avec getRequiredColumns()),
nous les parcourons et nous vrions que les valeurs fournies leur correspondent.
Cest au fournisseur de fournir les valeurs par dfaut (via populateDefaultValues())
pour les colonnes qui ne sont pas fournies lappel dinsert() et qui ne sont pas
automatiquement gres par la dnition de la table SQLite.
update()
La mthode update() prend en paramtre lUri de linstance ou de la collection modi-
er, une structure ContentValues contenant les nouvelles valeurs, une chane correspon-
dant une clause WHERE de SQL et un tableau de chanes contenant les valeurs qui
remplaceront les marqueurs demplacement dans la clause WHERE. Il vous appartient
didentier linstance ou les instances modier (en utilisant lUri et la clause WHERE)
puis de remplacer leurs valeurs actuelles par celles qui ont t fournies en paramtre.
Si vous utilisez SQLite comme systme de stockage, il suft de transmettre tous les para-
mtres la mthode update() de la base, mme si cet appel variera lgrement selon que
vous modiez une ou plusieurs instances.
Voici, par exemple, limplmentation de la mthode update() de Provider :
@Override
public int update(Uri url, ContentValues values, String where,
String[] whereArgs) {
int count;
if (isCollectionUri(url)) {
count=db.update(getTableName(), values, where, whereArgs);
}
else {
String segment=url.getPathSegments().get(1);
count=db
.update(getTableName(), values, getIdColumnName() + "="
+ segment


+ (!TextUtils.isEmpty(where) ? " AND (" + where
+ ) : ""), whereArgs);
}
getContext().getContentResolver().notifyChange(url, null);
return count;
}
Ici, les modications pouvant sappliquer une instance prcise ou toute la collection,
nous testons lUri avec isCollectionUri() : sil sagit dune modication de collection,
on se contente de la faire. Sil sagit dune modication dune seule instance, il faut ajouter
une contrainte la clause WHERE pour que la requte ne porte que sur la ligne concerne.
delete()
Comme update(), delete() reoit une Uri reprsentant linstance ou la collection
concerne, une clause WHERE et ses paramtres. Si lactivit supprime une seule
instance, lUri doit la reprsenter et la clause WHERE peut tre null. Lactivit peut
galement demander la suppression dun ensemble dinstances en utilisant la clause
WHERE pour indiquer les instances dtruire.
Comme pour update(), limplmentation de delete() est simple si lon utilise SQLite
car on peut laisser ce dernier le soin danalyser et dappliquer la clause WHERE la
seule chose faire est dappeler la mthode delete() de la base de donnes.
Voici, par exemple, limplmentation de la mthode delete() de Provider :
@Override
public int delete(Uri url, String where, String[] whereArgs) {
int count;
long rowId=0;
if (isCollectionUri(url)) {
count=db.delete(getTableName(), where, whereArgs);
}
else {
String segment=url.getPathSegments().get(1);
rowId=Long.parseLong(segment);
count=db
.delete(getTableName(), getIdColumnName() + "="
+ segment
+ (!TextUtils.isEmpty(where) ? " AND (" + where
+ ) : ""), whereArgs);
}
getContext().getContentResolver().notifyChange(url, null);
return count;
}


Ce code est quasiment identique celui de la mthode update() que nous avons dcrite
prcdemment il supprime un sous-ensemble de la collection ou une instance unique (si
elle vrie la clause WHERE passe en paramtre).
getType()
getType() est la dernire mthode quil faut implmenter. Elle prend une Uri en para-
mtre et renvoie le type MIME qui lui est associ. Cette Uri pouvant dsigner une
collection ou une instance, vous devez la tester pour renvoyer le type MIME corres-
pondant.
Voici limplmentation de la mthode getType() de Provider :
@Override
public String getType(Uri url) {
if (isCollectionUri(url)) {
return(getCollectionType());
}
return(getSingleType());
}
Comme vous pouvez le constater, lessentiel de ce code est dlgu aux mthodes prives
getCollectionType() et getSingleType() :
private String getCollectionType() {
return("vnd.android.cursor.dir/vnd.commonsware.constant");
}
private String getSingleType() {
return("vnd.android.cursor.item/vnd.commonsware.constant");
}
tape n 2 : fournir une Uri
Vous devez galement ajouter un membre public statique contenant lUri de chaque
collection supporte par votre fournisseur de contenu. Gnralement, on utilise une Uri
constante, publique et statique dans la classe du fournisseur lui-mme :
public static final Uri CONTENT_URI=
Uri.parse("content://com.commonsware.android.tourit.Provider/tours");
Vous pouvez utiliser le mme espace de noms pour lUri de contenu que pour vos classes
Java, an de rduire le risque de collisions de noms.


tape n 3 : dclarer les proprits
Pour dnir les noms des proprits de votre fournisseur, vous devez crer une classe
publique statique implmentant linterface BaseColumns :
public static final class Constants implements BaseColumns {
public static final Uri CONTENT_URI
=Uri.parse("content://com.commonsware.android.constants.Provider/
constants");
public static final String DEFAULT_SORT_ORDER="title";
public static final String TITLE="title";
public static final String VALUE="value";
}
Si vous utilisez SQLite pour stocker les donnes, les valeurs des constantes correspondant
aux noms des proprits doivent tre les noms des colonnes respectives de la table, an de
pouvoir simplement passer la projection (tableau de proprits) lors de lappel query()
ou le ContentValues lors dun appel insert() ou update().
Vous remarquerez que les types des proprits ne sont pas prciss. Il peut sagir de cha-
nes, dentiers, etc. en ralit, ils sont limits par les types autoriss par les mthodes
daccs du Cursor. Le fait que rien ne permette de tester les types signie que vous devez
documenter soigneusement vos proprits an que les utilisateurs de votre fournisseur de
contenu sachent quoi sattendre.
tape n 4 : modier le manifeste
Ce qui lie limplmentation du fournisseur de contenu au reste de lapplication se trouve
dans le chier AndroidManifest.xml. Il suft dajouter un lment <provider> comme
ls de llment <application> :
<provider
android:name=".Provider"
android:authorities="com.commonsware.android.tourit.Provider" />
La proprit android:name est le nom de la classe du fournisseur de contenu, prx par
un point pour indiquer quil se trouve dans lespace de noms des classes de cette application.
La proprit android:authorities est une liste des autorits reconnues par le fournis-
seur de contenu, spares par des points-virgules. Plus haut dans ce chapitre, nous avons
vu quune Uri de contenu tait constitue dun schma, dune autorit, dun chemin de
types de donnes et dun identiant dinstance. Chaque autorit de chaque valeur
CONTENT_URI devrait tre incluse dans la liste android:authorities.


Quand Android rencontre une Uri de contenu, il peut dsormais passer en revue les four-
nisseurs enregistrs via les manifestes an de trouver une autorit qui correspond. Il saura
alors quelle application et quelle classe implmentent le fournisseur de contenu et pourra
ainsi tablir le lien entre lactivit appelante et le fournisseur de contenu appel.
Avertissements en cas de modications
Votre fournisseur de contenu peut ventuellement avertir ses clients lorsque des modications
ont t apportes aux donnes dune Uri de contenu particulire.
Supposons, par exemple, que vous ayez cr un fournisseur de contenu qui rcupre des
ux RSS et Atom sur Internet en fonction des abonnements de lutilisateur (via OPML,
par exemple). Le fournisseur offre un accs en lecture seule au contenu des ux et garde
un il vers les diffrentes applications du tlphone qui les utilisent (ce qui est prfrable
une solution o chacun implmenterait son propre systme de rcupration des ux).
Vous avez galement implment un service qui obtient de faon asynchrone les mises
jour de ces ux et qui modie les donnes stockes sous-jacentes. Votre fournisseur de
contenu pourrait alerter les applications qui utilisent les ux que tel ou tel ux a t mis
jour, an que celles qui utilisent ce ux prcis puissent rafrachir ses donnes pour disposer
de la dernire version.
Du ct du fournisseur de contenu, cela ncessite dappeler notifyChange() sur votre
instance de ContentResolver (que vous pouvez obtenir via un appel get
Context().getContentResolver()). Cette mthode prend deux paramtres : lUri de
la partie du contenu qui a t modie et le ContentObserver qui a initi la modica-
tion. Dans de nombreux cas, ce deuxime paramtre vaudra null ; une valeur non null
signie simplement que lobservateur qui a lanc la modication ne sera pas prvenu de
sa propre modication.
Du ct du consommateur de contenu, une activit peut appeler registerContentObser
ver() sur son ContentResolver (obtenu via getContentResolver()). Cet appel lie une
instance de ContentObserver lUri fournie lobservateur sera prvenu chaque appel
de notifyChange() pour cette Uri. Lorsque le consommateur en a ni avec lUri, un
appel unregisterContentObserver() permet de librer la connexion.


29
Demander et exiger
des permissions
la n des annes 1990, une vague de virus sest rpandue sur Internet via les courriers
lectroniques. Ces virus utilisaient les informations stockes dans le carnet dadresses de
Microsoft Outlook : ils senvoyaient simplement en copie tous les contacts Outlook
dune machine infecte. Cette pidmie a t rendue possible parce que, cette poque,
Outlook ne prenait aucune mesure pour protger ses donnes des programmes qui utili-
saient son API puisque celle-ci avait t conue pour des dveloppeurs ordinaires, pas
pour des auteurs de virus.
Aujourdhui, de nombreuses applications qui utilisent des contacts les scurisent en
exigeant quun utilisateur donne le droit dy accder. Ces droits peuvent tre octroys au
cas par cas ou une fois pour toutes, lors de linstallation.
Android fonctionne de la mme faon : il exige que les applications qui souhaitent lire ou
crire les contacts aient les droits ncessaires. Cependant, son systme de permission va
bien au-del des donnes de contact et sapplique aux fournisseurs de contenu et aux services
qui ne font pas partie du framework initial.


En tant que dveloppeur Android, vous devrez frquemment vous assurer que vos applica-
tions aient les permissions adquates pour manipuler les donnes des autres applications.
Vous pouvez galement choisir dexiger des permissions pour que les autres applications
aient le droit dutiliser vos donnes ou vos services si vous les mettez disposition des
autres composants dAndroid.
Mre, puis-je ?
Demander dutiliser les donnes ou les services dautres applications exige dajouter
llment uses permission au chier AndroidManifest.xml. Votre manifeste peut
ainsi contenir zro ou plusieurs de ces lments comme ls directs de llment racine
manifest.
Llment uses permission na quun seul attribut, android:name, qui est le nom de la
permission exige par votre application :
<uses-permission
android:name="android.permission.ACCESS_LOCATION" />
Les permissions systme de base commencent toutes par android.permission et sont
numres dans la documentation du SDK la rubrique Manifest.permission. Les
applications tierces peuvent possder leurs propres permissions, qui seront sans doute
galement documentes. Voici quelques-unes des permissions prdnies les plus impor-
tantes :
INTERNET, si votre application souhaite accder Internet par quelque moyen que ce
soit, des sockets brutes de Java au widget WebView.
READ CALENDAR, READ CONTACTS, etc. pour la lecture des donnes partir des fournisseurs
de contenu intgrs.
WRITE CALENDAR, WRITE CONTACTS, etc. pour la modication des donnes dans les
fournisseurs de contenu intgrs.
Lutilisateur devra conrmer ces permissions lors de linstallation de lapplication. Cependant,
cette conrmation nest pas disponible dans la version actuelle de lmulateur.
Si vous ne possdez pas une permission donne et que vous tentiez deffectuer une opra-
tion qui lexige, lexception SecurityException vous informera du problme, bien que
ce ne soit pas une garantie cet chec peut prendre dautres formes si cette exception est
capture et traite par ailleurs.


Halte ! Qui va l ?
Lautre ct de la mdaille est, bien sr, la scurisation de votre propre application. Si
celle-ci est uniquement constitue dactivits et de rcepteurs dintention, la scurit peut
tre simplement tourne "vers lextrieur", lorsque vous demandez le droit dutiliser les
ressources dautres applications. Si, en revanche, votre application comprend des fournis-
seurs de contenu ou des services, vous voudrez mettre en place une scurit "vers lint-
rieur", pour contrler les applications qui peuvent accder vos donnes et le type de ces
accs.
Le problme, ici, est moins les "perturbations" que peuvent causer les autres applications
vos donnes que la condentialit des informations ou lutilisation de services qui pour-
raient vous coter cher. Les permissions de base dAndroid sont conues pour cela
pouvez-vous lire ou modier des contacts, envoyer des SMS, etc. Si votre application
stocke des informations susceptibles dtre prives, la scurit est moins un problme.
Mais, si elle stocke des donnes prives comme des informations mdicales, la scurit
devient bien plus importante.
La premire tape pour scuriser votre propre application laide de permissions consiste
les dclarer dans le chier AndroidManifest.xml. Au lieu dutiliser uses permission,
vous ajoutez alors des lments permission. L encore, il peut y avoir zro ou plusieurs
de ces lments, qui seront tous des ls directs de manifest.
Dclarer une permission est un peu plus compliqu quutiliser une permission car vous
devez donner trois informations :
1. Le nom symbolique de la permission. Pour viter les collisions de vos permissions
avec celles des autres applications, utilisez lespace de noms Java de votre application
comme prxe.
2. Un label pour la permission : un texte court et comprhensible par les utilisateurs.
3. Une description de la permission : un texte un peu plus long et comprhensible par les
utilisateurs.
<permission
android:name="vnd.tlagency.sekrits.SEE_SEKRITS"
android:label="@string/see_sekrits_label"
android:description="@string/see_sekrits_description" />
Cet lment nimpose pas la permission. Il indique seulement quil sagit dune permis-
sion possible ; votre application doit quand mme savoir dtecter les violations de scurit
lorsquelles ont lieu.


Imposer les permissions via le manifeste
Une application a deux moyens dimposer des permissions, en prcisant o et dans quelles
circonstances elles sont requises. Le plus simple consiste indiquer dans le manifeste les
endroits o elles sont requises.
Les activits, les services et les rcepteurs dintentions peuvent dclarer un attribut
android:permission dont la valeur est le nom de la permission exige pour accder ces
lments :
<activity
android:name=".SekritApp"
android:label="Top Sekrit"
android:permission="vnd.tlagency.sekrits.SEE_SEKRITS">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Seules les applications qui ont demand la permission indique pourront accder au
composant ainsi scuris. Ici, "accder" la signication suivante :
Les activits ne peuvent pas tre lances sans cette permission.
Les services ne peuvent pas tre lancs, arrts ou lis une activit sans cette permission.
Les rcepteurs dintention ignorent les messages envoys via sendBroadcast() si
lexpditeur ne possde pas cette permission.
Les fournisseurs de contenu offrent deux attributs distincts, android:readPermission et
android:writePermission :
<provider
android:name=".SekritProvider"
android:authorities="vnd.tla.sekrits.SekritProvider"
android:readPermission="vnd.tla.sekrits.SEE_SEKRITS"
android:writePermission="vnd.tla.sekrits.MOD_SEKRITS" />
Dans ce cas, readPermission contrle laccs linterrogation du fournisseur de
contenu, tandis que writePermission contrle laccs aux insertions, aux modications
et aux suppressions de donnes dans le fournisseur.


Imposer les permissions ailleurs
Il y a deux moyens supplmentaires dimposer les permissions dans votre code.
Vos services peuvent vrier les permissions chaque appel, grce checkCalling
Permission(), qui renvoie PERMISSION GRANTED ou PERMISSION DENIED selon que
lappelant a ou non la permission indique. Si, par exemple, votre service implmente
des mthodes de lecture et dcriture spares, vous pourriez obtenir le mme effet que
readPermission et writePermission en vriant que ces mthodes ont les permissions
requises.
Vous pouvez galement inclure une permission dans lappel sendBroadcast(). Ceci
signie que les rcepteurs possibles doivent possder cette permission : sans elle, ils ne
pourront pas recevoir lintention. Le sous-systme dAndroid inclut probablement la
permission RECEIVE SMS lorsquil diffuse linformation quun SMS est arriv, par exem-
ple ceci restreint les rcepteurs de cette intention aux seuls qui sont autoriss recevoir
des messages SMS.
Vos papiers, sil vous plat !
Il ny a pas de dcouverte automatique des permissions lors de la compilation ; tous les
checs lis aux permissions ont lieu lexcution. Il est donc important de documenter
les permissions requises pour votre API publique, ce qui comprend les fournisseurs de
contenu, les services et les activits conues pour tre lances partir dautres activits.
Dans le cas contraire, les programmeurs voulant sinterfacer avec votre application
devront batailler pour trouver les rgles de permissions que vous avez mises en place.
En outre, vous devez vous attendre ce que les utilisateurs de votre application soient
interrogs pour conrmer les permissions dont votre application a besoin. Vous devez
donc leur indiquer quoi sattendre, sous peine quune question pose par le tlphone ne
les dcide ne pas installer ou ne pas utiliser lapplication.


30
Cration dun service
Comme on la dj mentionn, les services Android sont destins aux processus de longue
haleine, qui peuvent devoir continuer de sexcuter mme lorsquils sont spars de toute
activit. titre dexemple, citons la musique, qui continue dtre joue mme lorsque
lactivit de "lecture" a t supprime, la rcupration des mises jour des ux RSS sur
Internet et la persistance dune session de chat, mme lorsque le client a perdu le focus
cause de la rception dun appel tlphonique.
Un service est cr lorsquil est lanc manuellement (via un appel lAPI) ou quand une
activit tente de sy connecter via une communication interprocessus (IPC). Il perdure
jusqu ce quil ne soit plus ncessaire et que le systme ait besoin de rcuprer de la
mmoire. Cette longue excution ayant un cot, les services doivent prendre garde ne
pas consommer trop de CPU sous peine duser trop rapidement la batterie du terminal.
Dans ce chapitre, vous apprendrez crer vos propres services ; le chapitre suivant sera
consacr lutilisation de ces services partir de vos activits ou dautres contextes. Tous
les deux utiliseront comme exemple le projet Service/WeatherPlus, dont limplmenta-
tion sera essentiellement prsente dans ce chapitre. Ce projet tend Internet/Weather
en lempaquetant dans un service qui surveille les modications de lemplacement du
terminal, an que les prvisions soient mises jour lorsque celui-ci "se dplace".


Service avec classe
Limplmentation dun service partage de nombreuses caractristiques avec la construction
dune activit. On hrite dune classe de base fournie par Android, on rednit certaines
mthodes du cycle de vie et on accroche le service au systme via le manifeste.
La premire tape de cration dun service consiste tendre la classe Service en drivant
une classe WeatherPlusService dans le cadre de notre exemple.
Tout comme les activits peuvent rednir les mthodes onCreate(), onResume(),
onPause() et apparentes, les implmentations de Service peuvent rednir trois mthodes
du cycle de vie :
1. onCreate(), qui, comme pour les activits, sera appele lorsque le processus du
service est cr.
2. onStart(), qui est appele lorsquun service est lanc manuellement par un autre
processus, ce qui est diffrent dun lancement implicite par une requte IPC (voir
Chapitre 31).
3. onDestroy(), qui est appele lorsque le service est teint.
Voici, par exemple, la mthode onCreate() de WeatherPlusService :
@Override
public void onCreate() {
super.onCreate();
client=new DefaultHttpClient();
format=getString(R.string.url);
myLocationManager=
(LocationManager)getSystemService(Context.LOCATION_SERVICE);
myLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
10000,
10000.0f,
onLocationChange);
singleton=this;
}
On appelle dabord la mthode onCreate() de la superclasse an quAndroid puisse
effectuer correctement son travail dinitialisation. Puis on cre notre HttpClient et la
chane de format, comme nous lavions fait dans le projet Weather. On obtient ensuite
linstance de LocationManager pour notre application et on lui demande de rcuprer
les mises jour mesure que notre emplacement volue, via LocationMana
ger.GPS MANAGER, qui sera dtaill au Chapitre 33.


La mthode onDestroy() est bien plus simple :
@Override
public void onDestroy() {
super.onDestroy();
singleton=null;
myLocationManager.removeUpdates(onLocationChange);
}
On se contente ici de stopper la surveillance des dplacements, aprs avoir appel la
mthode onDestroy() de la superclasse pour quAndroid puisse effectuer les travaux de
nettoyage ncessaires.
Outre ces mthodes du cycle de vie, votre service doit galement implmenter la mthode
onBind(), qui renvoie un IBinder, la composante fondamentale du mcanisme dIPC.
Pour les services locaux ce qui nous intresse dans ce chapitre , il suft que cette
mthode renvoie null.
Il ne peut en rester quun !
Par dfaut, les services sexcutent dans le mme processus que tous les autres compo-
sants de lapplication les activits, par exemple. On peut donc appeler les mthodes de
lAPI sur lobjet service... condition de mettre la main dessus. Dans lidal, il devrait
exister un moyen de demander Android de nous donner lobjet du service local ; malheu-
reusement, ce moyen nexiste pas encore et nous en sommes donc rduits tricher.
Il ne peut y avoir, au plus, quune seule copie dun mme service qui sexcute en
mmoire. Il peut ny en avoir aucune si le service na pas t lanc mais, mme si
plusieurs activits tentent dutiliser le service, un seul sexcutera vraiment. Il sagit donc
dune implmentation du patron de conception singleton il suft donc que lon expose le
singleton lui-mme pour que les autres composants puissent accder lobjet.
Dans le cas de WeatherPlusService, nous utilisons un membre statique public, single
ton, pour stocker ce singleton (nous pourrions videmment rendre ce membre priv et
fournir une mthode daccs). Dans onCreate(), nous initialisons singleton avec lobjet
lui-mme, tandis que, dans onDestroy(), nous le rinitialisons null. Cette dernire
tape est trs importante. Les donnes statiques sont dangereuses car elles peuvent provo-
quer des fuites mmoire : si nous oublions de remettre singleton null dans onDes
troy(), lobjet WeatherPlusService restera indniment en mmoire, bien quil soit
dconnect du reste dAndroid. Assurez-vous de toujours rinitialiser null les rfrences
statiques de vos services !
Comme nous le verrons au chapitre suivant, les activits peuvent dsormais accder aux
mthodes publiques de votre objet service grce ce singleton.


Destine du manifeste
Enn, vous devez ajouter le service votre chier AndroidManifest.xml pour quil soit
reconnu comme un service utilisable. Il suft pour cela dajouter un lment service
comme ls de llment application en utilisant lattribut android:name pour dsigner
la classe du service.
Voici, par exemple, le chier AndroidManifest.xml du projet WeatherPlus :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.service">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application android:label="@string/app_name">
<activity android:name=".WeatherPlus" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".WeatherPlusService" />
</application>
</manifest>
La classe du service tant dans le mme espace de noms que tout ce qui se trouve dans
cette application, vous pouvez utiliser la notation pointe simplie (".WeatherPlus
Service") pour dsigner votre classe.
Si vous voulez exiger certaines permissions pour lancer ou lier le service, ajoutez un attri-
but android:permission prcisant la permission demande voir Chapitre 29 pour plus
de dtails.
Sauter la clture
Parfois, le service doit prvenir de faon asynchrone une activit dun certain vnement.
La thorie derrire limplmentation de WeatherPlusService, par exemple, est que le
service est prvenu lorsque le terminal (ou lmulateur) change demplacement. Le service
appelle alors le service web et produit une nouvelle page de prvisions. Puis il doit prve-
nir lactivit que ces nouvelles prvisions sont disponibles, an quelle puisse les charger
et les afcher.
Pour interagir de cette faon avec les composants, deux possibilits sont votre dispo-
sition : les mthodes de rappel et les intentions diffuses.


Notez que, si votre service doit simplement alerter lutilisateur dun certain vnement,
vous pouvez utiliser une notication, qui est le moyen le plus classique de grer ce type de
scnario.
Mthodes de rappel
Une activit pouvant travailler directement avec un service local, elle peut fournir une
sorte dobjet "couteur" au service, que ce dernier pourra ensuite appeler au besoin. Pour
que ceci fonctionne, vous devez agir comme suit :
1. Dnissez une interface Java pour cet objet couteur.
2. Donnez au service une API publique pour enregistrer et supprimer des couteurs.
3. Faites en sorte que le service utilise ces couteurs au bon moment, pour prvenir ceux
qui ont enregistr lcouteur dun vnement donn.
4. Faites en sorte que lactivit enregistre et supprime un couteur en fonction de ses
besoins.
5. Faites en sorte que lactivit rponde correctement aux vnements grs par les couteurs.
La plus grande difcult consiste sassurer que lactivit inactive les couteurs
lorsquelle se termine. Les objets couteurs connaissent gnralement leur activit, soit
explicitement (via un membre) soit implicitement (en tant implments comme une
classe interne). Si le service repose sur des objets couteurs qui ont t inactivs, les acti-
vits correspondantes resteront en mmoire, mme si elles ne sont plus utilises par
Android. Ceci cre donc une fuite mmoire importante. Vous pouvez utiliser des WeakRe
ference, des SoftReference ou des constructions similaires pour garantir que les cou-
teurs enregistrs par une activit pour votre service ne maintiendront pas cette dernire en
mmoire lorsquelle est supprime.
Intentions diffuses
Une autre approche que nous avions dj mentionne au chapitre consacr aux ltres
dintention consiste faire en sorte que le service lance une intention diffuse qui pourra
tre capture par lactivit, en supposant que cette dernire existe encore et ne soit pas en
pause. Nous examinerons le ct client de cet change au prochain chapitre mais, pour le
moment, intressons-nous la faon dont un service peut envoyer une telle intention.
Limplmentation de haut niveau du ux est empaquete dans FetchForecastTask, une
implmentation dAsyncTask qui nous permet de dplacer laccs Internet vers un thread
en arrire-plan :
class FetchForecastTask extends AsyncTask<Location, Void, Void> {
@Override
protected Void doInBackground(Location... locs) {
Location loc=locs[0];


String url=String.format(format, loc.getLatitude(),
loc.getLongitude());
HttpGet getMethod=new HttpGet(url);
try {
ResponseHandler<String> responseHandler=new BasicResponseHandler();
String responseBody=client.execute(getMethod, responseHandler);
String page=generatePage(buildForecasts(responseBody));
synchronized(this) {
forecast=page;
}
sendBroadcast(broadcast);
}
catch (Throwable t) {
android.util.Log.e("WeatherPlus",
"Exception dans updateForecast()", t);
}
return(null);
}
@Override
protected void onProgressUpdate(Void... inutilis) {
// Inutile ici
}
@Override
protected void onPostExecute(Void inutilis) {
// Inutile ici
}
}
Lessentiel de ce code ressemble au code du projet Weather original il effectue la
requte HTTP, convertit sa rponse en un ensemble dobjets Forecast quil transforme
ensuite en page web. La premire diffrence, outre lintroduction dAsyncTask, est que la
page web est simplement mise en cache dans le service car celui-ci ne peut pas directe-
ment la placer dans le WebView de lactivit. La seconde est que lon appelle sendBroad
cast(), qui prend une intention en paramtre et lenvoie toutes les parties concernes.
Cette intention a t dclare dans le prologue de la classe :
private Intent broadcast=new Intent(BROADCAST_ACTION);
Ici, BROADCAST ACTION est simplement une chane statique dont la valeur distingue cette
intention de toutes les autres :
public static final String BROADCAST_ACTION=
"com.commonsware.android.service.ForecastUpdateEvent";
"com.commonsware.android.service.ForecastUpdateEvent";


31
Appel dun service
Les services peuvent tre utiliss par nimporte quel composant de lapplication capable
dattendre pendant un certain temps. Ceci inclut les activits, les fournisseurs de contenu
et les autres services. Par contre, ceci ne comprend pas les rcepteurs dintention purs (les
couteurs qui ne font pas partie dune activit) car ils sont automatiquement supprims
aprs le traitement dune intention.
Pour utiliser un service local, vous devez le lancer, obtenir un accs lobjet service, puis
appeler ses mthodes. Vous pouvez ensuite arrter le service lorsque vous nen avez plus
besoin ou, ventuellement, le laisser sarrter de lui-mme. Lutilisation des services
distants est un peu plus complexe et ne sera pas prsente dans ce livre.
Dans ce chapitre, nous tudierons la partie cliente de lapplication Service/Weather
Plus. Lactivit WeatherPlus ressemble normment lapplication Weather originale
comme le montre la Figure 31.1, il sagit simplement dune page web qui afche les prvisions
mtorologiques.
La diffrence est quici ces prvisions changent lorsque le terminal "se dplace", en retant
les modications fournies par le service.


Transmission manuelle
Pour dmarrer un service, il suft dappeler startService() en lui passant lintention qui
indique le service lancer (l encore, le plus simple consiste prciser la classe du service
sil sagit de votre propre service).
Inversement, on larrte par un appel la mthode stopService(), laquelle on passe
lintention fournie lappel startService() correspondant.
Lorsque le service est lanc, on doit communiquer avec lui. Cette communication peut tre
exclusivement ralise via les "extras" que lon a fournis dans lintention ou, sil sagit
dun service local qui offre un singleton, vous pouvez utiliser ce dernier.
Dans le cas de WeatherPlus et de WeatherPlusService, le client utilise le singleton du
service chaque fois quil veut obtenir de nouvelles prvisions :
private void updateForecast() {
try {
String page=WeatherPlusService
.singleton
.getForecastPage();
browser.loadDataWithBaseURL(null, page, "text/html",
"UTF-8", null);
}
catch (final Throwable t) {
goBlooey(t);
}
}
Figure 31.1
Le client du service
WeatherPlus.


Une partie pineuse de ce code consiste sassurer que le singleton est bien l quand on a
besoin de lui. Lappel startService() tant asynchrone, vous reprenez le contrle tout
de suite, or le service dmarrera bientt, mais pas immdiatement. Dans le cas de
WeatherPlus, on peut sen contenter car on nessaie pas dutiliser le singleton tant que
le service ne nous a pas prvenus de la disponibilit dune prvision, via une intention
de diffusion. Cependant, dans dautres situations, il faudra peut-tre appeler la mthode
postDelayed() dun Handler an de reporter dune seconde ou deux lutilisation du
service, en esprant que le singleton sera devenu disponible entre-temps.
Capture de lintention
Au chapitre prcdent, nous avons vu comment le service diffusait une intention pour
signaler lactivit WeatherPlus quun mouvement du terminal avait provoqu une modi-
cation des prvisions. Nous pouvons maintenant tudier la faon dont lactivit reoit et
utilise cette intention.
Voici les implmentations donResume() et donPause() de WeatherPlus :
@Override
public void onResume() {
super.onResume();
registerReceiver(receiver,
new IntentFilter(WeatherPlusService.BROADCAST_ACTION));
}
@Override
public void onPause() {
super.onPause();
unregisterReceiver(receiver);
}
Dans onResume(), nous enregistrons un BroadcastReceiver statique pour recevoir les
intentions qui correspondent laction dclare par le service. Dans onPause(), nous
dsactivons ce rcepteur car nous ne recevrons plus ces intentions pendant que nous sommes
en pause.
Le BroadcastReceiver, de son ct, met simplement les prvisions jour :
private BroadcastReceiver receiver=new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
updateForecast();
}
};


32
Alerter les utilisateurs
avec des notications
Les messages qui surgissent, les icnes et les "bulles" qui leur sont associes, les icnes
qui bondissent dans la barre dtat, etc. sont utiliss par les programmes pour attirer votre
attention, et parfois pour de bonnes raisons.
Votre tlphone vous alerte aussi probablement pour dautres motifs que la rception dun
appel : batterie faible, alarme programme, notications de rendez-vous, rception de
SMS ou de courrier lectronique, etc.
Il nest donc pas tonnant quAndroid dispose dun framework complet pour grer ce
genre dvnements, dsigns sous le terme de notications.
Types davertissements
Un service qui sexcute en arrire-plan doit pouvoir attirer lattention des utilisateurs
lorsquun vnement survient la rception dun courrier, par exemple. En outre, le
service doit galement diriger lutilisateur vers une activit lui permettant dagir en
rponse cet vnement lire le message reu, par exemple. Pour ce type daction,


Android fournit des icnes dans la barre dtat, des avertissements lumineux et dautres
indicateurs que lon dsigne globalement par le terme de notications.
Votre tlphone actuel peut possder ce type dicne pour indiquer le niveau de charge de
la batterie, la force du signal, lactivation de Bluetooth, etc. Avec Android, les applications
peuvent ajouter leurs propres icnes dans la barre dtat et faire en sorte quelles nappa-
raissent que lorsque cela est ncessaire (lorsquun message est arriv, par exemple).
Vous pouvez lancer des notications via le NotificationManager, qui est un service du
systme. Pour lutiliser, vous devez obtenir lobjet service via un appel la mthode
getSystemService (NOTIFICATION SERVICE) de votre activit. Le NotificationManager
vous offre trois mthodes : une pour avertir (notify()) et deux pour arrter davertir
(cancel() et cancelAll()).
La mthode notify() prend en paramtre une Notification, qui est une structure dcri-
vant la forme que doit prendre lavertissement. Les sections qui suivent dcrivent tous les
champs publics mis votre disposition (mais souvenez-vous que tous les terminaux ne
les supportent pas ncessairement tous).
Notications matrielles
Vous pouvez faire clignoter les LED du terminal en mettant lights true et en prcisant
leur couleur (sous la forme dune valeur #ARGB dans ledARGB). Vous pouvez galement
prciser le type de clignotement (en indiquant les dures dallumage et dextinction en
millisecondes dans ledOnMS et ledOffMS).
Vous pouvez faire retentir un son en indiquant une Uri vers un contenu situ, par exemple,
dans un ContentManager (sound). Considrez ce son comme une sonnerie pour votre
application.
Enn, vous pouvez faire vibrer le terminal en indiquant les alternances de la vibration
(vibrate) en millisecondes dans un tableau de valeurs long. Cette vibration peut tre le
comportement par dfaut ou vous pouvez laisser le choix lutilisateur, au cas o il aurait
besoin dun avertissement plus discret quune sonnerie.
Icnes
Alors que les lumires, les sons et les vibrations sont destins faire en sorte que lutilisa-
teur regarde son terminal, les icnes constituent ltape suivante consistant signaler ce
qui est si important.
Pour congurer une icne pour une Notification, vous devez initialiser deux champs
publics : icon, qui doit fournir lidentiant dune ressource Drawable reprsentant licne
voulue, et contentIntent, qui indique le PendingIntent qui devra tre dclench
lorsquon clique sur licne. Vous devez vous assurer que le PendingIntent sera captur


ventuellement par le code de votre application pour prendre les mesures ncessaires
an que lutilisateur puisse grer lvnement qui a dclench la notication.
Vous pouvez galement fournir un texte qui apparatra lorsque licne est place sur la
barre dtat (tickerText).
Pour congurer ces trois composantes, lapproche la plus simple consiste appeler la
mthode setLatestEventInfo(), qui enveloppe leurs initialisations dans un seul appel.
Les avertissements en action
Examinons le projet Notifications/Notify1, notamment sa classe NotifyDemo :
public class NotifyDemo extends Activity {
private static final int NOTIFY_ME_ID=1337;
private Timer timer=new Timer();
private int count=0;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Button btn=(Button)findViewById(R.id.notify);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
TimerTask task=new TimerTask() {
public void run() {
notifyMe();
}
};

timer.schedule(task, 5000);
}
});

btn=(Button)findViewById(R.id.cancel);

btn.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
NotificationManager mgr=
(NotificationManager)getSystemService(NOTIFICATION_SERVICE);

mgr.cancel(NOTIFY_ME_ID);
}


});
}

private void notifyMe() {
final NotificationManager mgr=
(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
Notification note=new Notification(R.drawable.red_ball,
"Message detat!",
System.currentTimeMillis());
PendingIntent i=PendingIntent.getActivity(this, 0,
new Intent(this, NotifyMessage.class),
0);

note.setLatestEventInfo(this, "Titre de la notification",
"Message de notification", i);
note.number=++count;

mgr.notify(NOTIFY_ME_ID, note);
}
}
Cette activit fournit deux gros boutons, un pour lancer une notication aprs un dlai de
5 secondes, lautre pour annuler cette notication si elle est active. La Figure 32.1 montre
laspect de son interface lorsquelle vient dtre lance.
Figure 32.1
La vue principale de
NotifyDemo.


La cration de la notication dans notifyMe() se droule en cinq tapes :
1. Obtenir laccs linstance de NotificationManager.
2. Crer un objet Notification avec une icne (une boule rouge), un message qui
clignote sur la barre dtat lorsque la notication est lance et le temps associ cet
vnement.
3. Crer un PendingIntent qui dclenchera lafchage dune autre activit (Notify
Message).
4. Utiliser setLatestEventInfo() pour prciser que, lorsquon clique sur la notica-
tion, on afche un titre et un message et que, lorsquon clique sur ce dernier, on lance
le PendingIntent.
5. Demander au NotificationManager dafcher la notication.
Par consquent, si lon clique sur le bouton du haut, la boule rouge apparatra dans la barre
de menu aprs un dlai de 5 secondes, accompagne brivement du message dtat,
comme le montre la Figure 32.2. Aprs la disparition du message, un chiffre safchera
sur la boule rouge (initialement 1) qui pourrait par exemple indiquer le nombre de
messages non lus.
En cliquant sur la boule rouge, un tiroir apparatra sous la barre dtat. Louverture de
ce tiroir montrera les notications en suspens, dont la ntre, comme le montre la
Figure 32.3.
Figure 32.2
Notre notication appa-
rat dans la barre dtat,
avec notre message dtat.


En cliquant sur lentre de la notication dans la barre de notication, on dclenche une
activit trs simple se bornant afcher un message dans une vraie application, cette
activit raliserait un traitement tenant compte de lvnement qui est survenu (prsenter
lutilisateur les nouveaux messages de courrier, par exemple).
Si lon clique sur le bouton dannulation ou sur le bouton "Effacer les notications" du
tiroir, la balle rouge disparat de la barre dtat.
Figure 32.3
Le tiroir des notications
compltement ouvert,
contenant notre notication.


Partie VI
Autres fonctionnalits
dAndroid
CHAPITRE 33. Accs aux services de localisation
CHAPITRE 34. Cartographie avec MapView et MapActivity
CHAPITRE 35. Gestion des appels tlphoniques
CHAPITRE 36. Recherches avec SearchManager
CHAPITRE 37. Outils de dveloppement
CHAPITRE 38. Pour aller plus loin


33
Accs aux services
de localisation
Le GPS est une fonctionnalit trs apprcie des terminaux mobiles actuels car il permet
de vous indiquer votre emplacement gographique tout moment. Bien que lutilisation la
plus frquente du GPS soit la cartographie et lorientation, connatre votre emplacement
vous ouvre de nombreux autres horizons. Vous pouvez, par exemple, mettre en place une
application de chat dynamique o vos contacts sont classs selon leurs emplacements
gographiques, an de choisir ceux qui sont les plus prs de vous. Vous pouvez galement
"gotaguer" automatiquement les articles que vous postez sur Twitter ou dautres services
similaires.
Cependant, le GPS nest pas le seul moyen didentier un emplacement gographique :
Lquivalent europen de GPS, Galileo, est encore en cours de mise au point.
La triangulation permet de dterminer votre position en fonction de la force du signal
des antennes relais proches de vous.
La proximit des "hotspots" Wi, dont les positions gographiques sont connues.


Les terminaux Android peuvent utiliser un ou plusieurs de ces services. En tant que
dveloppeur, vous pouvez demander au terminal de vous indiquer votre emplacement,
ainsi que des dtails sur les fournisseurs disponibles. Vous pouvez mme simuler votre
localisation avec lmulateur pour tester les applications qui ont besoin de cette fonction-
nalit.
Fournisseurs de localisation : ils savent o vous vous
cachez
Les terminaux Android peuvent utiliser plusieurs moyens pour dterminer votre emplace-
ment gographique. Certains ont une meilleure prcision que dautres ; certains sont
gratuits, tandis que dautres sont payants ; certains peuvent vous donner des informations
supplmentaires, comme votre altitude par rapport au niveau de la mer ou votre vitesse
courante.
Android a donc abstrait tout cela en un ensemble dobjets LocationProvider. Votre envi-
ronnement utilisera zro ou plusieurs instances de LocationProvider, une par service de
localisation disponible sur le terminal. Ces fournisseurs ne connaissent pas seulement
votre emplacement mais possdent galement leurs propres caractristiques prcision,
prix, etc.
Vous aurez donc besoin dun LocationManager contenant lensemble des LocationPro
vider pour savoir quel est le LocationProvider qui convient votre cas particulier. Votre
application devra galement disposer de la permission ACCESS LOCATION ; sinon les diff-
rentes API de localisation choueront cause dune violation de scurit. Selon les four-
nisseurs de localisation que vous voulez utiliser, vous pourrez galement avoir besoin
dautres permissions, comme ACCESS COARSE LOCATION ou ACCESS FINE LOCATION.
Se trouver soi-mme
Lopration la plus vidente dun fournisseur de localisation consiste trouver votre
emplacement actuel. Pour ce faire, vous avez besoin dun LocationManager, que vous
obtiendrez par un appel getSystemService(LOCATION SERVICE) partir de votre acti-
vit ou service, en transtypant le rsultat pour obtenir un LocationManager.
Ltape suivante consiste obtenir le nom du LocationProvider que vous voulez utiliser.
Pour ce faire, deux possibilits soffrent vous :
demander lutilisateur de choisir un fournisseur ;
trouver le fournisseur qui convient le mieux en fonction dun ensemble de critres.


Si vous choisissez la premire approche, un appel la mthode getProviders() du
LocationManager vous donnera une liste de fournisseurs que vous pouvez prsenter
lutilisateur pour quil fasse son choix.
Vous pouvez galement crer et initialiser un objet Criteria, en prcisant ce que vous
attendez dun LocationProvider. Par exemple :
setAltitudeRequired() pour indiquer si vous avez besoin ou non de connatre votre
altitude ;
setAccuracy() pour xer un niveau de prcision minimal de la position, en mtres ;
setCostAllowed() pour indiquer si le fournisseur doit tre gratuit ou non (cest--dire
sil peut impliquer un paiement de la part de lutilisateur du terminal).
Lorsque lobjet Criteria a t rempli, appelez la mthode getBestProvider() de votre
LocationManager et Android passera les critres en revue pour vous donner la meilleure
rponse. Tous ces critres peuvent ne pas tre vris part celui concernant le prix, ils
peuvent tous tre ignors si rien ne correspond.
Pour effectuer des tests, vous pouvez galement indiquer directement dans votre code le
nom dun LocationProvider (gps, par exemple).
Lorsque vous connaissez le nom du LocationProvider, vous pouvez appeler getLast
KnownPosition() pour trouver votre dernire position. Notez, cependant, que cette
"dernire position" peut tre obsolte (si, par exemple, le tlphone tait teint) ou valoir
null si aucune position na encore t enregistre pour ce fournisseur. En revanche,
getLastKnownPosition() est gratuite et ne consomme pas de ressource car le fournisseur
na pas besoin dtre activ pour connatre cette valeur.
Ces mthodes renvoient un objet Location qui vous indiquera la latitude et la longitude
du terminal en degrs des valeurs double de Java. Si le fournisseur donne dautres infor-
mations, vous pouvez les rcuprer laide des mthodes suivantes :
hasAltitude() indique sil y a une valeur pour laltitude et getAltitude() renvoie
laltitude en mtres.
hasBearing() indique sil y a une information dorientation (une valeur de compas) et
getBearing() renvoie cette valeur en degrs par rapport au vrai nord.
hasSpeed() indique si la vitesse est connue et getSpeed() la renvoie en mtres par
seconde.
Ceci dit, une approche plus frquente pour obtenir lobjet Location partir dun Loca
tionProvider consiste senregistrer pour les modications de la position du terminal,
comme expliqu dans la section suivante.


Se dplacer
Tous les fournisseurs de localisation ne rpondent pas immdiatement. GPS, par exemple,
ncessite lactivation dun signal et la rception des satellites (cest ce que lon appelle un
"x GPS") avant de pouvoir connatre sa position. Cest la raison pour laquelle Android ne
fournit pas de mthode getMeMyCurrentLocationNow(). Ceci combin avec le fait que
les utilisateurs puissent vouloir que leurs mouvements soient pris en compte dans lappli-
cation, vous comprendrez pourquoi il est prfrable denregistrer les modications de la
position et les utiliser pour connatre la position courante.
Les applications Weather et WeatherPlus montrent comment enregistrer ces mises jour
en appelant la mthode requestLocationUpdates() de lobjet LocationManager.
Cette mthode prend quatre paramtres :
1. Le nom du fournisseur de localisation que vous souhaitez utiliser.
2. Le temps, en millisecondes, qui doit scouler avant que lon puisse obtenir une mise
jour de la position.
3. Le dplacement minimal du terminal en mtres pour que lon puisse obtenir une mise
jour de la position.
4. Un LocationListener qui sera prvenu des vnements lis la localisation, comme
le montre le code suivant :
LocationListener onLocationChange=new LocationListener() {
public void onLocationChanged(Location location) {
updateForecast(location);
}
public void onProviderDisabled(String provider) {
// Exige par linterface, mais inutilise
}
public void onProviderEnabled(String provider) {
// Exige par linterface, mais inutilise
}
public void onStatusChanged(String provider, int status,
Bundle extras) {
// Exige par linterface, mais inutilise
}
};
Ici, nous appelons simplement updateForecast() en lui passant lobjet Location fourni
lappel de la mthode de rappel onLocationChanged(). Comme on la vu au Chapi-
tre 30, limplmentation dupdateForecast() construit une page web contenant les
prvisions mtorologiques pour lemplacement courant et envoie un message de diffusion
an que lactivit sache quune mise jour est disponible.


Lorsque lon na plus besoin des mises jour, on appelle removeUpdates() avec le
LocationListener que lon avait enregistr.
Est-on dj arriv ? Est-on dj arriv ?
Est-on dj arriv ?
Parfois, on veut savoir non pas o lon se trouve ni mme o lon va, mais si lon est l o
lon voulait aller. Il pourrait sagir dune destination nale ou dune tape dans un ensemble
de directions pour pouvoir indiquer le virage suivant, par exemple.
Dans ce but, LocationManager fournit la mthode addProximityAlert(), qui enregistre
un PendingIntent qui sera dclench lorsque le terminal se trouvera une certaine
distance dun emplacement donn. La mthode addProximityAlert() attend les paramtres
suivants :
La latitude et la longitude de la position qui nous intresse.
Un rayon prcisant la proximit avec la position pour que lintention soit leve.
Une dure denregistrement en millisecondes passe cette priode, lenregistrement
expirera automatiquement. Une valeur de 1 indique que lenregistrement sera main-
tenu jusqu ce que vous le supprimiez manuellement via un appel removeProximity
Alert().
Le PendingIntent quil faudra lever lorsque le terminal se trouve dans la "zone de tir"
dnie par la position et le rayon.
Notez quil nest pas garanti que vous receviez une intention sil y a eu une interruption
dans les services de localisation ou si le terminal nest pas dans la zone cible pendant le
temps o lalerte de proximit est active. Si la position, par exemple, est trop proche du but
et que le rayon est un peu trop rduit, le terminal peut ne faire que longer le bord de la
zone cible ou y passer si rapidement que sa position ne sera pas enregistre pendant quil
est dans la zone.
Il vous appartient de faire en sorte quune activit ou un rcepteur dintention rponde
lintention que vous avez enregistre pour lalerte de proximit. Cest galement vous de
dterminer ce qui doit se passer lorsque cette intention arrive : congurer une notication
(faire vibrer le terminal, par exemple), enregistrer linformation dans un fournisseur de
contenu, poster un message sur un site web, etc. Notez que vous recevrez lintention
chaque fois que la position est enregistre et que vous tes dans la zone cible pas
simplement lorsque vous y entrez. Par consquent, vous la recevrez plusieurs fois le
nombre doccurrences dpend de la taille de la zone et de la vitesse de dplacement du
terminal.


Tester... Tester...
Lmulateur dAndroid ne permet pas dobtenir un "x GPS", de trianguler votre position
partir des antennes relais ni de dduire votre position partir des signaux Wi voisins. Si
vous voulez simuler un terminal qui se dplace, il faut donc trouver un moyen de fournir
lmulateur des donnes de localisation simules.
Pour une raison inconnue, ce domaine a subi des changements importants au cours de
lvolution dAndroid. une poque, il tait possible de fournir des donnes de localisa-
tion simules une application, ce qui tait trs pratique pour les tests et les dmonstrations
mais, malheureusement, cette possibilit a disparu partir dAndroid 1.0.
Ceci dit, DDMS (Dalvik Debug Monitor Service) permet de fournir ce type de donnes.
Il sagit dun programme externe, spar de lmulateur, qui peut fournir ce dernier des
points demplacements ou des routes compltes, dans diffrents formats. DDMS est dcrit
en dtail au Chapitre 37.


34
Cartographie avec
MapView et MapActivity
Google Maps est lun des services les plus connus de Google aprs le moteur de recher-
che, bien entendu. Avec lui, vous pouvez tout trouver, de la pizzeria la plus proche au trajet
menant de Toulouse Paris en passant par les vues dtailles des rues (Street View) et les
images satellites.
Android intgre Google Maps : cette activit de cartographie est directement disponible
partir du menu principal mais, surtout, les dveloppeurs ont leur disposition les classes
MapView et MapActivity pour intgrer des cartes gographiques dans leurs applications.
Grce elles, ils peuvent non seulement contrler le niveau du zoom, permettre aux utili-
sateurs de faire dler la carte, mais galement utiliser les services de localisation pour
marquer lemplacement du terminal et indiquer son dplacement.
Heureusement, cette intgration est assez simple et vous pouvez exploiter toute sa puis-
sance si vous le souhaitez.


Termes dutilisation
Google Maps, notamment lorsquil est intgr dans des applications tierces, ncessite le
respect dun assez grand nombre de termes juridiques. Parmi ceux-ci se trouvent des clau-
ses que vous trouverez peut-tre insupportables. Si vous dcidez dutiliser Google Maps,
prenez soin de bien lire tous ces termes an dtre sr que lutilisation que vous comptez
en faire ne les viole pas. Nous vous conseillons fortement de demander lavis dun
conseiller juridique en cas de doute.
En outre, ne dlaissez pas les autres possibilits de cartographie qui reposent sur dautres
sources de donnes gographiques, comme OpenStreetMap
1
.
Empilements
partir dAndroid 1.5, Google Maps ne fait plus partie du SDK proprement parler mais
a t dplac dans les API supplmentaires de Google, qui sont des extensions au SDK de
base. Ce systme dextension fournit des points dentre aux autres sous-systmes qui
peuvent se trouver sur certains terminaux mais pas sur dautres.
En ralit, Google Maps ne fait pas partie du projet open-source Android, et il existera
ncessairement des terminaux qui nen disposeront pas cause des problmes de licence.
Dans lensemble, le fait que Google Maps soit une extension naffectera pas votre dve-
loppement habituel condition de ne pas oublier les points suivants :
Vous devrez crer votre projet pour quil utilise la cible 3 ( t 3), an dtre sr que les
API de Google Maps sont disponibles.
Pour tester lintgration de Google Maps, vous aurez galement besoin dun AVD qui
utilise la cible 3 ( t 3).
Inversement, pour tester votre application dans un environnement Android 1.5 sans
Google Maps, vous devrez crer un AVD qui utilise la cible 2 ( t 2).
Les composants essentiels
Pour insrer une carte gographique dans une application, le plus simple consiste crer
une sous-classe de MapActivity. Comme ListActivity, qui enveloppe une partie des
dtails cachs derrire une activit domine par une ListView, MapActivity gre une
partie de la conguration dune activit domine par une MapView.
1. http://www.openstreetmap.org/.


Dans le chier de layout de la sous-classe de MapActivity, vous devez ajouter un lment
qui, actuellement, sappelle com.google.android.maps.MapView. Il sagit ici dun nom
totalement dvelopp qui ajoute le nom de paquetage complet au nom de la classe (cette
notation est ncessaire car MapView ne se trouve pas dans lespace de noms
com.google.android.widget). Vous pouvez donner la valeur que vous souhaitez
lattribut android:id du widget MapView et grer tous les dtails lui permettant de safcher
correctement ct des autres widgets.
Vous devez cependant prciser les attributs suivants :
android:apiKey. Dans une version de lapplication en production, cet attribut doit
contenir une cl de lAPI Google Maps (voir plus loin).
android:clickable = "true". Si vous voulez que les utilisateurs puissent cliquer
sur la carte et la faire dler.
Voici, par exemple, le contenu du chier layout principal de lapplication Maps/NooYawk :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.google.android.maps.MapView android:id="@+id/map"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="<VOTRE_CL_API>"
android:clickable="true" />
<LinearLayout android:id="@+id/zoom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true" />
</RelativeLayout>
Nous prsenterons ces mystrieux LinearLayout zoom et apiKey plus bas dans ce chapitre.
Vous devez galement ajouter deux informations supplmentaires votre chier Android
Manifest.xml :
Les permissions INTERNET et ACCESS COARSE LOCATION.
Dans llment <application>, ajoutez un lment <uses library> avec lattribut
android:name = "com.google.android.maps" pour indiquer que vous utilisez lune
des API facultatives dAndroid.
Voici le chier AndroidManifest.xml du projet NooYawk:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.maps">
<uses-permission android:name="android.permission.INTERNET" />


<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<application android:label="@string/app_name">
<uses-library android:name="com.google.android.maps" />
<activity android:name=".NooYawk" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Avec la sous-classe de MapActivity, cest peu prs tout ce dont vous avez besoin pour
dbuter. Si vous ne faites rien dautre, que vous compiliez ce projet et que vous linstalliez
dans lmulateur, vous obtiendrez une belle carte du monde. Notez, cependant, que
MapActivity est une classe abstraite et que vous devez donc implmenter la mthode
isRouteDisplayed() pour prciser si vous fournissez ou non une gestion des itinraires.
En thorie, lutilisateur doit pouvoir faire dler la carte en utilisant le pad directionnel.
Cependant, ce nest pas trs pratique lorsque lon a le monde entier dans sa main...
Une carte du monde ntant pas trs utile en elle-mme, nous devons lui ajouter quelques
fonctionnalits.
Testez votre contrle
Pour trouver votre widget MapView, il suft, comme dhabitude, dappeler la mthode
findViewById(). Le widget lui-mme fournit la mthode getMapController(). Entre le
MapView et le MapController, vous disposez dun bon nombre de possibilits pour dter-
miner ce quafche la carte et la faon dont elle se comporte ; les sections suivantes
prsentent le zoom et le centrage, qui sont srement celles que vous utiliserez le plus.
Zoom
La carte du monde avec laquelle vous dmarrez est plutt vaste. Sur un tlphone, on
prfre gnralement consulter une carte ayant une tendue plus rduite quelques pts
de maisons, par exemple.
Vous pouvez contrler directement le niveau du zoom grce la mthode setZoom() de
MapController. Celle-ci attend un paramtre entier reprsentant le niveau du zoom, o 1
reprsente la vue du monde entier et 21, le plus fort grossissement que vous pouvez obte-
nir. Chaque niveau double la rsolution effective par rapport au niveau prcdent : au
niveau 1, lquateur fait 256 pixels de large et il en fait 268 435 456 au niveau 21. Lcran
du tlphone nayant srement pas autant de pixels ; lutilisateur ne verra donc quune
petite partie de la carte centre sur un endroit du globe. Le niveau 16 montrera plusieurs


pts de maisons dans chaque dimension et constitue gnralement un bon point de dpart
pour vos essais.
Si vous souhaitez que les utilisateurs aient le droit de changer le niveau du zoom, utilisez
lappel setBuiltInZoomControls(true) : il pourra alors utiliser les contrles de zoom
qui se trouvent en bas de la carte.
Centrage
Gnralement, quel que soit le niveau du zoom, vous voudrez contrler ce qui est afch
sur la carte : la position courante de lutilisateur ou un emplacement sauvegard avec
dautres donnes de votre activit, par exemple. Pour changer la position de la carte, appelez
la mthode setCenter() de MapController.
Cette mthode prend un objet GeoPoint en paramtre. Un GeoPoint reprsente un empla-
cement exprim par une latitude et une longitude. En ralit, il les stocke sous la forme
dentiers en multipliant leurs vraies valeurs par 1E6, ce qui permet dconomiser un peu de
mmoire par rapport des float ou des double et dacclrer un peu la conversion dun
GeoPoint en position sur la carte. En revanche, vous ne devez pas oublier ce facteur de
1E6.
Terrain accident
Tout comme sur votre ordinateur de bureau, vous pouvez afcher les images satellites avec
Google Maps et Android.
MapView offre la mthode toggleSatellite(), qui, comme son nom lindique, permet
dactiver ou de dsactiver la vue satellite de la surface prsente sur la carte. Vous pouvez
faire en sorte de laisser lutilisateur le soin de faire ce choix partir dun menu ou,
comme dans NooYawk, via des touches :
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_S) {
map.setSatellite(!map.isSatellite());
return(true);
}
else if (keyCode == KeyEvent.KEYCODE_Z) {
map.displayZoomControls(true);
return(true);
}

return(super.onKeyDown(keyCode, event));
}


Couches sur couches
Si vous avez dj utilis la version complte de Google Maps, vous avez srement dj vu
que lon pouvait dposer des choses sur la carte elle-mme : les "repres", par exemple,
qui indiquent les emplacements des points dintrt proches de la position que vous avez
demande. En termes de carte et galement pour la plupart des diteurs graphiques
srieux , ces repres sont placs sur une couche distincte de celle de la carte elle-mme et
ce que vous voyez au nal est la superposition de ces deux couches.
Android permet de crer de telles couches, an de marquer les cartes en fonction des choix
de lutilisateur et des besoins de votre application. NooYawk, par exemple, utilise une
couche pour montrer les emplacements des immeubles slectionns dans Manhattan.
Classes Overlay
Toute couche ajoute votre carte doit tre implmente comme une sous-classe dOverlay.
Si vous voulez simplement ajouter des repres, vous pouvez utiliser la sous-classe Itemized
Overlay, qui vous simpliera la tche.
Pour attacher une couche votre carte, il suft dappeler la mthode getOverlays() de
votre objet MapView et dajouter votre instance dOverlay avec add() :
marker.setBounds(0, 0, marker.getIntrinsicWidth(),
marker.getIntrinsicHeight());
map.getOverlays().add(new SitesOverlay(marker));
Nous expliquerons un peu plus loin le rle de marker.
Afchage dItemizedOverlay
Comme son nom lindique, ItemizedOverlay permet de fournir une liste de points dint-
rt (des instances dOverlayItem) pour les afcher sur la carte. La couche gre ensuite
lessentiel du dessin pour vous, mais vous devez toutefois effectuer les oprations
suivantes :
Drivez votre sous-classe (SitesOverlay, dans notre exemple) dItemizedOver
lay<OverlayItem>.
Dans le constructeur, mettez en place la liste des instances OverlayItem et appelez
populate() lorsquelles sont prtes tre utilises par la couche.
Implmentez size() pour quelle renvoie le nombre dlments qui devront tre grs
par la couche.
Rednissez createItem() pour quelle renvoie linstance OverlayItem correspondant
lindice qui lui est pass en paramtre.


Lors de linstanciation de la sous-classe dItemizedOverlay, fournissez-lui un objet
Drawable reprsentant licne par dfaut de chaque lment (une pinglette, par exemple).
Le marker que lon passe au constructeur de NooYawk est le Drawable utilis en dernier
recours il afche une pinglette.
Vous pouvez galement rednir draw() pour mieux grer lombre de vos marqueurs.
Bien que la carte fournisse une ombre, il peut tre utile de laider un peu en lui indiquant
o se trouve le bas de licne, an quelle puisse en tenir compte pour lombrage.
Voici, par exemple, le code de la classe SitesOverlay :
private class SitesOverlay extends ItemizedOverlay<OverlayItem> {
private List<OverlayItem> items=new ArrayList<OverlayItem>();
private Drawable marker=null;
public SitesOverlay(Drawable marker) {
super(marker);
this.marker=marker;
items.add(new OverlayItem(getPoint(40.748963847316034,
-73.96807193756104),
"UN", "Nations Unies"));
items.add(new OverlayItem(getPoint(40.76866299974387,
-73.98268461227417),
"Lincoln Center",
"La maison du Jazz"));
items.add(new OverlayItem(getPoint(40.765136435316755,
-73.97989511489868),
"Carnegie Hall",
"Entranezvous avant dy jouer !"));
items.add(new OverlayItem(getPoint(40.70686417491799,
-74.01572942733765),
"The Downtown Club",
"Le lieu dorigine du trophe Heisman"));
populate();
}
@Override
protected OverlayItem createItem(int i) {
return(items.get(i));
}
@Override
public void draw(Canvas canvas, MapView mapView,
boolean shadow) {
super.draw(canvas, mapView, shadow);
boundCenterBottom(marker);
}
@Override
protected boolean onTap(int i) {
Toast.makeText(NooYawk.this,
items.get(i).getSnippet(),


Toast.LENGTH_SHORT).show();
return(true);
}
@Override
public int size() {
return(items.size());
}
}
Gestion de lcran tactile
Une sous-classe dOverlay peut galement implmenter onTap()pour tre prvenue lors-
que lutilisateur touche la carte an que la couche ajuste ce quelle afche. Dans Google
Maps, cliquer sur une pinglette fait surgir une bulle dinformation consacre lemplace-
ment marqu, par exemple : grce onTap(), vous pouvez obtenir le mme rsultat avec
Android.
La mthode onTap() dItemizedOverlay prend en paramtre lindice de lobjet Over
layItem sur lequel on a cliqu. Cest ensuite vous de traiter cet vnement.
Dans le cas de la classe SitesOverlay que nous venons de prsenter, le code donTap()
est le suivant :
@Override
protected boolean onTap(int i) {
Toast.makeText(NooYawk.this,
items.get(i).getSnippet(),
Toast.LENGTH_SHORT).show();
return(true);
}
Ici, on lve simplement un Toast contenant le texte associ lOverlayItem et lon
renvoie true pour indiquer que lon a gr le toucher de cet objet.
Moi et MyLocationOverlay
Android dispose dune couche intgre permettant de grer deux scnarios classiques :
lafchage de votre position sur la carte, en fonction du GPS ou dun autre fournisseur
de localisation ;
lafchage de la direction vers laquelle vous vous dirigez, en fonction de la boussole
intgre lorsquelle est disponible.
Il vous suft pour cela de crer une instance de MyLocationOverlay, de lajouter la liste
des couches de votre MapView et dactiver et de dsactiver ces fonctionnalits aux
moments opportuns.


La notion de "moments opportuns" est lie lconomie de la batterie. Comme il ny a
aucune raison de mettre jour des emplacements ou des directions lorsque lactivit est en
pause, il est conseill dactiver ces fonctionnalits dans onResume() et de les dsactiver
dans onPause().
Pour que NooYawk afche une boussole dans MyLocationOverlay, par exemple, nous
devons dabord crer la couche et lajouter la liste des couches :
me=new MyLocationOverlay(this, map);
map.getOverlays().add(me);
Puis nous activons et dsactivons cette boussole lorsque cela est ncessaire :
@Override
public void onResume() {
super.onResume();
me.enableCompass();
}
@Override
public void onPause() {
super.onPause();
me.disableCompass();
}
La cl de tout
Si vous compilez le projet NooYawk et que vous linstalliez dans votre mulateur, vous
verrez srement un cran montrant une grille et deux pinglettes, mais pas de carte.
La raison en est que la cl de lAPI dans le code source nest pas valide pour votre
machine de dveloppement. Vous devez donc produire votre propre cl pour lutiliser avec
votre application.
Le site web dAndroid
1
donne toutes les instructions ncessaires pour produire ces cls,
que ce soit pour le dveloppement ou pour la production. Pour rester brefs, nous nous int-
resserons ici au cas particulier de lexcution de NooYawk dans votre mulateur. Vous
devez effectuer les tapes suivantes :
1. Allez sur la page dinscription pour la cl de lAPI et lisez les termes dutilisation.
2. Relisez ces termes et soyez absolument sr que vous les approuvez.
3. Recherchez la signature MD5 du certicat utilis pour signer vos applications en
mode debug (voir ci-aprs).
1. http://code.google.com/android/toolbox/apis/mapkey.html.


4. Sur la page dinscription pour la cl de lAPI, collez cette signature MD5 et envoyez le
formulaire.
5. Sur la page de rponse, copiez la cl de lAPI et collez-la dans la valeur de lattribut
android:apiKey du layout de votre MapView.
La partie la plus complique consiste trouver la signature MD5 du certicat utilis pour
signer vos applications en mode debug... et une bonne partie de cette complexit consiste
comprendre le concept.
Toutes les applications Android sont signes laide dune signature numrique produite
partir dun certicat. Vous recevez automatiquement un certicat de dbogage lorsque
vous installez le SDK et il faut suivre un autre processus pour crer un certicat autosign
utilisable avec vos applications en production. Ce processus ncessite dutiliser les outils
keytool et jarsigner de Java. Pour obtenir votre cl dAPI, vous navez besoin que de
keytool.
Si vous utilisez OS X ou Linux, faites la commande suivante pour obtenir la signa-
ture MD5 de votre certicat de dbogage :
keytool -list -alias androiddebugkey -keystore ~/.android/debug.keystore
-storepass android -keypass android
Sur les autres plates-formes de dveloppement, vous devrez remplacer la valeur de keystore
par lemplacement sur votre machine et votre compte utilisateur :
Windows XP : C:\Documents et Settings\<utilisateur>\Local
Settings\ApplicationData\Android\debug.keystore.
Windows Vista : C:\Users\<utilisateur>\AppData\Local\Android\debug.key
store (o <utilisateur> est le nom de votre compte).
La seconde ligne du rsultat qui safche contient votre signature MD5, qui est une suite
de paires de chiffres hexadcimaux spares par des caractres deux-points.


35
Gestion des appels
tlphoniques
La plupart des terminaux Android, si ce nest tous, sont des tlphones. Leurs utilisateurs
sattendent donc pouvoir tlphoner et recevoir des appels et, si vous le souhaitez, vous
pouvez les y aider :
Vous pourriez crer une interface pour une application de gestion des ventes ( la
Salesforce.com) en offrant la possibilit dappeler les vendeurs dun simple clic, sans
que lutilisateur soit oblig de mmoriser ces contacts la fois dans lapplication et
dans son rpertoire tlphonique.
Vous pourriez dvelopper une application de rseau social avec une liste de numros
de tlphone qui volue constamment : au lieu de "synchroniser" ces contacts avec
ceux du tlphone, lutilisateur pourrait les appeler directement partir de cette appli-
cation.
Vous pourriez crer une interface personnalise pour le systme de contacts existants,
ventuellement pour que les utilisateurs mobilit rduite (telles les personnes ges)
puissent disposer de gros boutons pour faciliter la composition des appels.
Quoi quil en soit, Android vous permet de manipuler le tlphone comme nimporte
quelle autre composante du systme.


Le Manager
Pour tirer le meilleur parti de lAPI de tlphonie, utilisez la classe TelephonyManager,
qui permet notamment de :
dterminer si le tlphone est en cours dutilisation, via sa mthode getCallState(),
qui renvoie les valeurs CALL STATE IDLE (tlphone non utilis), CALL STATE RINGING
(appel en cours de connexion) et CALL STATE OFFHOOK (appel en cours) ;
trouver lidentiant de la carte SIM avec getSubscriberId();
connatre le type du tlphone (GSM, par exemple) avec getPhoneType() ou celui de
la connexion (comme GPRS, EDGE) avec getNetworkType().
Appeler
Pour effectuer un appel partir d'une application, en utilisant par exemple un numro que
vous avez obtenu par votre propre service web, crez une intention ACTION DIAL avec une
Uri de la forme tel:NNNNN (o NNNNN est le numro de tlphone appeler) et utilisez
cette intention avec startActivity(). Cela ne lancera pas l'appel, mais activera l'activit
du combin, partir duquel l'utilisateur pourra alors appuyer sur un bouton pour effectuer
l'appel.
Voici, par exemple, un chier de disposition simple mais efcace, extrait du projet Phone/
Dialer :
<?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"
>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Numero : "
/>
<EditText android:id="@+id/number"
android:layout_width="fill_parent"
android:layout_height="wrap_content"


android:cursorVisible="true"
android:editable="true"
android:singleLine="true"
/>
</LinearLayout>
<Button android:id="@+id/dial"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Appeler !"
/>
</LinearLayout>
Nous utilisons simplement un champ de saisie pour entrer un numro de tlphone et un
bouton pour appeler ce numro.
Le code Java se contente de lancer le combin en utilisant le numro saisi dans le champ :
package com.commonsware.android.dialer;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class DialerDemo extends Activity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);

final EditText number=(EditText)findViewById(R.id.number);
Button dial=(Button)findViewById(R.id.dial);

dial.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
String toDial="tel:" + number.getText().toString();

startActivity(new Intent(Intent.ACTION_DIAL,
Uri.parse(toDial)));
}
});
}
}


Comme le montre la Figure 35.1, linterface de cette activit nest pas trs impression-
nante.
Cependant, le combin tlphonique que lon obtient en cliquant sur le bouton "Appeler !"
est plus joli, comme le montre la Figure 35.2.
Figure 35.1
Lapplication
DialerDemo lors
de son lancement.
Figure 35.2
Lactivit Dialer
dAndroid lance partir
de DialerDemo.


36
Recherches avec
SearchManager
Lune des socits lorigine de lalliance Open Handset Google dispose dun petit
moteur de recherche dont vous avez sans doute entendu parler. Il nest donc pas tonnant
quAndroid intgre quelques fonctionnalits de recherche.
Plus prcisment, les recherches avec Android ne sappliquent pas seulement aux donnes
qui se trouvent sur lappareil, mais galement aux sources de donnes disponibles sur
Internet.
Vos applications peuvent participer ce processus en dclenchant elles-mmes des recherches
ou en autorisant que lon fouille dans leurs donnes.
Cette fonctionnalit tant assez rcente dans Android, les API risquent dtre
modies : surveillez les mises jour.
In
f
o


La chasse est ouverte
Android dispose de deux types de recherches : locales et globales. Les premires effec-
tuent la recherche dans lapplication en cours tandis que les secondes utilisent le moteur de
Google pour faire une recherche sur le Web. Chacune delles peut tre lance de diffrentes
faons :
Vous pouvez appeler onSearchRequested() partir dun bouton ou dun choix de
menu an de lancer une recherche locale (sauf si vous avez redni cette mthode dans
votre activit).
Vous pouvez appeler directement startSearch() pour lancer une recherche locale ou
globale en fournissant ventuellement une chane de caractres comme point de dpart.
Vous pouvez faire en sorte quune saisie au clavier dclenche une recherche locale avec
setDefaultKeyMode(DEFAULT KEYS SEARCH LOCAL) ou globale avec setDefault
KeyMode(DEFAULT KEYS SEARCH GLOBAL).
Dans tous les cas, la recherche apparat comme un ensemble de composants graphi-
ques disposs en haut de lcran, votre activit apparaissant en ou derrire eux (voir
Figures 36.1 et 36.2).
Figure 36.1
Les composants
de la recherche locale
dAndroid.


Recherches personnelles
terme, il existera deux variantes de recherches disponibles :
les recherches de type requte, o la chane recherche par lutilisateur est passe une
activit qui est responsable de la recherche et de lafchage des rsultats ;
les recherches de type ltre, o la chane recherche par lutilisateur est passe une
activit chaque pression de touche et o lactivit est charge de mettre jour une
liste des correspondances.
Cette dernire approche tant encore en cours de dveloppement, intressons-nous la
premire.
Cration de lactivit de recherche
Pour quune application puisse proposer des recherches de type requte, la premire chose
faire consiste crer une activit de recherche. Bien quil soit possible quune mme
activit puisse tre ouverte partir du lanceur et partir dune recherche, il savre que
cela trouble un peu les utilisateurs. En outre, utiliser une activit spare est plus propre
dun point de vue technique.
Lactivit de recherche peut avoir laspect que vous souhaitez. En fait, part examiner les
requtes, elle ressemble, se comporte et rpond comme toutes les autres activits du
systme. La seule diffrence est quune activit de recherche doit vrier les intentions
Figure 36.2
Les composants de la
recherche globale
dAndroid avec une liste
droulante montrant les
recherches prcdentes.


fournies onCreate() (via getIntent()) et onNewIntent() pour savoir si lune delles
est une recherche, auquel cas elle effectue la recherche et afche le rsultat.
Lapplication Search/Lorem, par exemple, commence comme un clone de lapplication
du Chapitre 8, qui afchait une liste des mots pour dmontrer lutilisation du conteneur
ListView. Ici, nous la modions pour pouvoir rechercher les mots qui contiennent une
chane donne.
Lactivit principale et lactivit de recherche partagent un layout form dune ListView
et dun TextView montrant lentre slectionne :
<?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" >
<TextView
android:id="@+id/selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="false"
/>
</LinearLayout>
Lessentiel du code des activits se trouve dans une classe abstraite LoremBase :
abstract public class LoremBase extends ListActivity {
abstract ListAdapter makeMeAnAdapter(Intent intent);

private static final int LOCAL_SEARCH_ID = Menu.FIRST+1;
private static final int GLOBAL_SEARCH_ID = Menu.FIRST+2;
private static final int CLOSE_ID = Menu.FIRST+3;
TextView selection;
ArrayList<String> items=new ArrayList<String>();

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
selection=(TextView)findViewById(R.id.selection);

try {
XmlPullParser xpp=getResources().getXml(R.xml.words);

while (xpp.getEventType()!=XmlPullParser.END_DOCUMENT) {


if (xpp.getEventType()==XmlPullParser.START_TAG) {
if (xpp.getName().equals("word")) {
items.add(xpp.getAttributeValue(0));
}
}

xpp.next();
}
}
catch (Throwable t) {
Toast
.makeText(this, "Echec de la requete : " + t.toString(), 4000)
.show();
}

setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);

onNewIntent(getIntent());
}

@Override
public void onNewIntent(Intent intent) {
ListAdapter adapter=makeMeAnAdapter(intent);

if (adapter==null) {
finish();
}
else {
setListAdapter(adapter);
}
}

public void onListItemClick(ListView parent, View v, int position,
long id) {
selection.setText(items.get(position).toString());
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, LOCAL_SEARCH_ID, Menu.NONE, "Recherche locale")
.setIcon(android.R.drawable.ic_search_category_default);
menu.add(Menu.NONE, GLOBAL_SEARCH_ID, Menu.NONE, "Recherche globale")
.setIcon(R.drawable.search)
.setAlphabeticShortcut(SearchManager.MENU_KEY);
menu.add(Menu.NONE, CLOSE_ID, Menu.NONE, "Fermeture")
.setIcon(R.drawable.eject)
.setAlphabeticShortcut(f);

return(super.onCreateOptionsMenu(menu));
}


@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case LOCAL_SEARCH_ID:
onSearchRequested();
return(true);

case GLOBAL_SEARCH_ID:
startSearch(null, false, null, true);
return(true);

case CLOSE_ID:
finish();
return(true);
}
return(super.onOptionsItemSelected(item));
}
}
Cette activit prend en charge tout ce qui est li lafchage dune liste de mots, y
compris lextraction des mots partir du chier XML. En revanche, elle ne fournit pas le
ListAdapter placer dans la ListView cette tche est dlgue aux sous-classes.
Lactivit principale LoremDemo utilise simplement un ListAdapter pour la liste de mots :
package com.commonsware.android.search;
import android.content.Intent;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
public class LoremDemo extends LoremBase {
@Override
ListAdapter makeMeAnAdapter(Intent intent) {
return(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
items));
}
}
Lactivit de recherche, cependant, fonctionne un peu diffremment. Elle commence par
inspecter lintention fournie la mthode makeMeAnAdapter() ; cette intention provient
soit donCreate(), soit donNewIntent(). Sil sagit dACTION SEARCH, on sait que cest
une recherche : on peut donc rcuprer la requte et, dans le cas de notre exemple stupide,
drouler la liste des mots chargs pour ne conserver que ceux qui contiennent la chane
recherche. La liste ainsi obtenue est ensuite enveloppe dans un ListAdapter que lon
renvoie pour quil soit afch :
package com.commonsware.android.search;
import android.app.SearchManager;
import android.content.Intent;


import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import java.util.ArrayList;
import java.util.List;
public class LoremSearch extends LoremBase {
@Override
ListAdapter makeMeAnAdapter(Intent intent) {
ListAdapter adapter=null;

if (intent.getAction().equals(Intent.ACTION_SEARCH)) {
String query=intent.getStringExtra(SearchManager.QUERY);
List<String> results=searchItems(query);

adapter=new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
results);
setTitle("LoremSearch de : " + query);
}

return(adapter);
}

private List<String> searchItems(String query) {
List<String> results=new ArrayList<String>();

for (String item : items) {
if (item.indexOf(query)>-1) {
results.add(item);
}
}

return(results);
}
}
Modication du manifeste
Bien que ce code implmente la recherche, il nest pas intgr au systme de recherche
dAndroid. Pour ce faire, vous devez modier le chier AndroidManifest.xml :
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.search">
<application>
<activity android:name=".LoremDemo" android:label="LoremDemo">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>


<meta-data android:name="android.app.default_searchable"
android:value=".LoremSearch" />
</activity>
<activity
android:name=".LoremSearch"
android:label="LoremSearch"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
</application>
</manifest>
Les modications ncessaires sont les suivantes :
1. Lactivit LoremDemo reoit un lment meta data avec un attribut android:name
valant android.app.default searchable et un attribut android:value contenant
la classe qui implmente la recherche (.LoremSearch).
2. Lactivit LoremSearch reoit un ltre dintention pour android.in
tent.action.SEARCH, an que les intentions de recherche puissent tre slectionnes.
3. Lactivit LoremSearch reoit lattribut android:launchMode = "singleTop", ce
qui signie quune seule instance de cette activit sera ouverte un instant donn, an
dviter que tout un lot de petites activits de recherche encombre la pile des activits.
4. Lactivit LoremSearch reoit un lment meta data dot dun attribut
android:name valant android.app.searchable et dun attribut android:value
pointant vers une ressource XML contenant plus dinformations sur la fonctionnalit
de recherche offerte par lactivit (@xml/searchable).
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/searchLabel"
android:hint="@string/searchHint" />
Actuellement, cette ressource XML fournit deux informations :
le nom qui doit apparatre dans le bouton du domaine de recherche droite du champ
de saisie, an dindiquer lutilisateur lendroit o il recherche (android:label) ;
le texte qui doit apparatre dans le champ de saisie, an de donner lutilisateur un
indice sur ce quil doit taper (android:hint).


Effectuer une recherche
Android sait dsormais que votre application peut tre consulte, connat le domaine de
recherche utiliser lors dune recherche partir de lactivit principale et lactivit sait
comment effectuer la recherche.
Le menu de cette application permet de choisir une recherche locale ou une recherche
globale. Pour effectuer la premire, on appelle simplement onSearchRequested() ; pour
la seconde, on appelle startSearch() en lui passant true dans son dernier paramtre,
an dindiquer que la porte de la recherche est globale.
En tapant une lettre ou deux, puis en cliquant sur le bouton, on lance lactivit de recher-
che et le sous-ensemble des mots contenant le texte recherch safche. Le texte tap
apparat dans la barre de titre de lactivit, comme le montre la Figure 36.3.
Vous pouvez obtenir le mme effet en commenant taper dans lactivit principale car
elle est congure pour dclencher une recherche locale.
Figure 36.3
Lapplication Lorem,
montrant une recherche
locale.


37
Outils de dveloppement
Le SDK Android nest pas quune bibliothque de classes Java et dAPI, il contient
galement un certain nombre doutils permettant de faciliter le dveloppement des
applications.
Nous avons surtout voqu le plug-in Eclipse, qui intgre le processus de dveloppement
Android dans cet IDE et nous avons galement cit les plug-in quivalents des autres envi-
ronnements, ainsi que les outils en ligne de commande, comme adb, qui permet de
communiquer avec lmulateur.
Dans ce chapitre, nous nous intresserons aux autres outils.
Gestion hirarchique
Android est fourni avec un outil permettant de visualiser une hirarchie, conu pour vous
aider consulter vos layouts tels quils sont vus par une activit en cours dexcution dans
un mulateur. Vous pouvez ainsi savoir lespace quoccupe un widget ou trouver un widget
particulier.
Pour utiliser cet outil, vous devez dabord lancer lmulateur, installer votre application,
lancer lactivit et naviguer vers lendroit que vous souhaitez examiner. Comme le montre


la Figure 37.1, nous utiliserons titre dexemple lapplication ReadWriteFileDemo que
nous avions prsente au Chapitre 18.
Pour lancer le visualisateur, utilisez le programme hierarchyviewer qui se trouve dans le
rpertoire tools/ de votre installation du SDK. Vous obtiendrez alors la fentre prsente
la Figure 37.2.
Figure 37.1
Lapplication
ReadWrite
FileDemo.
Figure 37.2
Fentre principale du visualisateur hirarchique.


La liste gauche montre les diffrents mulateurs que vous avez chargs. Le nombre aprs
le tiret devrait correspondre au nombre entre parenthses situ dans la barre de titre de
lmulateur. Si vous cliquez sur un mulateur, la liste des fentres accessibles apparat
droite, comme le montre la Figure 37.3.
Vous remarquerez quoutre lactivit ouverte apparaissent de nombreuses autres fentres,
dont celle du lanceur (lcran daccueil), celle du "Keyguard" (lcran noir "Appuyez sur
Menu pour dverrouiller le tlphone" qui apparat lorsque vous ouvrez lmulateur pour
la premire fois), etc. Votre activit est identie par le nom du paquetage et de la classe de
lapplication (com.commonsware.android.files/..., ici).
Les choses commencent devenir intressantes lorsque vous slectionnez lune de ces
fentres et que vous cliquez sur le bouton "Load View Hierarchy". Aprs quelques secondes,
les dtails apparaissent dans une fentre appele "Layout View" (voir Figure 37.4).
La zone principale de cette "Layout View" est occupe par une arborescence des diffren-
tes vues qui composent votre activit, en partant de la fentre principale du systme et en
descendant vers les diffrents widgets graphiques que verra lutilisateur. Dans la branche
infrieure droite de cet arbre, vous retrouverez les widgets LinearLayout, Button et
EditText utiliss par lapplication. Les autres vues sont toutes fournies par le systme, y
compris la barre de titre.
Si vous cliquez sur lune de ces vues, des informations supplmentaires apparaissent dans
le visualisateur, comme le montre la Figure 37.5.
Figure 37.3
Hirarchie des fentres disponibles.


Figure 37.4
Layout View de lapplication ReadWrite FileDemo.
Figure 37.5
Afchage des proprits dune vue.


Dans la rgion suprieure droite du visualisateur, nous pouvons maintenant voir les
proprits du widget slectionn ici, le bouton. Malheureusement, ces proprits ne sont
pas modiables.
En outre, le widget slectionn est surlign en rouge dans la reprsentation schmatique de
lactivit qui apparat sous la liste des proprits (par dfaut, les vues sont reprsentes par
des contours blancs sur un fond noir) : ceci permet de vrier que vous avez slectionn le
bon widget lorsque, par exemple, il y a plusieurs boutons.
Si vous double-cliquez sur une vue de larborescence, un panneau apparat pour ne vous
montrer que cette vue (et ses ls), isole du reste de lactivit.
Dans le coin infrieur gauche de la fentre principale se trouvent deux boutons celui qui
reprsente une arborescence est choisi par dfaut. Si vous cliquez sur le bouton qui repr-
sente une grille, le visualisateur afche une autre reprsentation, appele "Pixel Perfect
View" (voir Figure 37.6).
La partie gauche contient une reprsentation arborescente des widgets et des autres vues
de votre activit. Au milieu se trouve votre activit ("Normal View") et, sur la droite, vous
pouvez voir une version zoome ("Loupe View") de celle-ci.
Figure 37.6
Visualisateur hirarchique en mode "Pixel Perfect View".


Il faut bien comprendre que cette visualisation est en direct : lactivit est interroge selon la
frquence choisie par le curseur "Refresh Rate". Tout ce que vous faites avec cette activit se
retera donc dans les vues "Normal" et "Loupe" de la fentre "Pixel Perfect View".
Les lignes nes de couleur cyan places au-dessus de lactivit montrent la position sur laquelle
le zoom sapplique il suft de cliquer sur une nouvelle zone pour changer lendroit inspect
par la "Loupe View". Un autre curseur permet de rgler la puissance du grossissement.
DDMS (Dalvik Debug Monitor Service)
Lautre outil de larsenal du dveloppeur Android sappelle DDMS (Dalvik Debug Moni-
tor Service). Cest une sorte de "couteau suisse" qui vous permet de parcourir les chiers
journaux, de modier la position GPS fournie par lmulateur, de simuler la rception
dappels et de SMS et de parcourir le contenu de lmulateur pour y placer ou en extraire
des chiers. Nous ne prsenterons ici que les fonctionnalits les plus utiles.
Pour utiliser DDMS, lancez le programme ddms qui se trouve dans le rpertoire tools/
de votre installation du SDK. Au dpart, vous ne verrez dans la partie gauche quune arbo-
rescence des mulateurs avec les programmes quils excutent (voir Figure 37.7).
Cliquer sur un mulateur permet de parcourir le journal des vnements qui apparat dans
la zone du bas et de manipuler lmulateur via un onglet qui se trouve droite (voir
Figure 37.8).
Figure 37.7
Vue initiale
de DDMS.


Journaux
la diffrence dadb logcat, DDMS vous permet dexaminer le contenu du journal dans
un tableau dot dune barre de dlement. Il suft de cliquer sur lmulateur ou le terminal
que vous voulez surveiller pour que le bas de la fentre afche le contenu du journal.
En outre, vous pouvez agir comme suit :
Filtrer les entres du journal selon lun des cinq niveaux reprsents par les boutons E
V dans la barre doutils.
Crer un ltre personnalis pour ne voir que les entres correspondantes. Pour ce faire,
cliquez sur le bouton + et remplissez le formulaire : le nom que vous choisirez pour ce
ltre sera utilis pour nommer un autre onglet qui apparatra ct du contenu du
journal (voir Figure 37.9).
Sauvegarder les entres du journal dans un chier texte, an de pouvoir les rutiliser
plus tard.
Figure 37.8
mulateur
slectionn
dans DDMS.
Figure 37.9
Filtrage des entres du
journal avec DDMS.


Stockage et extraction de chiers
Bien que vous puissiez utiliser adb pull et adb push pour, respectivement, extraire ou stocker
des chiers sur un mulateur ou un terminal, DDMS permet de le faire de faon plus visuelle.
Il suft, pour cela, de slectionner lmulateur ou le terminal concern, puis de choisir
loption Device > File Explorer... partir du menu principal : une fentre de dialogue typique
comme celle de la Figure 37.10 permet alors de parcourir larborescence des chiers.
Slectionnez simplement le chier concern et cliquez sur le bouton dextraction (
gauche) ou de stockage (au milieu) de la barre doutils pour le transfrer vers ou partir de
votre machine de dveloppement. Le bouton de suppression ( droite) permet de supprimer
le chier slectionn.
Cet outil ne permet pas de crer de rpertoire : pour cela, vous devez soit utiliser
la commande adb shell, soit les crer partir de votre application.
Bien que vous puissiez parcourir la plupart des chiers dun mulateur, les
restrictions de scurit dAndroid limitent beaucoup laccs en dehors de
larborescence /sdcard sur un vrai terminal.
Copies dcran
Pour faire une copie dcran de lmulateur ou dun terminal Android, faites simplement
Ctrl+S ou choisissez Device > Screen capture... dans le menu principal. Ceci ouvrira une
bote de dialogue contenant une image de lcran courant, comme la Figure 37.11.
partir de l, vous pouvez cliquer sur "Save" pour sauvegarder limage au format PNG
sur votre machine de dveloppement, rafrachir limage partir de ltat courant de
lmulateur ou du terminal, ou cliquer sur "Done" pour fermer la bote de dialogue.
Figure 37.10
Explorateur
de chiers
de DDMS.
In
f
o


Mise jour de la position
Pour que DDMS mette jour la position de votre application, vous devez dabord faire en
sorte que lapplication utilise le fournisseur de localisation gps, car cest le seul que
DDMS sait modier. Puis cliquez sur longlet "Emulator Control" et recherchez la section
"Location Controls". Dans celle-ci, vous trouverez un cadre avec trois onglets permettant
de choisir le format des coordonnes : "Manual", "GPX" et "KML" (voir Figure 37.12).
Figure 37.11
Capture dcran
avec DDMS.
Figure 37.12
Contrle
de la position
avec DDMS.


Lutilisation de longlet "Manual" est assez vidente : on fournit une latitude et une
longitude et lon clique sur le bouton "Send" pour envoyer cet emplacement
lmulateur. Ce dernier, son tour, prviendra les couteurs de localisation de la nouvelle
position.
La prsentation des formats GPX et KML sort du cadre de ce livre.
Appels tlphoniques et SMS
DDMS sait galement simuler la rception dappels tlphoniques et de SMS via le
groupe "Telephony Actions" de longlet "Emulator Control" (voir Figure 37.13).
Pour simuler un appel tlphonique, saisissez un numro, cochez le bouton radio "Voice"
puis cliquez sur le bouton "Call". Comme le montre la Figure 37.14, lmulateur afchera
lappel entrant et vous demandera si vous lacceptez (avec le bouton vert du tlphone) ou
si vous le rejetez (avec le bouton rouge).
Pour simuler la rception dun SMS, saisissez un numro tlphonique, cochez le bouton
radio "SMS", saisissez un message dans la zone de texte et cliquez sur "Send". Le message
apparatra sous la forme dune notication, comme la Figure 37.15.
En cliquant sur la notication, vous pourrez voir le contenu intgral du message, comme le
montre la Figure 37.16.
Figure 37.13
Contrle de la tlphonie avec DDMS.


Figure 37.14
Simulation de
la rception dun appel.
Figure 37.15
Simulation de
la rception dun SMS.
Figure 37.16
Simulation de
la rception dun SMS
dans lapplication
SMS/MMS.


Gestion des cartes amovibles
Le G1 dispose dun emplacement pour carte microSD et de nombreux autres terminaux
Android disposent dune forme similaire de stockage amovible, dsign de faon gnrique
par le terme "Carte SD".
Les cartes SD servent stocker les gros chiers, comme les images, les clips vido, les
chiers musicaux, etc. La mmoire interne du G1, notamment, est relativement peu impor-
tante et il est prfrable de stocker un maximum de donnes sur une carte SD.
Bien que le G1 ait une carte SD par dfaut, le problme, videmment, est que lmulateur
nen a pas. Pour que ce dernier se comporte comme le G1, vous devez donc crer et "ins-
rer" une carte SD dans lmulateur.
Cration dune image de carte
Au lieu dexiger que les mulateurs aient accs un vrai lecteur de carte SD pour utiliser
de vraies cartes, Android est congur pour utiliser des images de cartes. Une image est
simplement un chier que lmulateur traitera comme sil sagissait dun volume de
carte SD : il sagit en fait du mme concept que celui utilis par les outils de virtualisation
(comme VirtualBox) Android utilise une image disque pour reprsenter le contenu dune
carte SD.
Pour crer cette image, utilisez le programme mksdcard qui se trouve dans le rpertoire
tools/ de votre installation du SDK. Ce programme attend au moins deux paramtres :
1. La taille de limage et donc de la "carte". Si vous fournissez un nombre, celui-ci sera
interprt comme un nombre doctets. Vous pouvez galement le faire suivre de K ou M
pour prciser que cette taille est, respectivement, exprime en kilo-octets ou en
mgaoctets.
2. Le nom du chier dans lequel stocker limage.
Pour, par exemple, crer limage dune carte SD de 1 Go an de simuler celle du GI dans
lmulateur, faites :
mksdcard 1024M sdcard.img
Insertion de la carte
Pour que lmulateur utilise cette image de carte, lancez-le avec loption sdcard suivie
du chemin complet vers le chier image cr avec mksdcard. Bien que cette option nait
pas deffet visible aucune icne dAndroid ne montrera quune carte est monte , le
rpertoire /sdcard sera dsormais accessible en lecture et en criture.
Pour placer et lire des chiers dans /sdcard, utilisez lexplorateur de chiers de DDMS
ou les commandes adb push et adb pull partir de la console.


38
Pour aller plus loin
Ce livre ne couvre videmment pas tous les sujets possibles et, bien que la ressource
numro un (en dehors de cet ouvrage) soit la documentation du SDK Android, vous aurez
srement besoin dinformations qui se trouvent ailleurs.
Une recherche web sur le mot "android" et un nom de classe est un bon moyen de trouver
des didacticiels pour une classe donne. Cependant, noubliez pas que les documents
crits avant n 2008 concernent probablement le SDK M5 et ncessiteront donc des modi-
cations trs importantes pour fonctionner correctement avec les SDK actuels.
Ce chapitre vous donnera donc quelques pistes explorer.
Questions avec, parfois, des rponses
Les groupes Google consacrs Android sont les endroits ofciels pour obtenir de laide.
Trois groupes sont consacrs au SDK :
Android Beginners
1
est le meilleur endroit pour poster des questions de dbutant.
1. http://groups.google.com/group/android-beginners.


Android Developers
1
est consacr aux questions plus compliques ou celles qui relvent
de parties plus exotiques du SDK.
Android Discuss
2
est rserv aux discussions btons rompus sur tout ce qui est li
Android, pas ncessairement aux problmes de programmation.
Vous pouvez galement consulter :
les didacticiels et les forums consacrs Android sur le site anddev.org
3
;
le canal IRC #android sur freenode.
Aller la source
Le code source dAndroid est dsormais disponible, essentiellement pour ceux qui
souhaitent amliorer le systme ou jouer avec ses dtails internes. Toutefois, vous
pouvez aussi y trouver les rponses que vous recherchez, notamment si vous voulez
savoir comment fonctionne un composant particulier.
Le code source et les ressources qui y sont lies se trouvent sur le site web du projet
Android
4
. partir de ce site, vous pouvez :
tlcharger
5
ou parcourir
6
le code source ;
signaler des bogues
7
du systme lui-mme ;
proposer des patchs
8
et apprendre comment ces patchs sont valus et approuvs ;
rejoindre un groupe Google particulier
9
pour participer au dveloppement de la plate-
forme Android.
Citons galement quelques ressources francophones :
les sites www.frandroid.com et www.pointgphone.com, qui proposent des articles et
des forums de discussion.
groups.google.com/group/android-fr, qui est un groupe Google francophone consacr
Android.
1. http://groups.google.com/group/android-developers.
2. http://groups.google.com/group/android-discuss.
3. http://anddev.org/.
4. http://source.android.com.
5. http://source.android.com/download.
6. http://git.source.android.com/.
7. http://source.android.com/report-bugs.
8. http://source.android.com/submit-patches.
9. http://source.android.com/discuss.


Lire les journaux
Ed Burnette, qui a crit son propre livre sur Android, est galement le gestionnaire de
Planet Android
1
, un agrgateur de ux pour un certain nombre de blogs consacrs
Android. En vous abonnant ce ux, vous pourrez ainsi surveiller un grand nombre darticles,
pas ncessairement consacrs la programmation.
Pour surveiller plus prcisment les articles lis la programmation dAndroid, faites une
recherche sur le mot-cl "android" sur Dzone ; vous pouvez galement vous abonner un
ux
2
qui rsume cette recherche.
1. http://www.planetandroid.com/.
2. http://www.dzone.com/links/feed/search/android/rss.xml.


Index
A
aapt, outil 30
AbsoluteLayout, conteneur 128
Acclromtres 265
Activity, classe 25, 165, 272
activity, lment 272
activity, lment du manifeste 15
ActivityAdapter, adaptateur 67
ActivityAdapter, classe 264
activityCreator, script 203
ActivityIconAdapter, adaptateur 67
ActivityManager, classe 161
Adaptateur 65
adb, programme 224, 351, 357, 358, 362
add(), mthode 130, 332
addId(), mthode 280
addIntentOptions(), mthode 131, 263, 264
addMenu(), mthode 131
addProximityAlert(), mthode 325
addSubMenu(), mthode 131
addTab(), mthode 116
AlertDialog, classe 156
AnalogClock, widget 111
android
alphabeticShortcut, attribut 140
apiKey, attribut 329, 336
authorities, attribut 295
autoText, attribut de widget 38
background, attribut 44
capitalize, attribut de widget 38
clickable, attribut 329
collapseColumns, proprit 59
columnWidth, proprit 74
completionThreshold, proprit 77
configChanges, attribut 272
content, attribut 127
digits, attribut de widget 38
drawSelectorOnTop, proprit 71, 81
ellipsize, attribut 145
enabled, attribut 139
handle, attribut 127
hint, attribut 348
horizontalSpacing, proprit 74
icon, attribut 139
id, attribut 114, 138, 329
id, attribut de main.xml 31
inputMethod, attribut de widget 38
label, attribut 348
launchMode, attribut 348
layout_above, proprit 54
layout_alignBaseline, proprit 54
layout_alignBottom, proprit 54


android (suite)
layout_alignLeft, proprit 54
layout_alignParentBottom, proprit 53
layout_alignParentLeft, proprit 53
layout_alignParentRight, proprit 53
layout_alignParentTop, proprit 53, 56
layout_alignRight, proprit 54
layout_alignTop, proprit 54
layout_below, proprit 54
layout_centerHorizontal, proprit 53
layout_centerInParent, proprit 53
layout_centerVertical, proprit 53
layout_column, proprit 58
layout_gravity, proprit 47
layout_height, attribut 114
layout_span, proprit 58
layout_toLeftOf, proprit 54
layout_toRightOf, proprit 54
layout_weight, proprit 47
layout_width, attribut de main.xml 31
layout_width, proprit 46, 55
menuCategory, attribut 139
name, attribut 295, 298, 306, 348
name, attribut du manifeste 15
nextFocusDown, attribut 43
nextFocusLeft, attribut 43
nextFocusRight, attribut 43
nextFocusUp, attribut 43
numColumns, proprit 74
numeric, attribut de widget 38
numericShortcut, attribut 140
orderInCategory, attribut 139
orientation, proprit 46
padding, proprit 47
paddingLeft, proprit 48
paddingTop, proprit 114
password, attribut de widget 38
permission, attribut 300, 306
phoneNumber, attribut de widget 38
readPermission, attribut 300
screenOrientation, attribut 274
shrinkColumns, proprit 59
singleLine, attribut de widget 38
spacing, proprit 81
spinnerSelector, proprit 81
src, attribut de widget 37
stretchColumns, proprit 59
stretchMode, proprit 74
text, attribut de main.xml 31
text, attribut de widget 36
typeface, attribut 143
value, attribut 348
versionCode, attribut du manifeste 17
verticalSpacing, proprit 74
visibility, attribut 44
visible, attribut 139
writePermission, attribut 300
Android Scripting Environment (ASE) 233
android, paquetage 25
android, script 22, 23
AndroidManifest 182
AndroidManifest.xml, fichier 9, 13, 148,
295, 298, 306, 347
animateClose(), mthode 128
animateOpen(), mthode 128
animateToggle(), mthode 128
Animations 124
apk, fichier 11
appendWhere(), mthode 223
application, lment 295, 306
application, lment du manifeste 14
Arborescence de rpertoires 9
ArrayAdapter, adaptateur 66, 69, 85, 96
ArrayAdapter, classe 169, 194
ArrayList, classe 194
AssetManager, classe 143
AsyncTask, classe 166, 307
AutoCompleteTextView, widget 39, 77
Auto-compltion 77
AVD (Android Virtual Device) 22
B
BaseColumns, interface 295
BeanShell, programme 229
beforeTextChanged(), mthode 79


bindView(), mthode 105
BLOB (Binary Large Object) 286
BroadcastReceiver, classe 251, 311
BroadcastReceiver, interface 247
build.xml, fichier 10
Builder, classe 156
buildQuery(), mthode 223
bulkInsert(), mthode 285
Bundle, classe 176, 252, 266, 271
Bundle, objet 174
Button, widget 36
C
Calendar, classe 110
cancel(), mthode 314
cancelAll(), mthode 314
canGoBack(), mthode 151
canGoBackOrForward(), mthode 151
canGoForward(), mthode 151
Catgories d'activits 244
check(), mthode 42
CheckBox, widget 40
CheckBoxPreference, lment 181
checkCallingPermission(), mthode 301
clear(), mthode 180
clearCache(), mthode 151
clearCheck(), mthode 42
clearHistory(), mthode 151
close(), mthode 128, 195, 219, 224
color, lment 211
commit(), mthode 180
ComponentName, classe 264
CompoundButton, widget 42
ContentManager, classe 314
ContentObserver, classe 296
ContentProvider, classe 289
ContentProvider, interface 285
ContentResolver, classe 285, 296
ContentValues, classe 220, 285, 291, 292
Context, classe 179, 195
ContextMenuInfo, classe 132
convertView, paramtre de getView() 89
create(), mthode 157
createDatabase(), mthode 225
createFromAsset(), mthode 143
createItem(), mthode 332
createTabContent(), mthode 118
Criteria, classe 323
Cursor, classe 221
Cursor, interface 281, 290
Cursor, widget 67
CursorAdapter, adaptateur 67, 105
CursorFactory, classe 224
D
DatabaseHelper, classe 289
DateFormat, classe 110
DatePicker, widget 108
DatePickerDialog, widget 108
DDMS (Dalvik Debug Monitor Service) 326
ddms, programme 356
default.properties, fichier 10
DefaultHttpClient, classe 236
delete(), mthode 220, 285, 293
dex, programme 229
DialogWrapper, classe 285
DigitalClock, widget 111
dimen, lment 211
doInBackground(), mthode 167
draw(), mthode 333
Drawable, classe 333
Drawable, interface 205
E
edit(), mthode 180
EditText, widget 38
EditTextPreference, lment 188
Espace de noms 14
execSQL(), mthode 219
execute(), mthode 166, 171, 236
ExpandableListView, classe 128


F
fill_parent, valeur de remplissage 46
findViewById (), mthode 32
findViewById() 91
findViewById(), mthode 44, 89, 116,
191, 330
finish(), mthode 175, 197
Forecast, classe 239, 308
format(), mthode 201
FrameLayout, conteneur 114, 121
fromHtml(), mthode 202
G
Gallery, widget 81
GeoPoint, classe 331
getAltitude(), mthode 323
getAsInteger(), mthode 220
getAssets(), mthode 143
getAsString(), mthode 220
getAttributeCount(), mthode 209
getAttributeName(), mthode 209
getBearing(), mthode 323
getBestProvider(), mthode 323
getBoolean(), mthode 180
getCallState(), mthode 338
getCheckedItemPositions(), mthode 71
getCheckedRadioButtonId(), mthode 42
getColor(), mthode 211
getColumnIndex(), mthode 224
getColumnNames(), mthode 224
getContentProvider(), mthode 285
getContentResolver(), mthode 296
getCount(), mthode 223
getDefaultSharedPreferences(), mthode 180
getDimen(), mthode 211
getFloat(), mthode 284
getInputStream(), mthode 286
getInt(), mthode 224, 284
getIntent(), mthode 344
getItemId(), mthode 131
getLastKnownPosition(), mthode 323
getLastNonConfigurationInstance(), mthode
270
getLatitude(), mthode 237
getListView(), mthode 69
getLongitude(), mthode 237
getMapController(), mthode 330
getMenuInfo(), mthode 132
getNetworkType(), mthode 338
getOutputStream(), mthode 286
getOverlays(), mthode 332
getPackageManager(), mthode 264
getParent(), mthode 44
getPhoneType(), mthode 338
getPosition(), mthode 284
getPreferences(), mthode 179
getProgress(), mthode 113
getProviders(), mthode 323
getReadableDatabase(), mthode 219
getRequiredColumns(), mthode 292
getResources(), mthode 191, 194, 208
getRootView(), mthode 44
getSettings(), mthode 149, 153
getSharedPreferences(), mthode 179
getSpeed(), mthode 323
getString(), mthode 201, 224, 284
getStringArray(), mthode 212
getSubscriberId(), mthode 338
getSystemService(), mthode 314, 322
getTableName(), mthode 223
getTag(), mthode 91, 97
getType(), mthode 294
getView(), mthode 66, 86, 89, 105, 283
getWriteableDatabase(), mthode 219
getXml(), mthode 207
goBack(), mthode 151
goBackOrForward(), mthode 151
goForward(), mthode 151
GPS (Global Positioning System) 321
group, lment 138


H
handleMessage(), mthode 162
Handler, classe 162, 311
hasAltitude(), mthode 323
hasBearing(), mthode 323
hasSpeed(), mthode 323
hierarchyviewer, programme 352
htmlEncode(), mthode 204
HttpClient, classe 304
HttpClient, interface 236
HttpComponents, bibliothque 236
HttpGet, classe 236
HttpPost, classe 236
HttpRequest, interface 236
HttpResponse, classe 236
I
IBinder, classe 305
ImageView, classe 286
ImageView, widget 37
incrementProgressBy(), mthode 113
indiquant 182
InputMethod, interface 38
InputStream, classe 191, 195, 239
InputStreamReader, classe 195
insert(), mthode 220, 285, 291
instrumentation, lment du manifeste 14
Intent, classe 244
intent-filter, lment 245
Internationalisation (I18N) 200, 212
Interpreter, classe de BeanShell 229
isAfterLast(), mthode 223, 284
isBeforeFirst(), mthode 284
isChecked(), mthode 40
isCollectionUri(), mthode 292, 293
isEnabled(), mthode 44
isFirst(), mthode 284
isLast(), mthode 284
isNull(), mthode 284
isRouteDisplayed(), mthode 330
item, lment 138, 212
ItemizedOverlay, classe 332
Items, classe 264
Iterator, interface 284
J
JavaScript, et WebView 149
JRuby, langage de script 233
Jython, langage de script 233
K
keytool, utilitaire 336
L
LayoutInflater, classe 87, 103
LinearLayout, conteneur 46, 84, 97, 103
ListActivity, classe 67, 168
ListAdapter, adaptateur 100
ListPreference, lment 188
ListView, widget 67, 83
loadData(), mthode 150
loadUrl(), mthode 148
Localisation (L10N) 200, 212
Location, classe 237, 323
LocationListener, classe 324
LocationManager, classe 304, 322
LocationProvider, classe 322
lock(), mthode 128
M
makeText(), mthode 156
managedQuery(), mthode 281
manifest, lment 298
manifest, lment racine du manifeste 14
MapActivity, classe 327, 328
MapView, classe 327, 328
Menu, classe 130, 140, 263
menu, lment 138
MenuInflater, classe 140
MenuItem, classe 130
Message, classe 162


meta-data, lment 348
mthode 91
MIME, types 288
mksdcard, programme 362
move(), mthode 284
moveToFirst(), mthode 223, 284
moveToLast(), mthode 284
moveToNext(), mthode 223, 284
moveToPosition(), mthode 284
moveToPrevious(), mthode 284
MyLocationOverlay, classe 334
N
name, attribut 200, 211, 212
newCursor(), mthode 224
newTabSpec(), mthode 115
newView(), mthode 105
next(), mthode 208
Notification, classe 172
NotificationManager, classe 314
notify(), mthode 314
notifyChange(), mthode 296
O
obtainMessage(), mthode 162
onActivityResult(), mthode 251, 260
onBind(), mthode 305
OnCheckedChangeListener, interface 50
onClick(), mthode 26
OnClickListener(), mthode 158
OnClickListener, classe 110, 253
OnClickListener, interface 26
onConfigurationChanged(), mthode 272
onContextItemSelected(), mthode 132, 134
onCreate(), mthode 26, 134, 174, 176, 184,
194, 219, 266, 282, 289, 304, 344, 346
onCreateContextMenu(), mthode 132, 134
onCreateOptionsMenu(), menu 134
onCreateOptionsMenu(), mthode 130, 131
onCreatePanelMenu(), mthode 131
OnDateChangedListener, classe 108
OnDateSetListener, classe 108
onDestroy(), mthode 174, 304
OnItemSelectedListener, interface 72
onListItemClick(), mthode 69, 97
onLocationChanged(), mthode 324
onNewIntent(), mthode 344, 346
onOptionsItemSelected(), mthode 130, 131,
134
onPageStarted(), mthode 151
onPause(), mthode 175, 197, 247, 304, 311,
335
onPostExecute(), mthode 167
onPreExecute(), mthode 167
onPrepareOptionsMenu(), mthode 130
onProgressUpdate(), mthode 168
onRatingChanged(), mthode 97
onReceive(), mthode 247
onReceivedHttpAuthRequest(), mthode 151
onRestart(), mthode 175
onRestoreInstanceState(), mthode 176, 266
onResume(), mthode 175, 184, 197, 247,
304, 311, 335
onRetainNonConfigurationInstance(), mthode
270
onSaveInstanceState(), mthode 174, 176,
248, 266, 267
onSearchRequested(), mthode 342, 349
onStart(), mthode 164, 175, 304
onStop(), mthode 175
onTap(), mthode 334
onTextChanged(), mthode 79
OnTimeChangedListener, classe 108
OnTimeSetListener, classe 108
onTooManyRedirects(), mthode 151
onUpgrade(), mthode 219
open(), mthode 128
openFileInput(), mthode 195, 197
openFileOutput(), mthode 195, 197
openRawResource(), mthode 191, 194
OpenStreetMap, cartographie 328
OutputStream, classe 195


OutputStreamWriter, classe 195
Overlay, classe 332
OverlayItem, classe 332
P
package, attribut du manifeste 14
PackageManager, classe 264
parse(), mthode 280, 281
PendingIntent, classe 314, 325
permission, lment 299
permission, lment du manifeste 14
populate(), mthode 332
populateDefaultValues(), mthode 292
post(), mthode 165
postDelayed(), mthode 165, 311
PreferenceCategory, lment 185
PreferenceScreen, lment 181, 185
PreferencesManager, classe 180
ProgressBar, widget 113, 163
provider, lment 295
provider, lment du manifeste 16
publishProgress(), mthode 168
Q
query(), mthode 221, 290
queryIntentActivityOptions(), mthode 264
queryWithFactory(), mthode 224
R
R.java, fichier 10
RadioButton, widget 42
RadioGroup, widget 42
RatingBar, widget 94
rawQuery(), mthode 221
rawQueryWithFactory(), mthode 224
receiver, lment 247
receiver, lment du manifeste 16
registerContentObserver(), mthode 296
registerForContextMenu(), mthode 131
registerReceiver(), mthode 247
RelativeLayout, conteneur 52
reload(), mthode 151
remove(), mthode 180
removeProximityAlert(), mthode 325
removeUpdates(), mthode 325
requery(), mthode 224, 285
requestFocus(), mthode 44
requestLocationUpdates(), mthode 324
Resources, classe 191, 207
resources, lment 210
ResponseHandler, classe 236
ressources, lment 200
RingtonePreference, lment 181
RowModel, classe 96
Runnable, classe 165
runOnUiThread(), mthode 165
S
ScrollView, conteneur 61
SecurityException, exception 298
sendBroadcast(), mthode 251, 301, 308
sendMessage(), mthode 162
sendMessageAtFrontOfQueue(), mthode 162
sendMessageAtTime(), mthode 162
sendMessageDelayed(), mthode 162
sendOrderedBroadcast(), mthode 251
Service, classe 304
service, lment 306
service, lment du manifeste 16
setAccuracy(), mthode 323
setAdapter(), mthode 67, 71
setAlphabeticShortcut(), mthode 131
setAltitudeRequired(), mthode 323
setBuiltInZoomControls(), mthode 331
setCenter(), mthode 331
setCheckable(), mthode 131
setChecked(), mthode 40, 43
setChoiceMode(), mthode 69
setColumnCollapsed(), mthode 59
setColumnStretchable(), mthode 59
setContent(), mthode 115, 118
setContentView(), mthode 32


setCostAllowed(), mthode 323
setCurrentTab(), mthode 116
setDefaultFontSize(), mthode 153
setDefaultKeyMode(), mthode 342
setDropDownViewResource(), mthode 71, 72
setDuration(), mthode 156
setEnabled(), mthode 139
setFantasyFontFamily(), mthode 153
setFlipInterval(), mthode 125
setGravity(), mthode 47
setGroupCheckable(), mthode 130, 131
setGroupEnabled(), mthode 139
setGroupVisible(), mthode 139
setIcon(), mthode 157
setImageURI(), mthode 37
setIndeterminate(), mthode 113
setIndicator(), mthode 115
setJavaScriptCanOpenWindowsAutomatical-
ly(), mthode 153
setJavaScriptEnabled(), mthode 149, 153
setLatestEventInfo(), mthode 315
setListAdapter(), mthode 69
setMax(), mthode 113, 164
setMessage(), mthode 156
setNegativeButton(), mthode 157
setNeutralButton(), mthode 157
setNumericShortcut(), mthode 131
setOnCheckedChanged(), mthode 41
setOnCheckedChangeListener(), mthode 41
setOnClickListener(), mthode 26, 197
setOnItemSelectedListener(), mthode 67, 71
setOrientation(), mthode 46
setProgress(), mthode 113
setProjectionMap(), mthode 223
setQwertyMode(), mthode 131
setResult(), mthode 252
setTables(), mthode 223
setTag(), mthode 91, 97
setText(), mthode 27
setTextSize(), mthode 153
setTitle(), mthode 157
setTypeface(), mthode 143
setup(), mthode 116
setUserAgent(), mthode 154
setView(), mthode 156
setVisible(), mthode 139
setWebViewClient(), mthode 151
setZoom(), mthode 330
SharedPreferences, classe 180, 188
shouldOverrideUrlLoading(), mthode 151
show(), mthode 156
showNext(), mthode 122
SimpleAdapter, adaptateur 67
SimpleCursorAdapter, classe 282
Singleton, patron de conception 305
size(), mthode 332
SlidingDrawer, widget 126
SOAP, protocole 236
SoftReference, classe 307
Spanned, interface 201
Spinner, widget 71
SQLite Manager, extension Firefox 225
sqlite3, programme 224
SQLiteDatabase, classe 219
SQLiteOpenHelper, classe 219
SQLiteQueryBuilder, classe 221, 290
SSL et HttpClient 240
startActivity(), mthode 251, 338
startActivityForResult(), mthode 251, 260
startFlipping(), mthode 125
startSearch(), mthode 342, 349
startService(), mthode 310
stopService(), mthode 310
string, lment 200
string-array, lment 212
SystemClock, classe 162
T
TabActivity, classe 114, 255
TabContentFactory(), mthode 118
TabHost, classe 256
TabHost, conteneur 113
TableLayout, conteneur 57, 184
TableRow, conteneur 57


TabSpec, classe 115
TabView, conteneur 255
TabWidget, widget 114
TelephonyManager, classe 338
TextView, widget 35, 67, 76, 85, 143
TextWatcher, interface 77
TimePicker, widget 108
TimePickerDialog, widget 108
Toast, classe 156
toggle(), mthode 40, 128
toggleSatellite(), mthode 331
TrueType, polices 143
Typeface, classe 143
U
unlock(), mthode 128
unregisterContentObserver(), mthode 296
unregisterReceiver(), mthode 247
update(), mthode 220, 292
uptimeMillis(), mthode 162
Uri, classe 244, 260, 279, 338
uses-library, lment du manifeste 14
uses-permission, lment 298
uses-sdk, lment du manifeste 14, 16
V
Versions du SDK 16
View, classe 26
View, widget 59, 86, 87
ViewFlipper, conteneur 120
Virus 297
W
WeakReference, classe 307
WebKit, widget 201, 235
WebSettings, classe 153
WebView, widget 147
WebViewClient, classe 151
wrap_content, valeur de remplissage 46
X
XmlPullParser, classe 208
XML-RPC, protocole 236

Tour dhorizon
Structure dun projet
Contenu du manifeste
Cration dun squelette dapplication
Utilisation des layouts XML
Utilisation des widgets de base
Conteneurs
Widgets de slection
Samuser avec les listes
Utiliser de jolis widgets et de beaux conteneurs
Utilisation des menus
Polices de caractres
Intgrer le navigateur de WebKit
Afchage de messages surgissant
Utilisation des threads
Gestion des vnements du cycle de vie dune
activit
Utilisation des prfrences
Accs aux chiers
Utilisation des ressources
Accs et gestion des bases de donnes locales
Tirer le meilleur parti des bibliothques Java
Communiquer via Internet
Cration de ltres dintentions
Lancement dactivits et de sous-activits
Trouver les actions possibles grce lintrospection
Gestion de la rotation
Utilisation dun fournisseur de contenu (content
provider)
Construction dun fournisseur de contenu
Demander et exiger des permissions
Cration dun service
Appel dun service
Alerter les utilisateurs avec des notications
Accs aux services de localisation
Cartographie avec MapView et MapActivity
Gestion des appels tlphoniques
Recherches avec SearchManager
Outils de dveloppement
Pour aller plus loin
laide dexemples simples et faciles excuter,
apprenez dvelopper des applications pour
terminaux Android.
Smartphones, PDA et autres terminaux mobiles
connaissent aujourdhui une vritable explosion. Dans
ce contexte, Android, le systme dexploitation mobile
cr par Google, prsente le double avantage dtre
gratuit et open-source. Libre donc tout un chacun
den exploiter lnorme potentiel !
Dans cet ouvrage, Mark Murphy, dveloppeur et
membre actif de la communaut Android, vous expli-
que tout ce que vous avez besoin de savoir pour
programmer des applications de la cration des
interfaces graphiques lutilisation de GPS, en passant
par laccs aux services web et bien dautres choses
encore ! Vous y trouverez une mine dastuces et de
conseils pour raliser vos premires applications Android
mais aussi pour accder facilement aux squences de
code qui vous intressent.
travers des dizaines dexemples de projets, vous
assimilerez les points techniques les plus dlicats et
apprendrez crer rapidement des applications
convaincantes.
Les codes sources du livre sont disponibles sur
www.pearson.fr.
Niveau : Intermdiaire / Avanc
Catgorie : Dveloppement mobile
Lart du dveloppement
Android
propos de lauteur
Mark Murphy programme depuis plus de 25 ans et a travaill sur des plates-
formes allant du TRS-80 aux derniers modles de terminaux mobiles. Il est
le rdacteur des rubriques Building Droids de AndroidGuys et Android
Angle de NetworkWorld.
Pearson Education France
47 bis, rue des Vinaigriers 75010 Paris
Tl. : 01 72 74 90 00
Fax : 01 42 05 22 17
www.pearson.fr
ISBN : 978-2-7440-4094-8